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, }, }), ); }); });