Files
lislgosms/tools/testing/verify-monitor-config-postgres.mjs
T
2026-09-06 20:22:48 +08:00

104 lines
5.0 KiB
JavaScript

// Runs real service/Prisma operations in a uniquely named schema; never writes public business tables.
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';
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_config_${randomUUID().replaceAll('-', '')}`;
assert.match(schema, /^qa_monitor_config_[a-f0-9]{32}$/);
const admin = new pg.Client({ connectionString });
let client;
let checks = 0;
await admin.connect();
try {
await admin.query(`CREATE SCHEMA "${schema}"`);
for (const table of [
'SmsChannel',
'SendingMonitorTarget',
'SendingMonitorTargetVersion',
'SendingMonitorRule',
'SendingMonitorRuleVersion',
'SendingMonitorAlert',
'OperationLog',
])
await admin.query(`CREATE TABLE "${schema}"."${table}" (LIKE public."${table}" INCLUDING ALL)`);
await admin.query(
`INSERT INTO "${schema}"."SmsChannel" SELECT * FROM public."SmsChannel" WHERE status<>'deleted' LIMIT 1`,
);
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);
const sut = new service.SendingMonitorService(client);
const channel = await client.smsChannel.findFirstOrThrow();
const actor = 'qa-monitor-config';
assert.equal(await client.sendingMonitorTarget.count(), 0);
const joined = await sut.target(channel.id, { enabled: true, version: 0 }, actor);
assert.equal(joined[0].enabled, true);
assert.equal(joined[0].version, 1);
assert.equal(await client.sendingMonitorTargetVersion.count(), 1);
checks++;
await assert.rejects(sut.target(channel.id, { enabled: true, version: 0 }, actor), (e) => e.getStatus?.() === 409);
assert.equal(await client.operationLog.count(), 1);
checks++;
const race = await Promise.allSettled([
sut.target(channel.id, { enabled: false, version: 1 }, actor),
sut.target(channel.id, { enabled: false, version: 1 }, actor),
]);
assert.equal(race.filter((r) => r.status === 'fulfilled').length, 1);
assert.equal(race.find((r) => r.status === 'rejected').reason.getStatus(), 409);
assert.equal(await client.sendingMonitorTargetVersion.count(), 2);
checks++;
await sut.target(channel.id, { enabled: true, version: 2 }, actor);
assert.equal((await client.sendingMonitorTarget.findUniqueOrThrow({ where: { channelId: channel.id } })).version, 3);
checks++;
for (const [id, body, status] of [
['missing', { enabled: true, version: 0 }, 404],
[channel.id, { enabled: 'true', version: 0 }, 400],
[channel.id, { enabled: true, version: -1 }, 400],
]) {
await assert.rejects(sut.target(id, body, actor), (e) => e.getStatus?.() === status);
checks++;
}
const config = { enabled: true, minSamples: 100, thresholds: [90, 95, 98], consecutiveBad: 1, consecutiveGood: 2 };
const body = { type: 'industry', scope: {}, config, version: 0 };
const rules = await sut.saveRule(body, actor);
assert.equal(rules[0].version, 1);
assert.equal(await client.sendingMonitorRuleVersion.count(), 1);
checks++;
const ruleRace = await Promise.allSettled([
sut.saveRule({ ...body, version: 1 }, actor),
sut.saveRule({ ...body, version: 1 }, actor),
]);
assert.equal(ruleRace.filter((r) => r.status === 'fulfilled').length, 1);
assert.equal(ruleRace.find((r) => r.status === 'rejected').reason.getStatus(), 409);
assert.equal(await client.sendingMonitorRuleVersion.count(), 2);
assert.equal(await client.operationLog.count(), 5);
checks++;
await assert.rejects(
sut.saveRule({ ...body, config: { ...config, minSamples: 0 } }, actor),
(e) => e.getStatus?.() === 400,
);
checks++;
// A later audit failure must roll back both the current row and immutable version.
await client.$executeRawUnsafe(
`ALTER TABLE "OperationLog" ADD CONSTRAINT qa_reject CHECK ("resource"<>'sending_monitor') NOT VALID`,
);
await assert.rejects(sut.target(channel.id, { enabled: false, version: 3 }, actor));
assert.equal((await client.sendingMonitorTarget.findUniqueOrThrow({ where: { channelId: channel.id } })).version, 3);
assert.equal(await client.sendingMonitorTargetVersion.count(), 3);
checks++;
await assert.rejects(sut.saveRule({ ...body, version: 2 }, actor));
assert.equal(await client.sendingMonitorRuleVersion.count(), 2);
checks++;
console.log(JSON.stringify({ checks, realPrisma: true, schemaIsolated: true, businessWrites: 0 }));
} finally {
if (client) await client.$disconnect();
await admin.query(`DROP SCHEMA "${schema}" CASCADE`);
await admin.end();
}