fix: reconcile shared-channel receipts and protocol logs
This commit is contained in:
@@ -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();
|
||||
}
|
||||
Reference in New Issue
Block a user