typescript-sdk/test/integration-tests/processCleanup.test.ts
2025-12-05 23:56:12 +00:00

113 lines
3.5 KiB
TypeScript

import path from 'node:path';
import { Readable, Writable } from 'node:stream';
import { Client } from '../../src/client/index.js';
import { StdioClientTransport } from '../../src/client/stdio.js';
import { Server } from '../../src/server/index.js';
import { StdioServerTransport } from '../../src/server/stdio.js';
import { LoggingMessageNotificationSchema } from '../../src/types.js';
const FIXTURES_DIR = path.resolve(__dirname, '../../src/__fixtures__');
describe('Process cleanup', () => {
vi.setConfig({ testTimeout: 5000 }); // 5 second timeout
it('server should exit cleanly after closing transport', async () => {
const server = new Server(
{
name: 'test-server',
version: '1.0.0'
},
{
capabilities: {}
}
);
const mockReadable = new Readable({
read() {
this.push(null); // signal EOF
}
}),
mockWritable = new Writable({
write(chunk, encoding, callback) {
callback();
}
});
// Attach mock streams to process for the server transport
const transport = new StdioServerTransport(mockReadable, mockWritable);
await server.connect(transport);
// Close the transport
await transport.close();
// ensure a proper disposal mock streams
mockReadable.destroy();
mockWritable.destroy();
// If we reach here without hanging, the test passes
// The test runner will fail if the process hangs
expect(true).toBe(true);
});
it('onclose should be called exactly once', async () => {
const client = new Client({
name: 'test-client',
version: '1.0.0'
});
const transport = new StdioClientTransport({
command: 'node',
args: ['--import', 'tsx', 'testServer.ts'],
cwd: FIXTURES_DIR
});
await client.connect(transport);
let onCloseWasCalled = 0;
client.onclose = () => {
onCloseWasCalled++;
};
await client.close();
// A short delay to allow the close event to propagate
await new Promise(resolve => setTimeout(resolve, 50));
expect(onCloseWasCalled).toBe(1);
});
it('should exit cleanly for a server that hangs', async () => {
const client = new Client({
name: 'test-client',
version: '1.0.0'
});
const transport = new StdioClientTransport({
command: 'node',
args: ['--import', 'tsx', 'serverThatHangs.ts'],
cwd: FIXTURES_DIR
});
await client.connect(transport);
await client.setLoggingLevel('debug');
client.setNotificationHandler(LoggingMessageNotificationSchema, notification => {
console.debug('server log: ' + notification.params.data);
});
const serverPid = transport.pid!;
await client.close();
// A short delay to allow the close event to propagate
await new Promise(resolve => setTimeout(resolve, 50));
try {
process.kill(serverPid, 9);
throw new Error('Expected server to be dead but it is alive');
} catch (err: unknown) {
// 'ESRCH' the process doesn't exist
if (err && typeof err === 'object' && 'code' in err && err.code === 'ESRCH') {
// success
} else throw err;
}
});
});