release: prepare RealeseV2.3

This commit is contained in:
hectorzhao
2026-08-06 10:48:36 +08:00
parent 57b58f1c40
commit 8ad8e61793
37 changed files with 997 additions and 64 deletions
@@ -198,6 +198,9 @@ function createPrismaMock() {
findFirst: jest.fn().mockResolvedValue(null),
findMany: jest.fn(),
},
smsReceiptAnomaly: {
upsert: jest.fn().mockResolvedValue({ id: 'receipt-anomaly-1', status: 'pending' }),
},
smsUplinkMessage: {
create: jest.fn().mockImplementation(({ data }) => Promise.resolve({ id: 'uplink-1', ...data })),
findMany: jest.fn(),
@@ -2654,6 +2657,125 @@ describe('SendChainService', () => {
});
});
it('treats one delivered receipt as the whole long-message success only for a message-level receipt channel', async () => {
const { service, prisma } = createService();
prisma.smsChannel.findUnique.mockResolvedValue({
id: 'channel-1',
config: { longMessageReceiptMode: 'message_level' },
});
prisma.smsMessageRecord.findUnique.mockResolvedValue({
id: 'record-message-level',
tenantId: 'tenant-1',
batchTaskId: 'task-1',
applicationId: 'app-1',
messageId: 'MSG-MESSAGE-LEVEL',
submitId: 'SUB-MESSAGE-LEVEL',
phoneNumber: '13127620092',
channelId: 'channel-1',
gatewayMessageId: 'GW-MESSAGE-LEVEL-1',
status: 'submitted',
billingUnits: 2,
});
prisma.smsSubmitRecord.findFirst.mockResolvedValue({
id: 'submit-message-level',
submitId: 'SUB-MESSAGE-LEVEL',
channelId: 'channel-1',
gatewayMessageId: 'GW-MESSAGE-LEVEL-1',
});
prisma.smsMessageSegmentAudit.findMany
.mockResolvedValueOnce([
{ id: 'segment-1', receiptStatus: 'delivered' },
{ id: 'segment-2', receiptStatus: null },
])
.mockResolvedValueOnce([
{ segmentIndex: 1, segmentTotal: 2, receiptStatus: 'delivered', rawStatus: 'DELIVRD', deliveredAt: new Date() },
{ segmentIndex: 2, segmentTotal: 2, receiptStatus: 'delivered', rawStatus: 'DELIVRD', compensationType: 'supplier_message_level_receipt', deliveredAt: new Date() },
]);
await service.handleReceipt({
messageId: 'MSG-MESSAGE-LEVEL',
channelId: 'channel-1',
gatewayMessageId: 'GW-MESSAGE-LEVEL-1',
phoneNumber: '13127620092',
receiptStatus: 'delivered',
rawStatus: 'DELIVRD',
});
expect(prisma.smsMessageSegmentAudit.updateMany).toHaveBeenCalledWith(expect.objectContaining({
where: expect.objectContaining({
messageRecordId: 'record-message-level',
submitRecordId: 'submit-message-level',
receiptStatus: null,
}),
data: expect.objectContaining({
receiptStatus: 'delivered',
compensationType: 'supplier_message_level_receipt',
}),
}));
expect(prisma.smsMessageRecord.update).toHaveBeenCalledWith(expect.objectContaining({
where: { id: 'record-message-level' },
data: expect.objectContaining({ status: 'delivered', receiptStatus: 'delivered' }),
}));
});
it('records a receipt anomaly when a message-level success is followed by a failure for the same attempt', async () => {
const { service, prisma, billing } = createService();
prisma.smsChannel.findUnique.mockResolvedValue({
id: 'channel-1',
config: { longMessageReceiptMode: 'message_level' },
});
prisma.smsMessageRecord.findUnique.mockResolvedValue({
id: 'record-conflict',
tenantId: 'tenant-1',
batchTaskId: 'task-1',
applicationId: 'app-1',
messageId: 'MSG-CONFLICT',
submitId: 'SUB-CONFLICT',
phoneNumber: '13127620092',
channelId: 'channel-1',
gatewayMessageId: 'GW-CONFLICT-1',
status: 'delivered',
billingUnits: 2,
});
prisma.smsMessageSegmentAudit.findFirst.mockResolvedValue({
id: 'segment-conflict-2',
messageRecordId: 'record-conflict',
submitRecordId: 'submit-conflict',
submitId: 'SUB-CONFLICT',
channelId: 'channel-1',
gatewayMessageId: 'GW-CONFLICT-2',
segmentIndex: 2,
segmentTotal: 2,
});
prisma.smsMessageSegmentAudit.findMany.mockResolvedValue([
{ segmentIndex: 1, segmentTotal: 2, receiptStatus: 'delivered', rawStatus: 'DELIVRD', deliveredAt: new Date() },
{ segmentIndex: 2, segmentTotal: 2, receiptStatus: 'undelivered', rawStatus: 'UNDELIV', deliveredAt: new Date() },
]);
await service.handleReceipt({
messageId: 'MSG-CONFLICT',
channelId: 'channel-1',
gatewayMessageId: 'GW-CONFLICT-2',
phoneNumber: '13127620092',
receiptStatus: 'undelivered',
rawStatus: 'UNDELIV',
errorCode: 'SP_CONFLICT',
});
expect(prisma.smsReceiptAnomaly.upsert).toHaveBeenCalledWith(expect.objectContaining({
where: { anomalyKey: 'aggregate-receipt-conflict:record-conflict:SUB-CONFLICT' },
create: expect.objectContaining({
anomalyType: 'aggregate_success_then_failure',
previousStatus: 'delivered',
incomingStatus: 'undelivered',
}),
}));
expect(prisma.smsMessageRecord.update).not.toHaveBeenCalledWith(expect.objectContaining({
data: expect.objectContaining({ status: 'failed' }),
}));
expect(billing.refund).not.toHaveBeenCalled();
});
it('creates and sends only one downstream receipt for the same fragment dedupe key', async () => {
const { service, prisma } = createService();
let claimedDelivery: Record<string, unknown> | null = null;