91 lines
3.8 KiB
JavaScript
91 lines
3.8 KiB
JavaScript
import { readdirSync, readFileSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
|
|
const examplesDir = join(process.cwd(), 'docs', 'contracts', 'examples');
|
|
|
|
const envelopeFields = ['schemaVersion', 'messageType', 'traceId', 'messageId', 'channelId', 'createdAt'];
|
|
const requiredByType = {
|
|
SubmitCommand: [
|
|
...envelopeFields,
|
|
'tenantId',
|
|
'applicationId',
|
|
'submitId',
|
|
'phoneNumber',
|
|
'content',
|
|
'signature',
|
|
'templateId',
|
|
'billingUnits',
|
|
'route',
|
|
'cmpp',
|
|
'retry',
|
|
],
|
|
SubmitResult: [...envelopeFields, 'sequenceId', 'gatewayMessageId', 'submitStatus', 'submittedAt'],
|
|
ReceiptEvent: [...envelopeFields, 'sequenceId', 'gatewayMessageId', 'receiptStatus', 'rawStatus', 'deliveredAt'],
|
|
UplinkEvent: [...envelopeFields, 'sequenceId', 'phoneNumber', 'destId', 'content', 'receivedAt'],
|
|
};
|
|
|
|
const validTypes = new Set(Object.keys(requiredByType));
|
|
const chinaMobileNumber = /^1[3-9][0-9]{9}$/;
|
|
|
|
function assert(condition, message) {
|
|
if (!condition) {
|
|
throw new Error(message);
|
|
}
|
|
}
|
|
|
|
function assertIsoDateTime(value, field, file) {
|
|
assert(typeof value === 'string' && !Number.isNaN(Date.parse(value)), `${file}: ${field} must be ISO date-time`);
|
|
}
|
|
|
|
function validateExample(fileName) {
|
|
const fullPath = join(examplesDir, fileName);
|
|
const payload = JSON.parse(readFileSync(fullPath, 'utf8'));
|
|
if (fileName === 'submit-result-outbox-event.json') {
|
|
for (const field of ['schemaVersion', 'eventId', 'eventType', 'path', 'messageId', 'channelId', 'submitId', 'payload', 'createdAt']) {
|
|
assert(Object.hasOwn(payload, field), `${fileName}: missing ${field}`);
|
|
}
|
|
assert(payload.schemaVersion === 'v1', `${fileName}: schemaVersion must be v1`);
|
|
assert(['submit_result', 'submit_segment_result'].includes(payload.eventType), `${fileName}: unsupported eventType`);
|
|
assert(['/gateway/events/submit-result', '/gateway/events/submit-segment-result'].includes(payload.path), `${fileName}: unsupported path`);
|
|
assert(payload.payload?.eventId === payload.eventId, `${fileName}: payload.eventId must match eventId`);
|
|
assertIsoDateTime(payload.createdAt, 'createdAt', fileName);
|
|
return `${fileName}: ${payload.eventType} ok`;
|
|
}
|
|
const type = payload.messageType;
|
|
|
|
assert(validTypes.has(type), `${fileName}: unsupported messageType ${type}`);
|
|
assert(payload.schemaVersion === 'v1', `${fileName}: schemaVersion must be v1`);
|
|
|
|
for (const field of requiredByType[type]) {
|
|
assert(Object.hasOwn(payload, field), `${fileName}: missing ${field}`);
|
|
}
|
|
|
|
for (const dateField of ['createdAt', 'submittedAt', 'deliveredAt', 'receivedAt']) {
|
|
if (payload[dateField]) {
|
|
assertIsoDateTime(payload[dateField], dateField, fileName);
|
|
}
|
|
}
|
|
|
|
if (payload.phoneNumber) {
|
|
assert(chinaMobileNumber.test(payload.phoneNumber), `${fileName}: phoneNumber must be a mainland China mobile number`);
|
|
}
|
|
|
|
if (type === 'SubmitCommand') {
|
|
assert(payload.billingUnits >= 1, `${fileName}: billingUnits must be >= 1`);
|
|
assert(payload.route?.channelCode, `${fileName}: route.channelCode is required`);
|
|
assert(payload.route?.cmppAccountCode, `${fileName}: route.cmppAccountCode is required`);
|
|
assert([0, 1].includes(payload.cmpp?.registeredDelivery), `${fileName}: cmpp.registeredDelivery must be 0 or 1`);
|
|
assert([8, 15].includes(payload.cmpp?.msgFmt), `${fileName}: cmpp.msgFmt must be 8 or 15`);
|
|
assert(payload.retry?.maxAttempts >= 1, `${fileName}: retry.maxAttempts must be >= 1`);
|
|
}
|
|
|
|
return `${fileName}: ${type} ok`;
|
|
}
|
|
|
|
const exampleFiles = readdirSync(examplesDir).filter((file) => file.endsWith('.json')).sort();
|
|
assert(exampleFiles.length > 0, 'No queue contract examples found');
|
|
|
|
const results = exampleFiles.map(validateExample);
|
|
console.log(results.join('\n'));
|
|
console.log(`Validated ${results.length} gateway queue contract examples.`);
|