diff --git a/src/features/tmux-subagent/pane-state-parser.test.ts b/src/features/tmux-subagent/pane-state-parser.test.ts new file mode 100644 index 000000000..991c3fd95 --- /dev/null +++ b/src/features/tmux-subagent/pane-state-parser.test.ts @@ -0,0 +1,72 @@ +/// + +import { describe, expect, it } from "bun:test" +import { parsePaneStateOutput } from "./pane-state-parser" + +describe("parsePaneStateOutput", () => { + it("rejects malformed integer fields", () => { + // given + const stdout = "%0\t120oops\t40\t0\t0\t1\t120\t40\n" + + // when + const result = parsePaneStateOutput(stdout) + + // then + expect(result).toBe(null) + }) + + it("rejects negative integer fields", () => { + // given + const stdout = "%0\t-1\t40\t0\t0\t1\t120\t40\n" + + // when + const result = parsePaneStateOutput(stdout) + + // then + expect(result).toBe(null) + }) + + it("rejects empty integer fields", () => { + // given + const stdout = "%0\t\t40\t0\t0\t1\t120\t40\n" + + // when + const result = parsePaneStateOutput(stdout) + + // then + expect(result).toBe(null) + }) + + it("rejects non-binary active flags", () => { + // given + const stdout = "%0\t120\t40\t0\t0\tx\t120\t40\n" + + // when + const result = parsePaneStateOutput(stdout) + + // then + expect(result).toBe(null) + }) + + it("rejects numeric active flags other than zero or one", () => { + // given + const stdout = "%0\t120\t40\t0\t0\t2\t120\t40\n" + + // when + const result = parsePaneStateOutput(stdout) + + // then + expect(result).toBe(null) + }) + + it("rejects empty active flags", () => { + // given + const stdout = "%0\t120\t40\t0\t0\t\t120\t40\n" + + // when + const result = parsePaneStateOutput(stdout) + + // then + expect(result).toBe(null) + }) +}) diff --git a/src/features/tmux-subagent/pane-state-parser.ts b/src/features/tmux-subagent/pane-state-parser.ts index c9aa1ecc9..3ae6579d8 100644 --- a/src/features/tmux-subagent/pane-state-parser.ts +++ b/src/features/tmux-subagent/pane-state-parser.ts @@ -60,6 +60,7 @@ function parsePaneLine(line: string): ParsedPaneLine | null { const height = parseInteger(heightString) const left = parseInteger(leftString) const top = parseInteger(topString) + const isActive = parseActiveValue(activeString) const windowWidth = parseInteger(windowWidthString) const windowHeight = parseInteger(windowHeightString) @@ -68,6 +69,7 @@ function parsePaneLine(line: string): ParsedPaneLine | null { height === null || left === null || top === null || + isActive === null || windowWidth === null || windowHeight === null ) { @@ -82,7 +84,7 @@ function parsePaneLine(line: string): ParsedPaneLine | null { left, top, title: fields.slice(MANDATORY_PANE_FIELD_COUNT).join("\t"), - isActive: activeString === "1", + isActive, }, windowWidth, windowHeight, @@ -120,6 +122,14 @@ function getMandatoryPaneFields(fields: string[]): MandatoryPaneFields | null { } function parseInteger(value: string): number | null { + if (!/^\d+$/.test(value)) return null + const parsedValue = Number.parseInt(value, 10) return Number.isNaN(parsedValue) ? null : parsedValue } + +function parseActiveValue(value: string): boolean | null { + if (value === "1") return true + if (value === "0") return false + return null +}