import { LoggingService } from './logging.service'; describe('LoggingService', () => { it('can persist an event through the caller transaction manager', async () => { const defaultRepository = { save: jest.fn() }; const transactionRepository = { save: jest.fn() }; const manager = { getRepository: jest.fn(() => transactionRepository), } as any; const service = new LoggingService(defaultRepository as any); const event = { event: 'admin_user_profile_update' as const, details: 'targetUserId=2', userId: 1, }; await service.info(event, manager); expect(transactionRepository.save).toHaveBeenCalledWith({ ...event, level: 'INFO', }); expect(defaultRepository.save).not.toHaveBeenCalled(); }); }); describe('LoggingService.findLogs', () => { let rows: any[]; let total: number; let query: any; let repository: any; let service: LoggingService; beforeEach(() => { rows = []; total = 0; query = chain({ getMany: jest.fn(() => rows), getCount: jest.fn(() => total), }); repository = { createQueryBuilder: jest.fn(() => query), }; service = new LoggingService(repository); }); it('returns a paginated page with data, total and hasNextPage', async () => { rows = [ { id: 1, level: 'INFO', event: 'team_create', details: 'teamId=5', userId: 3, createdAt: new Date('2026-08-01') }, ]; total = 21; const result = await service.findLogs({ page: 1, limit: 20 }); expect(result).toEqual({ data: rows, page: 1, limit: 20, total: 21, hasNextPage: true }); expect(query.orderBy).toHaveBeenCalledWith('log.createdAt', 'DESC'); expect(query.offset).toHaveBeenCalledWith(0); expect(query.limit).toHaveBeenCalledWith(20); }); it('reports hasNextPage=false on the last page', async () => { total = 20; const result = await service.findLogs({ page: 1, limit: 20 }); expect(result.hasNextPage).toBe(false); }); it('offsets by (page - 1) * limit', async () => { await service.findLogs({ page: 3, limit: 10 }); expect(query.offset).toHaveBeenCalledWith(20); }); it('filters by level and event when provided', async () => { await service.findLogs({ page: 1, limit: 20, level: 'ERROR', event: 'cashbox_export_subscription_run_fail' }); expect(query.andWhere).toHaveBeenCalledWith('log.level = :level', { level: 'ERROR' }); expect(query.andWhere).toHaveBeenCalledWith('log.event = :event', { event: 'cashbox_export_subscription_run_fail', }); }); it('filters by an inclusive date range when from/to are provided', async () => { await service.findLogs({ page: 1, limit: 20, from: '2026-01-01', to: '2026-01-31' }); expect(query.andWhere).toHaveBeenCalledWith('log.createdAt >= :from', { from: '2026-01-01' }); expect(query.andWhere).toHaveBeenCalledWith('log.createdAt <= :to', { to: '2026-01-31' }); }); it('does not add level/event/date filters when omitted', async () => { await service.findLogs({ page: 1, limit: 20 }); expect(query.andWhere).not.toHaveBeenCalled(); }); it('filters details by a case-insensitive search term', async () => { await service.findLogs({ page: 1, limit: 20, search: ' TeamId=5 ' }); expect(query.andWhere).toHaveBeenCalledWith('LOWER(log.details) LIKE :search', { search: '%teamid=5%', }); }); it('ignores a blank search term', async () => { await service.findLogs({ page: 1, limit: 20, search: ' ' }); expect(query.andWhere).not.toHaveBeenCalled(); }); function chain(overrides: Record) { const builder: Record = {}; ['andWhere', 'orderBy', 'offset', 'limit'].forEach((method) => { builder[method] = jest.fn(() => builder); }); return Object.assign(builder, overrides); } });