fix: harden channel retry attribution and operations UI
This commit is contained in:
@@ -1863,7 +1863,7 @@ describe('SendChainService', () => {
|
||||
});
|
||||
|
||||
expect(prisma.smsSubmitRecord.updateMany).toHaveBeenCalledWith({
|
||||
where: { submitId: 'SUB-1' },
|
||||
where: { id: 'submit-1' },
|
||||
data: expect.objectContaining({ sequenceId: 7, gatewayMessageId: 'GW-1', submitStatus: 'accepted' }),
|
||||
});
|
||||
expect(prisma.smsMessageRecord.updateMany).toHaveBeenCalledWith({
|
||||
@@ -1937,6 +1937,69 @@ describe('SendChainService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects a legacy aggregate SubmitResult when it cannot match one submit attempt uniquely', async () => {
|
||||
const { service, prisma } = createService();
|
||||
prisma.smsSubmitRecord.findMany.mockResolvedValue([
|
||||
{ id: 'submit-1', submitId: 'SUB-1', channelId: 'channel-1', gatewayMessageId: null },
|
||||
{ id: 'submit-2', submitId: 'SUB-2', channelId: 'channel-1', gatewayMessageId: null },
|
||||
]);
|
||||
|
||||
await expect(service.handleSubmitResult({
|
||||
messageId: 'MSG-1',
|
||||
channelId: 'channel-1',
|
||||
gatewayMessageId: 'GW-LEGACY',
|
||||
submitStatus: 'accepted',
|
||||
})).rejects.toThrow('cannot be matched uniquely');
|
||||
|
||||
expect(prisma.smsSubmitRecord.updateMany).not.toHaveBeenCalled();
|
||||
expect(prisma.smsMessageRecord.updateMany).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('retries a direct-signature CMPP message through the next approved channel', async () => {
|
||||
const { service, prisma } = createService();
|
||||
const route = await prisma.channelRouteRule.findFirst();
|
||||
const primary = route.group.items[0].channel;
|
||||
const backup = { ...primary, id: 'channel-backup', code: 'CMPP-B' };
|
||||
prisma.channelRouteRule.findFirst.mockResolvedValue({
|
||||
...route,
|
||||
group: {
|
||||
...route.group,
|
||||
items: [
|
||||
{ ...route.group.items[0], channelId: primary.id, priority: 1, channel: primary },
|
||||
{ ...route.group.items[0], id: 'item-2', channelId: backup.id, priority: 2, channel: backup },
|
||||
],
|
||||
},
|
||||
});
|
||||
prisma.smsSubmitRecord.findMany.mockResolvedValue([
|
||||
{ id: 'submit-1', submitId: 'SUB-1', channelId: primary.id, createdAt: new Date() },
|
||||
]);
|
||||
const submitMessageToGateway = jest.spyOn(service as any, 'submitMessageToGateway')
|
||||
.mockResolvedValue({ submitted: true, messageRecordId: 'record-1', channelId: backup.id, attempt: 1 });
|
||||
|
||||
await expect((service as any).retryMessageIfAllowed({
|
||||
id: 'record-1',
|
||||
tenantId: 'tenant-1',
|
||||
batchTaskId: 'task-1',
|
||||
applicationId: 'app-1',
|
||||
templateId: null,
|
||||
signatureId: 'sig-direct',
|
||||
messageId: 'MSG-DIRECT-SIGNATURE',
|
||||
phoneNumber: '13800000001',
|
||||
content: '【签名】无模板内容',
|
||||
billingUnits: 1,
|
||||
queuedAt: new Date(),
|
||||
}, '回执失败补发')).resolves.toEqual(expect.objectContaining({ channelId: backup.id }));
|
||||
|
||||
expect(prisma.channelSignatureReportTask.findMany).toHaveBeenCalledWith(expect.objectContaining({
|
||||
where: expect.objectContaining({ signatureId: 'sig-direct' }),
|
||||
}));
|
||||
expect(submitMessageToGateway).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ signatureId: 'sig-direct' }),
|
||||
expect.objectContaining({ channel: expect.objectContaining({ id: backup.id }) }),
|
||||
1,
|
||||
);
|
||||
});
|
||||
|
||||
it('releases reservation for rejected submit result and refunds failed receipts', async () => {
|
||||
const { service, prisma, billing } = createService();
|
||||
prisma.smsBillingRecord.findFirst
|
||||
@@ -1955,6 +2018,7 @@ describe('SendChainService', () => {
|
||||
await service.handleSubmitResult({
|
||||
messageId: 'MSG-1',
|
||||
channelId: 'channel-1',
|
||||
submitId: 'SUB-1',
|
||||
gatewayMessageId: 'GW-1',
|
||||
submitStatus: 'rejected',
|
||||
});
|
||||
@@ -2858,11 +2922,32 @@ describe('SendChainService', () => {
|
||||
}),
|
||||
}));
|
||||
expect(prisma.smsSubmitRecord.updateMany).toHaveBeenCalledWith(expect.objectContaining({
|
||||
where: { submitId: 'SUB-1', gatewayMessageId: null },
|
||||
where: { id: 'submit-1', gatewayMessageId: null },
|
||||
data: expect.objectContaining({ sequenceId: 71, gatewayMessageId: 'GW-SEG-1' }),
|
||||
}));
|
||||
});
|
||||
|
||||
it('rejects a legacy segment result when multiple channel attempts could match', async () => {
|
||||
const { service, prisma } = createService();
|
||||
prisma.smsSubmitRecord.findMany.mockResolvedValue([
|
||||
{ id: 'submit-2', submitId: 'SUB-2', messageRecordId: 'record-1', channelId: 'channel-1' },
|
||||
{ id: 'submit-1', submitId: 'SUB-1', messageRecordId: 'record-1', channelId: 'channel-1' },
|
||||
]);
|
||||
|
||||
await expect(service.handleSubmitSegmentResult({
|
||||
messageId: 'MSG-1',
|
||||
channelId: 'channel-1',
|
||||
segmentTotal: 2,
|
||||
segmentIndex: 1,
|
||||
sequenceId: 72,
|
||||
gatewayMessageId: 'GW-SEG-2',
|
||||
submitStatus: 'accepted',
|
||||
})).rejects.toThrow('cannot be matched uniquely');
|
||||
|
||||
expect(prisma.smsMessageSegmentAudit.upsert).not.toHaveBeenCalled();
|
||||
expect(prisma.smsSubmitRecord.updateMany).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('durably intakes an upstream receipt before asynchronous business matching', async () => {
|
||||
const { service, prisma } = createService();
|
||||
jest.spyOn(service as any, 'processUpstreamReceiptInboxRecord').mockResolvedValue(false);
|
||||
|
||||
Reference in New Issue
Block a user