From 6d20b200b313b555c854b4055ba70a3906c68651 Mon Sep 17 00:00:00 2001 From: Kit <344842+roodboi@users.noreply.github.com> Date: Sat, 14 Nov 2026 11:54:01 +0000 Subject: [PATCH 1/2] fix(test): isolate daemon session routes from the default tmux server --- tests/daemon-sessions.test.ts | 89 ++++++++++++++++++++++++++++------- 1 file changed, 71 insertions(+), 18 deletions(-) diff --git a/tests/daemon-sessions.test.ts b/tests/daemon-sessions.test.ts index 4ab1b0dbb..e20ddce50 100644 --- a/tests/daemon-sessions.test.ts +++ b/tests/daemon-sessions.test.ts @@ -1,12 +1,21 @@ -import { describe, expect, test } from "bun:test"; -import { execSync } from "node:child_process"; +import { + afterAll, + afterEach, + beforeAll, + beforeEach, + describe, + expect, + test, +} from "bun:test"; import { once } from "node:events"; +import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; import { createConnection, createServer, type Server, type Socket, } from "node:net"; +import { join } from "node:path"; import { validateControlPlaneRouteTarget, @@ -14,21 +23,28 @@ import { } from "../src/daemon/control-plane-route-validation.ts"; import { startRequestTargetProxy } from "../src/daemon/request-target-proxy.ts"; import { handleSessionRoutes } from "../src/daemon/routes/sessions.ts"; - -/** - * Check if tmux is available (required for session tests). - */ -function isTmuxAvailable(): boolean { - try { - execSync("which tmux", { stdio: "ignore" }); - return true; - } catch { - return false; +import { exec } from "../src/lib/shell.ts"; +import { restoreEnv } from "./helpers/env.ts"; + +/** Real session-route tests require tmux, always on a task-owned socket. */ +const tmuxPath = Bun.which("tmux"); +const hasTmux = tmuxPath !== null; +let sessionRoot = ""; +let sessionSocket = ""; +let savedPath: string | undefined; + +/** Never address the default server, even when a route case fails after create. */ +function privateTmux(args: readonly string[]) { + if (!(tmuxPath && sessionSocket)) { + throw new Error("Session route fixture has no private tmux socket."); } + return exec([tmuxPath, "-f", "/dev/null", "-S", sessionSocket, ...args], { + stdin: "ignore", + unsetEnvKeys: ["TMUX"], + timeoutMs: 3000, + }); } -const hasTmux = isTmuxAvailable(); - /** * Helper to create a mock Request. */ @@ -510,6 +526,41 @@ describe("request target proxy", () => { }); describe.skipIf(!hasTmux)("handleSessionRoutes", () => { + beforeAll(async () => { + // Short path for Darwin's Unix socket limit; each process owns a new server. + sessionRoot = await mkdtemp("/tmp/hack-session-routes-"); + sessionSocket = join(sessionRoot, "tmux.sock"); + await mkdir(join(sessionRoot, "bin")); + // The route executes real tmux through this shim. Never inherit a caller's + // socket or config; only the fixture's private server can be created/stopped. + const quotedTmux = `'${tmuxPath?.replaceAll("'", "'\\''")}'`; + await writeFile( + join(sessionRoot, "bin", "tmux"), + `#!/bin/sh\nunset TMUX\nif [ "$1" = new-session ]; then\n exec ${quotedTmux} -f /dev/null -S '${sessionSocket}' "$@" /bin/sleep 300\nfi\nexec ${quotedTmux} -f /dev/null -S '${sessionSocket}' "$@"\n`, + { mode: 0o700 } + ); + }); + beforeEach(() => { + savedPath = process.env.PATH; + process.env.PATH = `${join(sessionRoot, "bin")}:${savedPath ?? "/usr/bin:/bin"}`; + }); + afterEach(async () => { + try { + await privateTmux(["kill-server"]); + expect((await privateTmux(["list-sessions"])).exitCode).toBe(1); + } finally { + restoreEnv("PATH", savedPath); + } + }); + afterAll(async () => { + try { + await privateTmux(["kill-server"]); + } finally { + await rm(sessionRoot, { recursive: true, force: true }); + sessionRoot = ""; + sessionSocket = ""; + } + }); describe("route matching", () => { test("returns null for non-session routes", async () => { const req = mockRequest({ method: "GET", path: "/v1/status" }); @@ -595,8 +646,10 @@ describe.skipIf(!hasTmux)("handleSessionRoutes", () => { }); const url = new URL(req.url); const result = await handleSessionRoutes({ req, url }); - // May fail if tmux isn't available, but should not fail on validation - expect(result?.status).not.toBe(400); + expect(result?.status).toBe(201); + expect( + (await privateTmux(["has-session", "-t", "=myproject123"])).exitCode + ).toBe(0); }); test("accepts names with dashes", async () => { @@ -607,7 +660,7 @@ describe.skipIf(!hasTmux)("handleSessionRoutes", () => { }); const url = new URL(req.url); const result = await handleSessionRoutes({ req, url }); - expect(result?.status).not.toBe(400); + expect(result?.status).toBe(201); }); test("accepts names with underscores", async () => { @@ -618,7 +671,7 @@ describe.skipIf(!hasTmux)("handleSessionRoutes", () => { }); const url = new URL(req.url); const result = await handleSessionRoutes({ req, url }); - expect(result?.status).not.toBe(400); + expect(result?.status).toBe(201); }); test("rejects names with dots", async () => { From 6eda997f92096bd55356334f50bcc83646e13b87 Mon Sep 17 00:00:00 2001 From: Kit <344842+roodboi@users.noreply.github.com> Date: Sat, 14 Nov 2026 11:55:50 +0000 Subject: [PATCH 2/2] test: verify all private sessions and guard fixture environment restore --- tests/daemon-sessions.test.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/daemon-sessions.test.ts b/tests/daemon-sessions.test.ts index e20ddce50..e0e7928eb 100644 --- a/tests/daemon-sessions.test.ts +++ b/tests/daemon-sessions.test.ts @@ -32,6 +32,7 @@ const hasTmux = tmuxPath !== null; let sessionRoot = ""; let sessionSocket = ""; let savedPath: string | undefined; +let pathSaved = false; /** Never address the default server, even when a route case fails after create. */ function privateTmux(args: readonly string[]) { @@ -533,6 +534,7 @@ describe.skipIf(!hasTmux)("handleSessionRoutes", () => { await mkdir(join(sessionRoot, "bin")); // The route executes real tmux through this shim. Never inherit a caller's // socket or config; only the fixture's private server can be created/stopped. + // The finite pane command also caps a server's life if the test process dies. const quotedTmux = `'${tmuxPath?.replaceAll("'", "'\\''")}'`; await writeFile( join(sessionRoot, "bin", "tmux"), @@ -542,6 +544,7 @@ describe.skipIf(!hasTmux)("handleSessionRoutes", () => { }); beforeEach(() => { savedPath = process.env.PATH; + pathSaved = true; process.env.PATH = `${join(sessionRoot, "bin")}:${savedPath ?? "/usr/bin:/bin"}`; }); afterEach(async () => { @@ -549,7 +552,10 @@ describe.skipIf(!hasTmux)("handleSessionRoutes", () => { await privateTmux(["kill-server"]); expect((await privateTmux(["list-sessions"])).exitCode).toBe(1); } finally { - restoreEnv("PATH", savedPath); + if (pathSaved) { + restoreEnv("PATH", savedPath); + pathSaved = false; + } } }); afterAll(async () => { @@ -661,6 +667,9 @@ describe.skipIf(!hasTmux)("handleSessionRoutes", () => { const url = new URL(req.url); const result = await handleSessionRoutes({ req, url }); expect(result?.status).toBe(201); + expect( + (await privateTmux(["has-session", "-t", "=my-project"])).exitCode + ).toBe(0); }); test("accepts names with underscores", async () => { @@ -672,6 +681,9 @@ describe.skipIf(!hasTmux)("handleSessionRoutes", () => { const url = new URL(req.url); const result = await handleSessionRoutes({ req, url }); expect(result?.status).toBe(201); + expect( + (await privateTmux(["has-session", "-t", "=my_project"])).exitCode + ).toBe(0); }); test("rejects names with dots", async () => {