import { beforeEach, describe, expect, it, mock } from "bun:test" import type { TmuxConfig } from "../../../config/schema" import type { TmuxCommandResult } from "../runner" const sessionSpawnSpecifier = import.meta.resolve("./session-spawn") const enabledTmuxConfig = { enabled: true, layout: "main-vertical", main_pane_size: 60, main_pane_min_width: 120, agent_pane_min_width: 40, isolation: "inline", } satisfies TmuxConfig const runTmuxCommandMock = mock(async (): Promise => ({ success: true, output: "", stdout: "", stderr: "", exitCode: 0, })) const isInsideTmuxMock = mock((): boolean => true) const isServerRunningMock = mock(async (): Promise => true) const getTmuxPathMock = mock(async (): Promise => "sh") const logMock = mock(() => undefined) function toStringArray(value: unknown): string[] { if (!Array.isArray(value)) { throw new Error("Expected array value") } const items: string[] = [] for (const item of value) { items.push(String(item)) } return items } function getRunTmuxCommandCall(index: number): [string, string[]] { const call = Reflect.get(runTmuxCommandMock.mock.calls, index) const command = Reflect.get(call, 0) const args = Reflect.get(call, 1) if (!Array.isArray(call) || typeof command !== "string" || !Array.isArray(args)) { throw new Error(`Expected tmux runner call at index ${index}`) } return [command, toStringArray(args)] } function getSpawnCommand(): string { const newSessionCall = getRunTmuxCommandCall(2) const newSessionCommand = newSessionCall[1][newSessionCall[1].length - 1] if (newSessionCommand === undefined) { throw new Error("Expected new-session command") } return newSessionCommand } function createDeps(): NonNullable[6]> { return { log: logMock, runTmuxCommand: runTmuxCommandMock, isInsideTmux: isInsideTmuxMock, isServerRunning: isServerRunningMock, getTmuxPath: getTmuxPathMock, } } async function loadSpawnTmuxSession(): Promise { const module = await import(`${sessionSpawnSpecifier}?test=${crypto.randomUUID()}`) return module.spawnTmuxSession } describe("spawnTmuxSession runner integration", () => { beforeEach(() => { mock.restore() runTmuxCommandMock.mockClear() isInsideTmuxMock.mockClear() isServerRunningMock.mockClear() getTmuxPathMock.mockClear() logMock.mockClear() const tmuxCommandResults: TmuxCommandResult[] = [ { success: true, output: "120,40", stdout: "120,40", stderr: "", exitCode: 0 }, { success: false, output: "", stdout: "", stderr: "", exitCode: 1 }, { success: true, output: "%42", stdout: "%42", stderr: "", exitCode: 0 }, { success: true, output: "", stdout: "", stderr: "", exitCode: 0 }, ] runTmuxCommandMock.mockImplementation(async (): Promise => { const nextResult = tmuxCommandResults.shift() if (!nextResult) { throw new Error("No more tmux command results configured") } return nextResult }) isInsideTmuxMock.mockReturnValue(true) isServerRunningMock.mockResolvedValue(true) getTmuxPathMock.mockResolvedValue("sh") }) it("#given source pane available #when spawnTmuxSession called #then delegates display, has-session, new-session, and select-pane to shared runner", async () => { // given const spawnTmuxSession = await loadSpawnTmuxSession() const directory = "/tmp/omo-project/(session)" // when const result = await spawnTmuxSession("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", directory, "%0", createDeps()) // then const displayCall = getRunTmuxCommandCall(0) const hasSessionCall = getRunTmuxCommandCall(1) const newSessionCall = getRunTmuxCommandCall(2) const selectPaneCall = getRunTmuxCommandCall(3) expect(result).toEqual({ success: true, paneId: "%42" }) expect(displayCall[1]).toEqual(["display", "-p", "-t", "%0", "#{window_width},#{window_height}"]) expect(hasSessionCall[1][0]).toBe("has-session") expect(hasSessionCall[1][1]).toBe("-t") expect(hasSessionCall[1][2]?.startsWith("omo-agents-")).toBe(true) expect(newSessionCall[1].slice(0, 4)).toEqual(["new-session", "-d", "-s", newSessionCall[1][3]]) expect(String(newSessionCall[1][3]).startsWith("omo-agents-")).toBe(true) expect(selectPaneCall[1]).toEqual(["select-pane", "-t", "%42", "-T", "omo-subagent-worker"]) expect(getSpawnCommand()).toContain(` --dir '${directory}'`) }) it("#given directory with spaces #when spawnTmuxSession called #then wraps --dir value in single quotes", async () => { // given const spawnTmuxSession = await loadSpawnTmuxSession() // when await spawnTmuxSession("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", "/path with spaces/here", "%0", createDeps()) // then expect(getSpawnCommand()).toContain("--dir '/path with spaces/here'") }) it("#given empty directory #when spawnTmuxSession called #then falls back to process cwd", async () => { // given const spawnTmuxSession = await loadSpawnTmuxSession() // when await spawnTmuxSession("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", "", "%0", createDeps()) // then expect(getSpawnCommand()).toContain(`--dir '${process.cwd()}'`) }) it("#given directory with single quotes #when spawnTmuxSession called #then escapes the value with POSIX-safe single quoting", async () => { // given const spawnTmuxSession = await loadSpawnTmuxSession() // when await spawnTmuxSession("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", "/path/with'quote", "%0", createDeps()) // then expect(getSpawnCommand()).toContain("--dir '/path/with'\\''quote'") }) })