fix: reconcile shared-channel receipts and protocol logs

This commit is contained in:
hectorzhao
2026-07-24 12:52:06 +08:00
parent 3997349c21
commit ca12f14b00
14 changed files with 1110 additions and 43 deletions
@@ -0,0 +1,169 @@
import { PrismaPg } from '../../api/node_modules/@prisma/adapter-pg/dist/index.js';
import { PrismaClient } from '../../api/node_modules/@prisma/client/index.js';
const databaseUrl = process.env.DATABASE_URL
?? 'postgresql://cmpp:cmpp_password@localhost:5432/cmpp_platform?schema=public';
const apiBaseUrl = process.env.API_BASE_URL ?? 'http://127.0.0.1:3102/api';
const databaseHost = new URL(databaseUrl).hostname;
const apiHost = new URL(apiBaseUrl).hostname;
if (!['localhost', '127.0.0.1', '::1'].includes(databaseHost)
|| !['localhost', '127.0.0.1', '::1'].includes(apiHost)) {
throw new Error('This smoke test is restricted to a local API and PostgreSQL database');
}
const prisma = new PrismaClient({ adapter: new PrismaPg(databaseUrl) });
const suffix = `${Date.now()}-${process.pid}`;
const messageId = `SMOKE-RECEIPT-${suffix}`;
const submitId = `SMOKE-SUBMIT-${suffix}`;
const gatewayMessageId1 = `SMOKE-GW-1-${suffix}`;
const gatewayMessageId2 = `SMOKE-GW-2-${suffix}`;
let messageRecordId;
let originalChannelId;
let copyChannelId;
async function postReceipt(body) {
const response = await fetch(`${apiBaseUrl}/gateway/events/receipt`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
const payload = await response.json().catch(() => ({}));
if (!response.ok) {
throw new Error(`receipt API returned ${response.status}: ${JSON.stringify(payload)}`);
}
return payload;
}
try {
const supplier = {
carrier: 'all',
sendRegion: '全国',
protocol: 'CMPP',
gatewayHost: '127.0.0.1',
gatewayPort: 27890,
account: 'smoke-shared-supplier',
passwordCipher: 'smoke-only',
srcId: '10690000',
cmppVersion: '2.0',
status: 'disabled',
};
const originalChannel = await prisma.smsChannel.create({
data: { ...supplier, code: `SMOKE-ORIG-${suffix}`, name: 'Smoke original supplier connection' },
});
const copyChannel = await prisma.smsChannel.create({
data: { ...supplier, code: `SMOKE-COPY-${suffix}`, name: 'Smoke copied supplier connection' },
});
originalChannelId = originalChannel.id;
copyChannelId = copyChannel.id;
const message = await prisma.smsMessageRecord.create({
data: {
messageId,
phoneNumber: '13127620092',
content: '本地跨连接长短信回执聚合验证',
billingUnits: 2,
channelId: originalChannel.id,
submitId,
gatewayMessageId: gatewayMessageId1,
status: 'submitted',
submitStatus: 'accepted',
submittedAt: new Date(),
},
});
messageRecordId = message.id;
const submit = await prisma.smsSubmitRecord.create({
data: {
messageRecordId: message.id,
channelId: originalChannel.id,
submitId,
gatewayMessageId: gatewayMessageId1,
submitStatus: 'accepted',
submittedAt: new Date(),
},
});
await prisma.smsMessageSegmentAudit.createMany({
data: [
{
messageRecordId: message.id,
submitRecordId: submit.id,
channelId: originalChannel.id,
submitId,
segmentTotal: 2,
segmentIndex: 1,
gatewayMessageId: gatewayMessageId1,
submitStatus: 'accepted',
},
{
messageRecordId: message.id,
submitRecordId: submit.id,
channelId: originalChannel.id,
submitId,
segmentTotal: 2,
segmentIndex: 2,
gatewayMessageId: gatewayMessageId2,
submitStatus: 'accepted',
},
],
});
await postReceipt({
messageId: `receipt-${gatewayMessageId2}`,
channelId: copyChannel.id,
gatewayMessageId: gatewayMessageId2,
phoneNumber: '13127620092',
receiptStatus: 'delivered',
rawStatus: 'DELIVRD',
});
const partial = await prisma.smsMessageRecord.findUniqueOrThrow({ where: { id: message.id } });
if (partial.status !== 'submitted' || partial.receiptStatus !== null) {
throw new Error(`first segment finalized the main message unexpectedly: ${partial.status}/${partial.receiptStatus}`);
}
const crossReceipt = await prisma.smsReceiptRecord.findFirstOrThrow({
where: { messageRecordId: message.id, gatewayMessageId: gatewayMessageId2 },
});
if (crossReceipt.channelId !== originalChannel.id) {
throw new Error(`cross-connection receipt kept physical channel ${crossReceipt.channelId}`);
}
await postReceipt({
messageId,
channelId: originalChannel.id,
gatewayMessageId: gatewayMessageId1,
phoneNumber: '13127620092',
receiptStatus: 'delivered',
rawStatus: 'DELIVRD',
});
const completed = await prisma.smsMessageRecord.findUniqueOrThrow({ where: { id: message.id } });
const receipts = await prisma.smsReceiptRecord.count({ where: { messageRecordId: message.id } });
const deliveredSegments = await prisma.smsMessageSegmentAudit.count({
where: { messageRecordId: message.id, receiptStatus: 'delivered' },
});
if (completed.status !== 'delivered' || completed.receiptStatus !== 'delivered'
|| receipts !== 2 || deliveredSegments !== 2) {
throw new Error(`aggregate result mismatch: ${completed.status}/${completed.receiptStatus}, receipts=${receipts}, segments=${deliveredSegments}`);
}
console.log(JSON.stringify({
passed: true,
messageId,
physicalReceiptChannelId: copyChannel.id,
logicalReceiptChannelId: crossReceipt.channelId,
statusAfterOneOfTwo: partial.status,
finalStatus: completed.status,
receipts,
deliveredSegments,
}));
} finally {
if (messageRecordId) {
await prisma.cmppDownstreamDelivery.deleteMany({ where: { messageRecordId } });
await prisma.smsReceiptRecord.deleteMany({ where: { messageRecordId } });
await prisma.smsMessageSegmentAudit.deleteMany({ where: { messageRecordId } });
await prisma.smsSubmitRecord.deleteMany({ where: { messageRecordId } });
await prisma.smsMessageRecord.deleteMany({ where: { id: messageRecordId } });
}
await prisma.protocolInteractionLog.deleteMany({ where: { messageId } }).catch(() => undefined);
const channelIds = [originalChannelId, copyChannelId].filter(Boolean);
if (channelIds.length > 0) {
await prisma.smsChannel.deleteMany({ where: { id: { in: channelIds } } });
}
await prisma.$disconnect();
}