release: prepare RealeseV2.3
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user