rwadurian/backend/services/presence-service/test/integration/application/queries/get-online-history.handler....

227 lines
8.2 KiB
TypeScript

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<IOnlineSnapshotRepository>;
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<IOnlineSnapshotRepository> = {
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>(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');
});
});
});