Files
lisglosips/apps/api/src/modules/dashboard/dashboard.service.spec.ts
T

95 lines
3.6 KiB
TypeScript

import { BadRequestException } from '@nestjs/common';
import { describe, expect, it } from 'vitest';
import { buildTrendBuckets, callMetrics, DashboardService, startOfShanghaiDayUtc } from './dashboard.service.js';
import type { DashboardRepository } from './dashboard.repository.js';
describe('dashboard service', () => {
it('loads summary and trends together for the overview response', async () => {
const repository = {
summaryWindow: async () => ({
cdrs: [], ratedCdrs: [], failureCodes: [], abnormalGateways: [],
activeCustomers: 0, activeCustomerGateways: 0, activeVendorGateways: 0, pendingQuality: 0
}),
trendWindow: async () => ({ cdrs: [], ratedCdrs: [] })
} as unknown as DashboardRepository;
const service = new DashboardService(repository);
const result = await service.overview({ hours: 1, bucketMinutes: 60 });
expect(result.summary).toHaveProperty('calls.totalCalls', 0);
expect(result.trends.buckets).toHaveLength(1);
});
it('uses Asia/Shanghai day boundary for today summary', async () => {
const calls: Array<{ start: Date; end: Date }> = [];
const repository = {
summaryWindow: async (start: Date, end: Date) => {
calls.push({ start, end });
return {
cdrs: [],
ratedCdrs: [],
failureCodes: [],
abnormalGateways: [],
activeCustomers: 0,
activeCustomerGateways: 0,
activeVendorGateways: 0,
pendingQuality: 0
};
}
} as unknown as DashboardRepository;
const service = new DashboardService(repository);
const now = new Date('2026-06-21T15:30:00.000Z');
await service.summary(now);
expect(calls[0]?.start.toISOString()).toBe('2026-06-20T16:00:00.000Z');
expect(startOfShanghaiDayUtc(now).toISOString()).toBe('2026-06-20T16:00:00.000Z');
});
it('computes call metrics and answer rate without floating point money exposure', () => {
expect(
callMetrics([
{ startedAt: new Date('2026-06-21T00:00:00.000Z'), durationSec: 10, sipCode: 200 },
{ startedAt: new Date('2026-06-21T00:01:00.000Z'), durationSec: 0, sipCode: 486 },
{ startedAt: new Date('2026-06-21T00:02:00.000Z'), durationSec: 0, sipCode: 503 }
])
).toEqual({
totalCalls: 3,
answeredCalls: 1,
failedCalls: 2,
answerRate: '0.3333',
totalDurationSec: 10
});
});
it('builds fixed trend buckets', () => {
const buckets = buildTrendBuckets(
new Date('2026-06-21T00:00:00.000Z'),
new Date('2026-06-21T01:00:00.000Z'),
30,
[
{ startedAt: new Date('2026-06-21T00:10:00.000Z'), durationSec: 30, sipCode: 200 },
{ startedAt: new Date('2026-06-21T00:45:00.000Z'), durationSec: 0, sipCode: 480 }
],
[
{ startedAt: new Date('2026-06-21T00:10:00.000Z'), customerFee: '0.100000', vendorCost: '0.030000', grossProfit: '0.070000' }
]
);
expect(buckets).toHaveLength(2);
expect(buckets[0]).toMatchObject({
calls: { totalCalls: 1, answeredCalls: 1, failedCalls: 0 },
money: { customerFee: '0.100000', vendorCost: '0.030000', grossProfit: '0.070000' }
});
expect(buckets[1]).toMatchObject({
calls: { totalCalls: 1, answeredCalls: 0, failedCalls: 1 }
});
});
it('validates trend query bounds', async () => {
const service = new DashboardService({} as DashboardRepository);
await expect(service.trends({ hours: '0' })).rejects.toThrow(BadRequestException);
await expect(service.trends({ bucketMinutes: '10' })).rejects.toThrow(BadRequestException);
});
});