import { Test, TestingModule } from '@nestjs/testing'; import { GetOnlineHistoryHandler } from '../../../../src/application/queries/get-online-history/get-online-history.handler'; import { GetOnlineHistoryQuery } from '../../../../src/application/queries/get-online-history/get-online-history.query'; import { IOnlineSnapshotRepository, ONLINE_SNAPSHOT_REPOSITORY, } from '../../../../src/domain/repositories/online-snapshot.repository.interface'; import { OnlineSnapshot } from '../../../../src/domain/entities/online-snapshot.entity'; describe('GetOnlineHistoryHandler', () => { let handler: GetOnlineHistoryHandler; let snapshotRepository: jest.Mocked; const createSnapshot = (ts: Date, onlineCount: number) => OnlineSnapshot.reconstitute({ id: BigInt(Math.floor(Math.random() * 1000000)), ts, onlineCount, windowSeconds: 180, }); beforeEach(async () => { const mockSnapshotRepository: jest.Mocked = { insert: jest.fn(), findByTimeRange: jest.fn(), findLatest: jest.fn(), }; const module: TestingModule = await Test.createTestingModule({ providers: [ GetOnlineHistoryHandler, { provide: ONLINE_SNAPSHOT_REPOSITORY, useValue: mockSnapshotRepository, }, ], }).compile(); handler = module.get(GetOnlineHistoryHandler); snapshotRepository = module.get(ONLINE_SNAPSHOT_REPOSITORY); }); afterEach(() => { jest.clearAllMocks(); }); describe('execute', () => { it('should return online history data', async () => { const startTime = new Date('2025-01-01T00:00:00.000Z'); const endTime = new Date('2025-01-01T01:00:00.000Z'); const snapshots = [ createSnapshot(new Date('2025-01-01T00:00:00.000Z'), 1000), createSnapshot(new Date('2025-01-01T00:05:00.000Z'), 1100), createSnapshot(new Date('2025-01-01T00:10:00.000Z'), 1200), ]; snapshotRepository.findByTimeRange.mockResolvedValue(snapshots); const query = new GetOnlineHistoryQuery(startTime, endTime, '5m'); const result = await handler.execute(query); expect(result.data.length).toBe(3); expect(result.interval).toBe('5m'); expect(result.startTime).toBe(startTime.toISOString()); expect(result.endTime).toBe(endTime.toISOString()); }); it('should aggregate data by interval', async () => { const startTime = new Date('2025-01-01T00:00:00.000Z'); const endTime = new Date('2025-01-01T00:10:00.000Z'); // Snapshots within same 5-minute bucket const snapshots = [ createSnapshot(new Date('2025-01-01T00:00:00.000Z'), 1000), createSnapshot(new Date('2025-01-01T00:01:00.000Z'), 1100), createSnapshot(new Date('2025-01-01T00:02:00.000Z'), 1200), createSnapshot(new Date('2025-01-01T00:05:00.000Z'), 1500), createSnapshot(new Date('2025-01-01T00:06:00.000Z'), 1600), ]; snapshotRepository.findByTimeRange.mockResolvedValue(snapshots); const query = new GetOnlineHistoryQuery(startTime, endTime, '5m'); const result = await handler.execute(query); // Should aggregate into 2 buckets: [00:00-00:05) and [00:05-00:10) expect(result.data.length).toBe(2); // First bucket average: (1000 + 1100 + 1200) / 3 = 1100 expect(result.data[0].onlineCount).toBe(1100); // Second bucket average: (1500 + 1600) / 2 = 1550 expect(result.data[1].onlineCount).toBe(1550); }); it('should calculate summary statistics', async () => { const startTime = new Date('2025-01-01T00:00:00.000Z'); const endTime = new Date('2025-01-01T01:00:00.000Z'); const snapshots = [ createSnapshot(new Date('2025-01-01T00:00:00.000Z'), 500), createSnapshot(new Date('2025-01-01T00:05:00.000Z'), 1000), createSnapshot(new Date('2025-01-01T00:10:00.000Z'), 2000), createSnapshot(new Date('2025-01-01T00:15:00.000Z'), 1500), ]; snapshotRepository.findByTimeRange.mockResolvedValue(snapshots); const query = new GetOnlineHistoryQuery(startTime, endTime, '5m'); const result = await handler.execute(query); expect(result.summary.maxOnline).toBe(2000); expect(result.summary.minOnline).toBe(500); expect(result.summary.avgOnline).toBe(1250); // (500+1000+2000+1500)/4 expect(result.summary.maxTimestamp).toBe('2025-01-01T00:10:00.000Z'); expect(result.summary.minTimestamp).toBe('2025-01-01T00:00:00.000Z'); }); it('should handle empty data', async () => { snapshotRepository.findByTimeRange.mockResolvedValue([]); const query = new GetOnlineHistoryQuery( new Date('2025-01-01T00:00:00.000Z'), new Date('2025-01-01T01:00:00.000Z'), '5m', ); const result = await handler.execute(query); expect(result.data.length).toBe(0); expect(result.total).toBe(0); expect(result.summary.maxOnline).toBe(0); expect(result.summary.minOnline).toBe(0); expect(result.summary.avgOnline).toBe(0); expect(result.summary.maxTimestamp).toBeNull(); expect(result.summary.minTimestamp).toBeNull(); }); it('should use default 5m interval', async () => { snapshotRepository.findByTimeRange.mockResolvedValue([]); const query = new GetOnlineHistoryQuery( new Date('2025-01-01T00:00:00.000Z'), new Date('2025-01-01T01:00:00.000Z'), ); const result = await handler.execute(query); expect(result.interval).toBe('5m'); }); it('should support 1m interval', async () => { const snapshots = [ createSnapshot(new Date('2025-01-01T00:00:00.000Z'), 1000), createSnapshot(new Date('2025-01-01T00:00:30.000Z'), 1100), createSnapshot(new Date('2025-01-01T00:01:00.000Z'), 1200), ]; snapshotRepository.findByTimeRange.mockResolvedValue(snapshots); const query = new GetOnlineHistoryQuery( new Date('2025-01-01T00:00:00.000Z'), new Date('2025-01-01T00:02:00.000Z'), '1m', ); const result = await handler.execute(query); expect(result.interval).toBe('1m'); // Should aggregate into 2 buckets expect(result.data.length).toBe(2); }); it('should support 1h interval', async () => { const snapshots = [ createSnapshot(new Date('2025-01-01T00:00:00.000Z'), 1000), createSnapshot(new Date('2025-01-01T00:30:00.000Z'), 1200), createSnapshot(new Date('2025-01-01T01:00:00.000Z'), 1500), ]; snapshotRepository.findByTimeRange.mockResolvedValue(snapshots); const query = new GetOnlineHistoryQuery( new Date('2025-01-01T00:00:00.000Z'), new Date('2025-01-01T02:00:00.000Z'), '1h', ); const result = await handler.execute(query); expect(result.interval).toBe('1h'); // Should aggregate into 2 buckets: [00:00-01:00) and [01:00-02:00) expect(result.data.length).toBe(2); }); it('should include total count in result', async () => { const snapshots = [ createSnapshot(new Date('2025-01-01T00:00:00.000Z'), 1000), createSnapshot(new Date('2025-01-01T00:05:00.000Z'), 1100), createSnapshot(new Date('2025-01-01T00:10:00.000Z'), 1200), ]; snapshotRepository.findByTimeRange.mockResolvedValue(snapshots); const query = new GetOnlineHistoryQuery( new Date('2025-01-01T00:00:00.000Z'), new Date('2025-01-01T00:15:00.000Z'), '5m', ); const result = await handler.execute(query); expect(result.total).toBe(result.data.length); }); it('should throw error when repository fails', async () => { snapshotRepository.findByTimeRange.mockRejectedValue( new Error('Database connection failed'), ); const query = new GetOnlineHistoryQuery( new Date('2025-01-01T00:00:00.000Z'), new Date('2025-01-01T01:00:00.000Z'), '5m', ); await expect(handler.execute(query)).rejects.toThrow('Database connection failed'); }); }); });