Files
lislgosms/tools/testing/verify-monitor-rule-management-postgres.mjs
hectorzhao e4f93f7193
CSS quality / css-quality (push) Has been cancelled
feat: 优化整体兜底个性化规则管理交互
2026-09-06 22:16:09 +08:00

161 lines
8.1 KiB
JavaScript

// 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();
}