pediatric-ai-scribe-v3/test/mcp-shutdown.test.js

57 lines
2.7 KiB
JavaScript

const { test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const vm = require('node:vm');
// Execute the actual server shutdown registration/handler, without booting DB or routes.
for (const failure of [null, 'audit', 'db', 'mcp']) {
test('server drains HTTP, audit and DB before MCP/exit: ' + (failure || 'success'), async () => {
const source = fs.readFileSync(require.resolve('../server'), 'utf8');
const events = [];
const signals = {};
let httpClosed;
let deadline;
let deadlineMs;
let releaseAudit;
const auditGate = new Promise(resolve => { releaseAudit = resolve; });
let releaseDb;
const dbGate = new Promise(resolve => { releaseDb = resolve; });
const context = {
console: { log() {}, error() {} },
imageWorker: { async stop() { events.push('images.stop'); } },
server: { close(callback) { events.push('http.close'); httpClosed = callback; } },
process: { on(signal, callback) { signals[signal] = callback; }, exit(code) { events.push('exit:' + code); } },
setTimeout(callback, ms) { deadline = callback; deadlineMs = ms; return { unref() { events.push('guard.unref'); } }; },
clearInterval(id) { assert.equal(id, 123); events.push('cleanup.clear'); },
require(name) {
if (name.endsWith('/auditQueue')) return { async drainAll() {
events.push('audit.start'); await auditGate; events.push('audit.end');
if (failure === 'audit') throw new Error('failure');
} };
if (name.endsWith('/database')) return { _cleanupInterval: 123, pool: { async end() {
events.push('db.start'); await dbGate; events.push('db.end');
if (failure === 'db') throw new Error('failure');
} } };
if (name.endsWith('/clinicalMcpClient')) return { async closeMcpSession() {
events.push('mcp.close'); if (failure === 'mcp') throw new Error('secret');
} };
throw new Error('unexpected dependency');
}
};
vm.runInNewContext(source.slice(source.indexOf('var shuttingDown = false;')), context);
signals.SIGTERM();
signals.SIGINT();
assert.deepEqual(events, ['images.stop', 'http.close', 'guard.unref']);
assert.equal(deadlineMs, 9000);
const done = httpClosed();
assert.deepEqual(events.slice(3), ['audit.start']);
releaseAudit();
await new Promise(resolve => setImmediate(resolve));
assert.deepEqual(events.slice(3), ['audit.start', 'audit.end', 'cleanup.clear', 'db.start']);
releaseDb();
await done;
assert.deepEqual(events.slice(3), ['audit.start', 'audit.end', 'cleanup.clear', 'db.start', 'db.end', 'mcp.close', 'exit:0']);
deadline();
assert.equal(events.at(-1), 'exit:1');
});
}