import assert from 'node:assert/strict'; import { createHash, randomUUID } from 'node:crypto'; import { createRequire } from 'node:module'; import { resolve } from 'node:path'; import { existsSync } from 'node:fs'; // Only a separately created QA database is accepted. No send entry, worker, // Gateway transport, balance mutation or remote business configuration is used. const require = createRequire(resolve('api/package.json')); for (const file of ['api/.env', '.env']) if (existsSync(file)) process.loadEnvFile(file); const name = process.env.CMPP_DRAINAGE_QA_DATABASE; assert.match(name ?? '', /^cmpp_qa_drainage_\d+$/); const url = new URL(process.env.DATABASE_URL); assert(['localhost', '127.0.0.1'].includes(url.hostname)); url.pathname = `/${name}`; process.env.DATABASE_URL = url.toString(); process.env.CMPP_PROCESS_ROLE = 'api'; const { PrismaService } = require('./dist/prisma/prisma.service.js'); const { evaluateMessageDrainage } = require('./dist/send-chain/drainage-authorization.js'); const { DrainageSubmitGuardController } = require('./dist/send-chain/drainage-submit-guard.controller.js'); const { ChannelReportingService } = require('./dist/channels/channel-reporting.service.js'); const { ReportsService } = require('./dist/reports/reports.service.js'); const { Module } = require('@nestjs/common'); const { NestFactory } = require('@nestjs/core'); const db = new PrismaService(); const checks = []; const prefix = `qa-drainage-${randomUUID()}`; let app; try { const application = await db.smsApplication.findFirstOrThrow({ where: { status: { not: 'deleted' } } }); const channels = await db.smsChannel.findMany({ take: 2 }); assert.equal(channels.length, 2); const signature = await db.smsSignature.create({ data: { id: prefix, tenantId: application.tenantId, applicationId: application.id, name: prefix, auditStatus: 'approved', }, }); const common = { tenantId: application.tenantId, applicationId: application.id, signatureId: signature.id }; const materials = []; for (const [index, target] of ['lisglo.cn', '02177882277'].entries()) { const material = await db.smsDrainageInfo.create({ data: { ...common, siteName: prefix, url: target, auditStatus: 'approved' }, }); materials.push(material); for (const channel of channels.slice(index)) await db.channelSignatureReportTask.create({ data: { tenantId: application.tenantId, signatureId: signature.id, channelId: channel.id, carrier: 'mobile', approvalScope: 'carrier', reportType: 'drainage', drainageItemId: material.id, status: 'approved', }, }); } for (const channel of channels) await db.channelSignatureReportTask.create({ data: { tenantId: application.tenantId, signatureId: signature.id, channelId: channel.id, carrier: 'mobile', approvalScope: 'carrier', reportType: 'signature', status: 'approved', }, }); const original = `【${prefix}】访问 https://sms.lisglo.cn/path?x=1 或联系 021-77882277`; const message = await db.smsMessageRecord.create({ data: { ...common, messageId: prefix, content: original, phoneNumber: '13800000000', carrier: 'mobile' }, }); const gate = await evaluateMessageDrainage(db, message, 'mobile', undefined, true); assert.deepEqual(gate.allowedChannelIds, [channels[1].id]); assert.equal(gate.targets.length, 2); assert.equal((await db.smsMessageRecord.findUniqueOrThrow({ where: { id: message.id } })).content, original); assert.equal(await db.smsDrainageDecision.count({ where: { messageRecordId: message.id } }), 1); checks.push( 'real rules, NFKC phone, two targets, channel intersection, original content preserved, durable decision', ); const submit = await db.smsSubmitRecord.create({ data: { messageRecordId: message.id, channelId: channels[1].id, submitId: `${prefix}-submit` }, }); class QAOnlyModule {} Module({ controllers: [DrainageSubmitGuardController], providers: [{ provide: PrismaService, useValue: db }] })( QAOnlyModule, ); app = await NestFactory.create(QAOnlyModule, { logger: false }); await app.listen(0, '127.0.0.1'); const base = await app.getUrl(); const body = { submitId: submit.submitId, channelId: channels[1].id, contentHash: createHash('sha256').update(original).digest('hex'), }; const check = async (extra = {}) => { const response = await fetch(`${base}/gateway/events/authorize-drainage`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...extra }, body: JSON.stringify(body), signal: AbortSignal.timeout(15000), }); assert.equal(response.status, 201); return response.json(); }; assert.equal((await check()).allowed, true); await db.channelSignatureReportTask.updateMany({ where: { drainageItemId: materials[1].id }, data: { status: 'rejected' }, }); assert.equal((await check()).allowed, false); checks.push('real HTTP final permission changes immediately after carrier report revocation'); await db.channelSignatureReportTask.updateMany({ where: { drainageItemId: materials[1].id }, data: { status: 'approved' }, }); await db.smsDrainageInfo.update({ where: { id: materials[0].id }, data: { auditStatus: 'pending' } }); assert.equal((await check()).allowed, false); await db.smsDrainageInfo.update({ where: { id: materials[0].id }, data: { auditStatus: 'approved' } }); const denied = await fetch(`${base}/gateway/events/authorize-drainage`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Forwarded-For': '198.51.100.1' }, body: JSON.stringify(body), }); assert.equal(denied.status, 403); checks.push('platform audit required; proxy-origin requests forbidden'); let acquired; let release; const locked = new Promise((resolve) => { acquired = resolve; }); const unlock = new Promise((resolve) => { release = resolve; }); const writer = db.$transaction( async (tx) => { await tx.smsDrainageInfo.update({ where: { id: materials[0].id }, data: { auditStatus: 'pending' } }); acquired(); await unlock; }, { timeout: 10000 }, ); await locked; let finished = false; const concurrent = check().then((result) => { finished = true; return result; }); await new Promise((resolve) => setTimeout(resolve, 100)); const waitedForWriter = !finished; release(); await writer; assert(waitedForWriter); assert.equal((await concurrent).allowed, false); checks.push('PostgreSQL writer lock blocks concurrent permission and committed revocation is observed'); await db.smsDrainageInfo.update({ where: { id: materials[0].id }, data: { auditStatus: 'approved' } }); for (const content of [ 'https://lisglo.cn.evil.com/p', 'https://evil.com/?next=lisglo.cn', 'https://lisglo.cn@evil.com/p', ]) { const rejected = await db.smsMessageRecord.create({ data: { ...common, messageId: `${prefix}-${randomUUID()}`, content, phoneNumber: '13800000000', carrier: 'mobile', }, }); await assert.rejects(evaluateMessageDrainage(db, rejected, 'mobile', undefined, true), /未在当前签名下添加/); } checks.push('real configured detectors reject suffix spoof, query spoof and URL userInfo spoof'); await evaluateMessageDrainage(db, message, 'mobile', undefined, true); await new ReportsService(db).refreshRollingWindow(new Date(Date.now() + 86400000)); const quality = await db.dailyQualityReport.findMany({ where: { dimensionType: 'drainage', drainageInfoId: { in: materials.map((item) => item.id) } }, }); assert.equal(quality.length, 2); assert(quality.every((row) => row.submittedUnits === 1)); const today = new Date(new Date().toISOString().slice(0, 10) + 'T00:00:00+08:00'); const customerUnits = await db.smsMessageRecord.aggregate({ where: { applicationId: application.id, queuedAt: { gte: today, lt: new Date(today.getTime() + 86400000) } }, _sum: { billingUnits: true }, }); const applicationQuality = await db.dailyQualityReport.findFirstOrThrow({ where: { dimensionType: 'application', dimensionId: application.id }, orderBy: { reportDate: 'desc' }, }); assert.equal(applicationQuality.submittedUnits, customerUnits._sum.billingUnits); // Synthetic rejected metadata only, no supplier transmission is performed. await db.smsSubmitRecord.update({ where: { id: submit.id }, data: { submitStatus: 'rejected', errorCode: 'DRN' } }); const reports = await new ChannelReportingService(db).listReportTasks( application.tenantId, undefined, channels[1].id, 'drainage', ); const ownReports = reports.filter((row) => row.signatureId === signature.id); assert.equal(ownReports.length, 2); assert(ownReports.every((row) => row.deliveryStats.total === 0)); checks.push( 'real quality SQL attributes one message to both targets without multiplying customer units; no-wire rejection counts zero channel attempts', ); assert.equal(await db.gatewaySubmitOutbox.count({ where: { messageRecordId: message.id } }), 0); assert.equal(await db.smsReceiptRecord.count({ where: { messageRecordId: message.id } }), 0); checks.push('no send intent or customer receipt was created by qualification checks'); console.log(JSON.stringify({ success: true, database: name, fixturePrefix: prefix, checks })); } finally { if (app) await app.close(); await db.$disconnect(); }