import { ReportsService } from './reports.service'; describe('ReportsService', () => { const tx = { dailyReconciliationReport: { deleteMany: jest.fn() }, dailyProfitReport: { deleteMany: jest.fn() }, dailyQualityReport: { deleteMany: jest.fn() }, $executeRaw: jest.fn(), }; const prisma = { dailyReconciliationReport: { findMany: jest.fn(), count: jest.fn() }, dailyProfitReport: { findMany: jest.fn(), count: jest.fn() }, dailyQualityReport: { findMany: jest.fn(), count: jest.fn() }, $transaction: jest.fn((callback: (client: typeof tx) => unknown) => callback(tx)), }; let service: ReportsService; beforeEach(() => { jest.clearAllMocks(); tx.dailyReconciliationReport.deleteMany.mockResolvedValue({ count: 0 }); tx.dailyProfitReport.deleteMany.mockResolvedValue({ count: 0 }); tx.dailyQualityReport.deleteMany.mockResolvedValue({ count: 0 }); tx.$executeRaw.mockResolvedValue(0); prisma.dailyReconciliationReport.findMany.mockResolvedValue([{ id: 'recon-1' }]); prisma.dailyReconciliationReport.count.mockResolvedValue(1); prisma.dailyProfitReport.findMany.mockResolvedValue([{ id: 'profit-1' }]); prisma.dailyProfitReport.count.mockResolvedValue(1); prisma.dailyQualityReport.findMany.mockResolvedValue([{ id: 'quality-1' }]); prisma.dailyQualityReport.count.mockResolvedValue(1); service = new ReportsService(prisma as never); }); it('rebuilds exactly T-4 through T-1 in independent transactions', async () => { await expect(service.refreshRollingWindow(new Date('2026-07-15T05:30:00.000Z'))).resolves.toEqual({ refreshedDates: ['2026-07-11', '2026-07-12', '2026-07-13', '2026-07-14'], }); expect(prisma.$transaction).toHaveBeenCalledTimes(4); expect(tx.dailyReconciliationReport.deleteMany).toHaveBeenCalledTimes(4); expect(tx.dailyProfitReport.deleteMany).toHaveBeenCalledTimes(4); expect(tx.dailyQualityReport.deleteMany).toHaveBeenCalledTimes(4); expect(tx.$executeRaw).toHaveBeenCalledTimes(28); }); it('queries reconciliation reports with server-side filters and bounded pagination', async () => { await expect(service.listReconciliation({ dateFrom: '2026-07-01', dateTo: '2026-07-14', tenantId: 'tenant-1', applicationId: 'app-1', page: 2, pageSize: 500, })).resolves.toEqual({ items: [{ id: 'recon-1' }], total: 1, page: 2, pageSize: 100 }); expect(prisma.dailyReconciliationReport.findMany).toHaveBeenCalledWith(expect.objectContaining({ where: expect.objectContaining({ tenantId: 'tenant-1', applicationId: 'app-1' }), skip: 100, take: 100, })); }); it('keeps application and channel profit filters separate', async () => { await service.listProfit({ dimensionType: 'channel', tenantId: 'tenant-1', applicationId: 'app-1', channelId: 'channel-1' }); expect(prisma.dailyProfitReport.findMany).toHaveBeenCalledWith(expect.objectContaining({ where: expect.objectContaining({ dimensionType: 'channel', tenantId: undefined, applicationId: undefined, channelId: 'channel-1', }), })); }); it('sorts quality reports by send volume and keeps the selected dimension', async () => { await expect(service.listQuality({ dimensionType: 'drainage', tenantId: 'tenant-1', page: 1, pageSize: 20 })).resolves.toEqual({ items: [{ id: 'quality-1' }], total: 1, page: 1, pageSize: 20, dimensionType: 'drainage', }); expect(prisma.dailyQualityReport.findMany).toHaveBeenCalledWith(expect.objectContaining({ where: expect.objectContaining({ dimensionType: 'drainage', tenantId: 'tenant-1' }), orderBy: [{ sentUnits: 'desc' }, { reportDate: 'desc' }, { dimensionName: 'asc' }], })); }); it('exports complete filtered report data as escaped CSV instead of the current page', async () => { prisma.dailyReconciliationReport.findMany.mockResolvedValueOnce([{ id: 'recon-export', reportDate: new Date('2026-07-14'), tenantName: '示例,企业', applicationName: '应用A', submittedUnits: 14, sentUnits: 12, unknownUnits: 1, successUnits: 10, failedUnits: 1, generatedAt: new Date('2026-07-15T00:00:00Z'), }]); const exported = await service.exportReconciliation({ tenantId: 'tenant-1', dateFrom: '2026-07-01', dateTo: '2026-07-14' }); expect(exported.fileName).toContain('对账单-'); expect(exported.content).toContain('"示例,企业"'); expect(exported.content).toContain('提交条数'); expect(prisma.dailyReconciliationReport.findMany).toHaveBeenCalledWith(expect.objectContaining({ where: expect.objectContaining({ tenantId: 'tenant-1' }), orderBy: [{ reportDate: 'desc' }, { tenantName: 'asc' }, { applicationName: 'asc' }], })); }); });