fix: bound formatter memory and improve operations workflows
This commit is contained in:
@@ -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();
|
||||
}
|
||||
Reference in New Issue
Block a user