Files
lislgosms/api/src/send-chain/send-downstream-requeue-task.service.spec.ts
T

69 lines
4.3 KiB
TypeScript

import { BadRequestException } from '@nestjs/common';
import { SendDownstreamRequeueTaskService } from './send-downstream-requeue-task.service';
function prismaMock(): Record<string, any> {
const result: Record<string, any> = {
cmppDownstreamDelivery: {
count: jest.fn(), groupBy: jest.fn(), findFirst: jest.fn(), findMany: jest.fn(), findUnique: jest.fn(),
},
downstreamRequeueTask: {
findFirst: jest.fn(), findMany: jest.fn(), count: jest.fn(), findUnique: jest.fn(), create: jest.fn(), update: jest.fn(),
},
downstreamRequeueTaskItem: {
createMany: jest.fn(), groupBy: jest.fn(), findMany: jest.fn(), findFirst: jest.fn(), updateMany: jest.fn(), update: jest.fn(),
},
operationLog: { create: jest.fn() },
};
result.$transaction = jest.fn(async (callback: (tx: unknown) => unknown) => callback(result));
return result;
}
let mock: Record<string, any>;
describe('SendDownstreamRequeueTaskService', () => {
beforeEach(() => { mock = prismaMock(); });
it('previews all matches separately from replayable records', async () => {
mock.cmppDownstreamDelivery.count.mockResolvedValueOnce(12).mockResolvedValueOnce(8);
mock.cmppDownstreamDelivery.groupBy
.mockResolvedValueOnce([{ status: 'pending', _count: { _all: 8 } }, { status: 'delivered', _count: { _all: 4 } }])
.mockResolvedValueOnce([{ applicationId: 'app-1', _count: { _all: 12 } }]);
mock.cmppDownstreamDelivery.findFirst.mockResolvedValue({ createdAt: new Date('2026-08-11T00:00:00Z') });
const service = new SendDownstreamRequeueTaskService(mock as never, { requeueDownstreamDelivery: jest.fn() });
const result = await service.preview({ status: 'all' });
expect(result).toEqual(expect.objectContaining({ matchedCount: 12, replayableCount: 8, skippedCount: 4, applicationCount: 1 }));
});
it('rejects delivered filters and short reasons', async () => {
const service = new SendDownstreamRequeueTaskService(mock as never, { requeueDownstreamDelivery: jest.fn() });
await expect(service.create({ filter: { status: 'delivered' }, snapshotAt: new Date().toISOString(), reason: '事故恢复' })).rejects.toBeInstanceOf(BadRequestException);
await expect(service.create({ filter: { status: 'pending' }, snapshotAt: new Date().toISOString(), reason: '短' })).rejects.toBeInstanceOf(BadRequestException);
});
it('rejects a new task when the same application scope already has an unfinished task', async () => {
mock.downstreamRequeueTask.findFirst.mockResolvedValue({ taskNo: 'DRT-EXISTING' });
const service = new SendDownstreamRequeueTaskService(mock as never, { requeueDownstreamDelivery: jest.fn() });
await expect(service.create({ filter: { applicationId: 'app-1', status: 'pending' }, snapshotAt: new Date().toISOString(), reason: '处理历史回执积压' })).rejects.toThrow('DRT-EXISTING');
});
it('skips a delivery that automatic recovery already confirmed before task execution', async () => {
mock.downstreamRequeueTask.findMany.mockResolvedValue([{ id: 'task-1' }]);
mock.downstreamRequeueTask.findUnique
.mockResolvedValueOnce({ id: 'task-1', taskNo: 'DRT-1', status: 'queued', startedAt: null, ratePerSecond: 10, consecutiveFailures: 0, consecutiveFailureLimit: 10 })
.mockResolvedValueOnce({ status: 'running' })
.mockResolvedValue({ status: 'running' });
mock.downstreamRequeueTaskItem.findMany
.mockResolvedValueOnce([])
.mockResolvedValueOnce([{ id: 'item-1', deliveryId: 'delivery-1' }])
.mockResolvedValueOnce([]);
mock.downstreamRequeueTaskItem.updateMany.mockResolvedValue({ count: 1 });
mock.cmppDownstreamDelivery.findUnique.mockResolvedValue({ status: 'delivered', payload: {}, deliveryType: 'receipt', application: { status: 'active', interfaceEnabled: true } });
mock.downstreamRequeueTaskItem.groupBy.mockResolvedValue([{ status: 'skipped', _count: { _all: 1 } }]);
const requeue = jest.fn();
const service = new SendDownstreamRequeueTaskService(mock as never, { requeueDownstreamDelivery: requeue });
await service.runScan();
expect(requeue).not.toHaveBeenCalled();
expect(mock.downstreamRequeueTaskItem.update).toHaveBeenCalledWith(expect.objectContaining({ data: expect.objectContaining({ status: 'skipped', skipReason: '已被客户确认' }) }));
});
});