From 1c57de85be0dc6664d078727d1af211a8eed3cf0 Mon Sep 17 00:00:00 2001 From: Brennan Benson Date: Thu, 9 Jul 2026 17:22:00 -0700 Subject: [PATCH] test(daemon): pin the connection/hello shutdown barriers with a mutation-killing test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The shuttingDown guards in handleConnection and handleFirstMessage had no test that fails when either is deleted — the existing coverage drove routeRequest directly or never sent a hello. Raise only the barrier flag while the listener still accepts, then assert a pre-admitted socket's late valid hello and a brand-new connection are both destroyed unregistered. Verified: deleting either guard fails this test. Co-authored-by: Orca --- src/main/daemon/daemon-server.test.ts | 37 +++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/main/daemon/daemon-server.test.ts b/src/main/daemon/daemon-server.test.ts index 33e15964677..f22ac50d241 100644 --- a/src/main/daemon/daemon-server.test.ts +++ b/src/main/daemon/daemon-server.test.ts @@ -55,6 +55,7 @@ type DaemonServerPrivate = { } > idleExit: { isArmed(): boolean } | null + shuttingDown: boolean routeRequest(clientId: string, request: DaemonRequest): Promise } @@ -687,6 +688,42 @@ describe('DaemonServer', () => { expect(daemon.clients.size).toBe(0) }) + it('destroys sockets that connect or hello after the shutdown barrier is raised', async () => { + // Why raise only the flag: while the listener is still accepting, the + // handleConnection/handleFirstMessage barriers are the only defense + // against a socket becoming a client on a half-shut server. + await startServer() + const daemon = server as unknown as DaemonServerPrivate + + // Admitted before the barrier, hello still unsent. + const preBarrier = connect(socketPath) + await new Promise((resolve) => preBarrier.once('connect', resolve)) + const preBarrierClosed = new Promise((resolve) => preBarrier.once('close', resolve)) + preBarrier.on('error', () => {}) + + daemon.shuttingDown = true + + // A valid late hello must be destroyed unanswered, never registered. + preBarrier.write( + encodeNdjson({ + type: 'hello', + version: PROTOCOL_VERSION, + token: readFileSync(tokenPath, 'utf-8').trim(), + clientId: 'late-hello', + role: 'control' + }) + ) + await preBarrierClosed + expect(daemon.clients.size).toBe(0) + + // A brand-new connection after the barrier is destroyed on arrival. + const postBarrier = connect(socketPath) + const postBarrierClosed = new Promise((resolve) => postBarrier.once('close', resolve)) + postBarrier.on('error', () => {}) + await postBarrierClosed + expect(daemon.clients.size).toBe(0) + }) + it('never fires while a session is alive and exits after the last session ends', async () => { // Why seed the session before start(): start() is what arms the // countdown, so with a session already present the timer never arms at