227 lines
8.2 KiB
TypeScript
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');
|
|
});
|
|
});
|
|
});
|