1
0

stdin-teardown.test.ts 1.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546
  1. /**
  2. * #799 — a socket-backed stdin that fails must shut the server down, not
  3. * orphan/busy-spin. treatStdinFailureAsShutdown is the shared guard.
  4. */
  5. import { describe, it, expect } from 'vitest';
  6. import { PassThrough } from 'stream';
  7. import { treatStdinFailureAsShutdown } from '../src/mcp/stdin-teardown';
  8. describe('treatStdinFailureAsShutdown (#799)', () => {
  9. it("treats a stdin 'error' (ECONNRESET/hangup) as a shutdown signal", () => {
  10. const s = new PassThrough();
  11. let calls = 0;
  12. treatStdinFailureAsShutdown(() => { calls++; }, s);
  13. // No extra 'error' listener would throw here — the guard registers one.
  14. s.emit('error', new Error('read ECONNRESET'));
  15. expect(calls).toBe(1);
  16. });
  17. it("also fires on 'end' and on 'close'", () => {
  18. for (const ev of ['end', 'close'] as const) {
  19. const s = new PassThrough();
  20. let calls = 0;
  21. treatStdinFailureAsShutdown(() => { calls++; }, s);
  22. s.emit(ev);
  23. expect(calls, `event ${ev}`).toBe(1);
  24. }
  25. });
  26. it('destroys the stream so a hung fd leaves epoll', () => {
  27. const s = new PassThrough();
  28. treatStdinFailureAsShutdown(() => { /* noop */ }, s);
  29. s.emit('error', new Error('boom'));
  30. expect(s.destroyed).toBe(true);
  31. });
  32. it('fires onTerminal at most once, even across error → close', () => {
  33. const s = new PassThrough();
  34. let calls = 0;
  35. treatStdinFailureAsShutdown(() => { calls++; }, s);
  36. s.emit('error', new Error('boom')); // fire() also destroys → emits 'close'
  37. s.emit('close'); // must not double-fire
  38. s.emit('end');
  39. expect(calls).toBe(1);
  40. });
  41. });