fix: bound formatter memory and improve operations workflows

This commit is contained in:
hectorzhao
2026-09-08 12:46:15 +08:00
parent 50ae37242b
commit 2c228a94e1
27 changed files with 4013 additions and 1230 deletions
@@ -0,0 +1,166 @@
// Test environment only. Uses real PostgreSQL in a new isolated schema; no Nest lifecycle or external delivery.
// CMPP_DIAGNOSTIC_ENV=test node --expose-gc <script> <candidate-api-root>
import { createRequire } from 'node:module';
import { resolve } from 'node:path';
import { readFileSync } from 'node:fs';
import assert from 'node:assert/strict';
if (process.env.CMPP_DIAGNOSTIC_ENV !== 'test') throw new Error('Explicit test environment is required');
const root = resolve(process.argv[2]);
const require = createRequire(resolve(root, 'package.json'));
const { Pool } = require('pg');
const { PrismaPg } = require('@prisma/adapter-pg');
const { PrismaClient } = require('@prisma/client');
const { SignatureRetirementService } = require(
resolve(root, 'dist/signature-retirement/signature-retirement.service.js'),
);
const schema = `cmpp_fix_20260908_${Date.now()}`;
const admin = new Pool({ connectionString: process.env.DATABASE_URL, max: 1 });
const tables = [
'Tenant',
'SmsApplication',
'SmsSignature',
'SmsChannel',
'SignatureRetirementDetection',
'SignatureRetirementMessage',
'SignatureRetirementSuppression',
'SignatureRetirementWebhook',
'SignatureRetirementWebhookDelivery',
'OperationLog',
'ChannelSignatureReportTask',
];
const clients = [];
const checks = [];
try {
const before = (await admin.query('SELECT count(*)::int AS count FROM public."SmsMessageRecord"')).rows[0].count;
await admin.query(`CREATE SCHEMA "${schema}"`);
for (const table of tables)
await admin.query(`CREATE TABLE "${schema}"."${table}" (LIKE public."${table}" INCLUDING ALL)`);
const columns = await admin.query(
'SELECT 1 FROM information_schema.columns WHERE table_schema=$1 AND table_name=$2 AND column_name=$3',
[schema, 'SignatureRetirementMessage', 'dailyGroupKey'],
);
if (!columns.rowCount) {
await admin.query(`SET search_path TO "${schema}"`);
await admin.query(
readFileSync(
resolve(root, 'prisma/migrations/20260908050000_retirement_application_daily_message/migration.sql'),
'utf8',
),
);
}
for (let i = 0; i < 2; i++) {
const url = new URL(process.env.DATABASE_URL);
url.searchParams.set('options', `-c search_path=${schema} -c statement_timeout=15000`);
clients.push(
new PrismaClient({
adapter: new PrismaPg(new Pool({ connectionString: url.toString(), max: 2 }), {
schema,
disposeExternalPool: true,
}),
}),
);
}
const [db, other] = clients;
assert.equal((await db.$queryRawUnsafe('SELECT current_schema() AS schema'))[0].schema, schema);
const service = new SignatureRetirementService(db);
const peer = new SignatureRetirementService(other);
const date = '2026-09-08';
for (const id of ['t1', 't2']) await db.tenant.create({ data: { id, code: `QA-${id}`, name: id } });
for (const id of ['app1', 'app2'])
await db.smsApplication.create({
data: {
id,
tenantId: 't1',
name: id,
cmppAccount: `QA-${id}`,
cmppEnterpriseCode: 'QA',
secretHash: 'isolated-not-a-credential',
status: 'disabled',
},
});
for (const [id, app, tenant] of [
['s1', 'app1', 't1'],
['s2', 'app1', 't1'],
['s3', 'app2', 't1'],
['s4', null, 't1'],
['s5', null, 't2'],
]) {
await db.smsSignature.create({
data: { id, tenantId: tenant, applicationId: app, name: id, auditStatus: 'approved' },
});
await db.signatureRetirementDetection.create({
data: {
id: `d-${id}`,
detectionDate: new Date(date),
dimensionType: 'enterprise',
tenantId: tenant,
applicationId: app,
signatureId: id,
carrier: 'mobile',
windowDays: 30,
threshold: 1,
approvedAt: new Date('2026-01-01'),
status: 'alert',
cycleId: `cycle-${id}`,
notificationTitle: '预警',
notificationContent: `冻结正文-${id}`,
},
});
}
const concurrent = await Promise.all([service.publishNotifications(date), peer.publishNotifications(date)]);
assert.equal(
concurrent.reduce((sum, item) => sum + item.created, 0),
4,
);
assert.equal(await db.signatureRetirementMessage.count(), 4);
assert.equal((await service.publishNotifications(date)).created, 0);
const grouped = await db.signatureRetirementMessage.findFirstOrThrow({ where: { applicationId: 'app1' } });
assert.deepEqual(grouped.detectionIds, ['d-s1', 'd-s2']);
assert.equal(grouped.content, '冻结正文-s1\n冻结正文-s2');
checks.push('two-instance daily uniqueness, app/tenant isolation, frozen contents, repeat idempotency');
const page = await service.listMessages({ dateFrom: date, dateTo: date, signatureKeyword: 's2' });
assert.equal(page.total, 1);
assert.equal(page.items[0].id, grouped.id);
assert.equal(page.items[0].detections.length, 2);
const unreadBefore = (await service.unreadCount()).count;
await service.markRead(grouped.id);
assert.equal((await service.unreadCount()).count, unreadBefore - 1);
checks.push('secondary member search, message pagination and deduplicated unread count');
await service.suppressMessage(grouped.id, { mode: 'temporary', days: 7, reason: 'isolated verification' });
assert.equal(await db.signatureRetirementSuppression.count(), 2);
assert.equal((await db.signatureRetirementMessage.findUniqueOrThrow({ where: { id: grouped.id } })).suppressed, true);
checks.push('atomic group suppression');
// Real database scale fixture, distinct from live business data and from the daily-message test above.
await admin.query(`INSERT INTO "${schema}"."SignatureRetirementDetection" (id,"detectionDate","dimensionType","tenantId","applicationId","signatureId","channelKey",carrier,"windowDays",threshold,"approvedAt",status)
SELECT 'scale-'||n, DATE '2026-09-08','channel','t1','app1','s1','scale-'||n,'mobile',30,1,DATE '2026-01-01','healthy' FROM generate_series(1,19783) n`);
const baseline = process.memoryUsage();
const started = Date.now();
const heatmap = await service.heatmap(date);
assert.equal(heatmap.items.length, 19788);
assert.ok(heatmap.items.every((item) => item.activityDate === '2026-09-07'));
const after = process.memoryUsage();
if (global.gc) global.gc();
const afterGc = process.memoryUsage();
assert.ok(after.rss - baseline.rss < 256 * 1024 ** 2, 'Heatmap RSS growth exceeds 256 MiB');
checks.push('19788 PostgreSQL rows through actual heatmap service, T-1 and bounded RSS');
const finalCount = (await admin.query('SELECT count(*)::int AS count FROM public."SmsMessageRecord"')).rows[0].count;
assert.equal(finalCount, before);
console.log(
JSON.stringify({
schema,
checks,
baseline,
after,
afterGc,
durationMs: Date.now() - started,
publicMessages: finalCount,
smsCalls: 0,
lifecycleStarted: false,
}),
);
} finally {
await Promise.all(clients.map((client) => client.$disconnect()));
// Keep the isolated schema for audit; no cleanup of existing schemas or business records.
await admin.end();
}