101 lines
3.5 KiB
TypeScript
101 lines
3.5 KiB
TypeScript
import { OperationsService } from './operations.service';
|
|
|
|
function createPrismaMock() {
|
|
return {
|
|
smsBatchTask: {
|
|
findMany: jest.fn(),
|
|
count: jest.fn().mockResolvedValue(3),
|
|
},
|
|
smsMessageRecord: {
|
|
findMany: jest.fn().mockResolvedValue([{ messageId: 'MSG-1' }]),
|
|
groupBy: jest.fn().mockResolvedValue([{ status: 'delivered', _count: { _all: 2 }, _sum: { amountCents: 20, billingUnits: 2 } }]),
|
|
aggregate: jest.fn().mockResolvedValue({ _count: { _all: 2 }, _sum: { amountCents: 20, billingUnits: 2 } }),
|
|
},
|
|
smsReceiptRecord: {
|
|
findMany: jest.fn().mockResolvedValue([]),
|
|
},
|
|
smsUplinkMessage: {
|
|
findMany: jest.fn().mockResolvedValue([{ id: 'uplink-1', messageId: 'MSG-1' }]),
|
|
count: jest.fn().mockResolvedValue(1),
|
|
},
|
|
smsBillingRecord: {
|
|
findMany: jest.fn().mockResolvedValue([{ id: 'bill-1', messageId: 'MSG-1' }]),
|
|
aggregate: jest.fn().mockResolvedValue({ _count: { _all: 2 }, _sum: { amountCents: 20, billingUnits: 2 } }),
|
|
},
|
|
accountTransaction: {
|
|
aggregate: jest.fn().mockResolvedValue({ _count: { _all: 2 }, _sum: { amountCents: -20, smsUnits: -2 } }),
|
|
},
|
|
operationLog: {
|
|
findMany: jest.fn(),
|
|
groupBy: jest.fn(),
|
|
},
|
|
};
|
|
}
|
|
|
|
describe('OperationsService', () => {
|
|
it('filters send-chain messages by tenant, application, channel, task, phone, and status', async () => {
|
|
const prisma = createPrismaMock();
|
|
const service = new OperationsService(prisma as never);
|
|
|
|
await service.listMessages({
|
|
tenantId: 'tenant-1',
|
|
applicationId: 'app-1',
|
|
channelId: 'channel-1',
|
|
taskId: 'task-1',
|
|
phoneNumber: '13800000001',
|
|
status: 'delivered',
|
|
});
|
|
|
|
expect(prisma.smsMessageRecord.findMany).toHaveBeenCalledWith({
|
|
where: {
|
|
tenantId: 'tenant-1',
|
|
applicationId: 'app-1',
|
|
channelId: 'channel-1',
|
|
batchTaskId: 'task-1',
|
|
phoneNumber: '13800000001',
|
|
status: 'delivered',
|
|
},
|
|
include: { submitRecords: true, receiptRecords: true },
|
|
orderBy: { queuedAt: 'desc' },
|
|
take: 500,
|
|
});
|
|
});
|
|
|
|
it('builds dashboard and statistics aggregates', async () => {
|
|
const prisma = createPrismaMock();
|
|
const service = new OperationsService(prisma as never);
|
|
|
|
await expect(service.dashboard({ tenantId: 'tenant-1' })).resolves.toEqual(
|
|
expect.objectContaining({ taskCount: 3, uplinkCount: 1 }),
|
|
);
|
|
await service.statistics({ tenantId: 'tenant-1', groupBy: 'application' });
|
|
|
|
expect(prisma.smsMessageRecord.groupBy).toHaveBeenCalledWith({
|
|
by: ['applicationId'],
|
|
where: expect.objectContaining({ tenantId: 'tenant-1' }),
|
|
_count: { _all: true },
|
|
_sum: { amountCents: true, billingUnits: true },
|
|
});
|
|
});
|
|
|
|
it('returns trace details and reconciliation diffs', async () => {
|
|
const prisma = createPrismaMock();
|
|
const service = new OperationsService(prisma as never);
|
|
|
|
await expect(service.trace({ tenantId: 'tenant-1', taskId: 'task-1', messageId: 'MSG-1' })).resolves.toEqual({
|
|
messages: [{ messageId: 'MSG-1' }],
|
|
billingRecords: [{ id: 'bill-1', messageId: 'MSG-1' }],
|
|
uplinks: [{ id: 'uplink-1', messageId: 'MSG-1' }],
|
|
});
|
|
await expect(service.reconciliation({ tenantId: 'tenant-1', taskId: 'task-1' })).resolves.toEqual(
|
|
expect.objectContaining({
|
|
diff: {
|
|
messageVsBillingAmountCents: 0,
|
|
billingVsTransactionAmountCents: 0,
|
|
messageVsBillingUnits: 0,
|
|
},
|
|
}),
|
|
);
|
|
});
|
|
});
|