// Real Prisma/service integration in an isolated schema. No public business writes or SMS. import assert from 'node:assert/strict'; import { randomUUID } from 'node:crypto'; import pg from '../../api/node_modules/pg/lib/index.js'; import prisma from '../../api/node_modules/@prisma/client/default.js'; import adapter from '../../api/node_modules/@prisma/adapter-pg/dist/index.js'; import service from '../../api/dist/sending-monitor/sending-monitor.module.js'; import management from '../../api/dist/sending-monitor/monitor-rule-management.service.js'; process.env.TZ = 'UTC'; const connectionString = process.env.QA_DATABASE_URL || process.env.DATABASE_URL; if (!connectionString) throw Error('QA_DATABASE_URL is required'); const schema = `qa_monitor_rules_${randomUUID().replaceAll('-', '')}`; assert.match(schema, /^qa_monitor_rules_[a-f0-9]{32}$/); const admin = new pg.Client({ connectionString }); let client, checks = 0; await admin.connect(); try { await admin.query(`CREATE SCHEMA "${schema}"`); for (const table of [ 'Tenant', 'SmsApplication', 'SmsSignature', 'SendingMonitorRule', 'SendingMonitorRuleVersion', 'OperationLog', ]) await admin.query(`CREATE TABLE "${schema}"."${table}" (LIKE public."${table}" INCLUDING ALL)`); const pool = new pg.Pool({ connectionString, max: 2, options: `-c search_path=${schema} -c timezone=UTC` }); client = new prisma.PrismaClient({ adapter: new adapter.PrismaPg(pool, { schema, disposeExternalPool: true }) }); assert.equal((await client.$queryRawUnsafe('SELECT current_schema() name'))[0].name, schema); for (const id of ['t1', 't2']) await client.tenant.create({ data: { id, name: `企业${id}`, code: id } }); for (let i = 0; i < 23; i++) await client.smsApplication.create({ data: { id: `a${i}`, tenantId: 't1', name: `应用${String(i).padStart(2, '0')}`, cmppAccount: `isolated${i}`, cmppEnterpriseCode: 'qa', secretHash: 'not-a-real-credential', }, }); await client.smsApplication.create({ data: { id: 'b1', tenantId: 't2', name: '企业乙应用', cmppAccount: 'isolated-b', cmppEnterpriseCode: 'qa', secretHash: 'not-a-real-credential', }, }); await client.smsSignature.create({ data: { id: 's1', tenantId: 't1', applicationId: 'a0', name: '同名签名' } }); await client.smsSignature.create({ data: { id: 's2', tenantId: 't2', applicationId: 'b1', name: '同名签名' } }); const sut = new service.SendingMonitorService(client), reads = new management.MonitorRuleManagementService(client); const options = await reads.options({ kind: 'application', keyword: '企业t1' }); assert.equal(options.items.length, 20); assert.equal(options.hasMore, true); assert(options.items.every((x) => x.name.includes('企业t1') && x.tenantId === 't1')); checks++; const next = await reads.options({ kind: 'application', keyword: '企业t1', page: '2' }); assert.equal(next.items.length, 3); assert.equal(next.hasMore, false); assert(next.items.every((x) => !options.items.some((old) => old.id === x.id))); checks++; assert.deepEqual( (await reads.options({ kind: 'signature', tenantId: 't1', applicationId: 'a0' })).items.map((x) => x.id), ['s1'], ); assert.equal((await reads.options({ kind: 'signature', tenantId: 't1', applicationId: 'b1' })).items.length, 0); checks++; await assert.rejects(reads.options({ kind: 'signature' }), (e) => e.getStatus() === 400); await assert.rejects(reads.list({ page: '0' }), (e) => e.getStatus() === 400); await assert.rejects(reads.editor({ tenantId: 't1' }), (e) => e.getStatus() === 400); checks++; const config = { enabled: true, minSamples: 100, thresholds: [90, 95, 98], consecutiveBad: 1, consecutiveGood: 2 }; const save = async (scope, values = config, version = 0) => (await sut.saveRule({ type: 'overall', scope, config: values, version }, 'qa-rule-manager'))[0]; const global = await save({}); for (let i = 0; i < 23; i++) await save({ tenantId: 't1', applicationId: `a${i}` }); const signature = await save({ tenantId: 't1', signatureId: 's1' }); const combinedScope = { tenantId: 't1', applicationId: 'a0', signatureId: 's1' }; const combined = await save(combinedScope); assert.equal((await reads.list({})).total, 25); assert.equal((await reads.list({ page: '2' })).items.length, 5); assert.equal((await reads.list({ keyword: '应用00', kind: 'combined' })).items[0].names.signatureName, '同名签名'); checks++; const pending = await reads.editor(combinedScope); assert.equal(pending.current.version, 1); assert.equal(pending.matched.length, 0); checks++; await client.$executeRawUnsafe( `UPDATE "SendingMonitorRuleVersion" SET "effectiveAt"=(CURRENT_TIMESTAMP AT TIME ZONE 'UTC')-interval '1 minute'`, ); await client.$executeRawUnsafe( `UPDATE "SendingMonitorRule" SET "effectiveAt"=(CURRENT_TIMESTAMP AT TIME ZONE 'UTC')-interval '1 minute'`, ); const effective = await reads.editor(combinedScope); assert.deepEqual( effective.matched.map((r) => Object.keys(r.scope).length), [3, 2, 2, 0], ); assert.equal(effective.inherited[0].ruleId, signature.id); assert.equal(effective.matched[3].ruleId, global.id); checks++; const newScope = await reads.editor({ tenantId: 't2', applicationId: 'b1' }); assert.equal(newScope.current, null); assert.equal(newScope.inherited[0].ruleId, global.id); checks++; await assert.rejects(save({ tenantId: 't1', applicationId: 'b1' }), (e) => e.getStatus() === 400); await assert.rejects(save({ tenantId: 't1', applicationId: 'a0', signatureId: 's2' }), (e) => e.getStatus() === 400); checks++; const disabled = await save(combinedScope, { ...config, enabled: false }, 1); const pendingDisable = await reads.list({ kind: 'combined' }); assert.equal(pendingDisable.items[0].config.enabled, false); assert.equal(pendingDisable.items[0].active.config.enabled, true); checks++; await client.$executeRawUnsafe( `UPDATE "SendingMonitorRuleVersion" SET "effectiveAt"=(CURRENT_TIMESTAMP AT TIME ZONE 'UTC')-interval '1 minute'`, ); assert.equal((await reads.editor(combinedScope)).matched[0].config.enabled, false); checks++; const race = await Promise.allSettled([ sut.restoreRule(combined.id, disabled.version, 'qa-rule-manager'), sut.restoreRule(combined.id, disabled.version, 'qa-rule-manager'), ]); assert.equal(race.filter((r) => r.status === 'fulfilled').length, 1); assert.equal(race.find((r) => r.status === 'rejected').reason.getStatus(), 409); checks++; await client.$executeRawUnsafe( `UPDATE "SendingMonitorRuleVersion" SET "effectiveAt"=(CURRENT_TIMESTAMP AT TIME ZONE 'UTC')-interval '1 minute'`, ); assert.equal((await reads.editor(combinedScope)).matched[0].ruleId, signature.id); checks++; await client.smsApplication.update({ where: { id: 'a1' }, data: { status: 'deleted' } }); await client.smsSignature.update({ where: { id: 's1' }, data: { auditStatus: 'deleted' } }); assert(!(await reads.options({ kind: 'application' })).items.some((r) => r.id === 'a1')); assert.equal((await reads.options({ kind: 'signature', tenantId: 't1' })).items.length, 0); const historical = (await reads.list({ keyword: '应用01' })).items[0]; assert.equal(historical.names.applicationStatus, 'deleted'); assert.equal(historical.names.applicationName, '应用01'); await assert.rejects(save(historical.scope, config, historical.version), (e) => e.getStatus() === 400); await sut.restoreRule(historical.id, historical.version, 'qa-rule-manager'); checks++; const oldCount = await client.sendingMonitorRuleVersion.count(); await client.$executeRawUnsafe( `ALTER TABLE "OperationLog" ADD CONSTRAINT qa_reject CHECK ("resource"<>'sending_monitor') NOT VALID`, ); await assert.rejects(sut.restoreRule(signature.id, signature.version, 'qa-rule-manager')); assert.equal(await client.sendingMonitorRuleVersion.count(), oldCount); checks++; console.log(JSON.stringify({ checks, realPrisma: true, schemaIsolated: true, publicBusinessWrites: 0 })); } finally { if (client) await client.$disconnect(); await admin.query(`DROP SCHEMA "${schema}" CASCADE`); await admin.end(); }