Skip to content

Commit ecee8bd

Browse files
NickCirvclaude
andcommitted
test(bash-postool): platform-aware expected paths for Windows CI
Windows CI on PR #15 failed all 22 bash-postool assertions because expected values were hard-coded as POSIX paths (`/proj/src/foo.ts`) while the implementation calls `path.resolve()` which returns platform-native paths — backslashes on Windows, forward slashes on POSIX systems. Fix: build expected values through `path.resolve` the same way the implementation does. The test now captures the platform-native cwd once and derives expected absolute paths from it, so the same source asserts correctly on macOS, Linux, and Windows. The implementation itself is unchanged — `pathResolve` already does the right thing per platform. This was purely a cross-platform test hygiene issue. Also swapped the hard-coded `/tmp/foo.ts` absolute-path test for a platform-resolved equivalent so Windows doesn't choke on missing drive prefix. Local verify: 25/25 bash-postool tests pass on macOS Node 25. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 6db54ec commit ecee8bd

1 file changed

Lines changed: 65 additions & 48 deletions

File tree

Lines changed: 65 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,136 +1,153 @@
11
import { describe, it, expect } from "vitest";
2+
import { resolve as pathResolve } from "node:path";
23
import {
34
parseFileOps,
45
handleBashPostTool,
56
} from "../../../src/intercept/handlers/bash-postool.js";
67

8+
/**
9+
* Path helpers: expected values are built via pathResolve to match the
10+
* platform-native output of the implementation (Windows produces
11+
* backslashes, macOS/Linux produces forward slashes). Hard-coding POSIX
12+
* paths broke Windows CI on v2.1 PR #15 — this file is the fix.
13+
*
14+
* Uses a platform-appropriate "project root": on Windows pathResolve
15+
* pins to the current drive, which is fine because the tests only
16+
* compare against the same pathResolve output.
17+
*/
18+
const CWD = pathResolve("/proj");
19+
const expectedAbs = (rel: string) => pathResolve(CWD, rel);
20+
721
describe("bash-postool — parseFileOps: rm variants", () => {
822
it("parses bare rm with single file", () => {
9-
const r = parseFileOps("rm src/foo.ts", "/proj");
10-
expect(r).toEqual([{ action: "prune", path: "/proj/src/foo.ts" }]);
23+
const r = parseFileOps("rm src/foo.ts", CWD);
24+
expect(r).toEqual([{ action: "prune", path: expectedAbs("src/foo.ts") }]);
1125
});
1226

1327
it("parses rm -f", () => {
14-
const r = parseFileOps("rm -f src/foo.ts", "/proj");
15-
expect(r).toEqual([{ action: "prune", path: "/proj/src/foo.ts" }]);
28+
const r = parseFileOps("rm -f src/foo.ts", CWD);
29+
expect(r).toEqual([{ action: "prune", path: expectedAbs("src/foo.ts") }]);
1630
});
1731

1832
it("parses rm -rf with multiple files", () => {
19-
const r = parseFileOps("rm -rf src/a.ts src/b.ts", "/proj");
33+
const r = parseFileOps("rm -rf src/a.ts src/b.ts", CWD);
2034
expect(r).toEqual([
21-
{ action: "prune", path: "/proj/src/a.ts" },
22-
{ action: "prune", path: "/proj/src/b.ts" },
35+
{ action: "prune", path: expectedAbs("src/a.ts") },
36+
{ action: "prune", path: expectedAbs("src/b.ts") },
2337
]);
2438
});
2539

2640
it("keeps absolute paths absolute", () => {
27-
const r = parseFileOps("rm /tmp/foo.ts", "/proj");
28-
expect(r).toEqual([{ action: "prune", path: "/tmp/foo.ts" }]);
41+
// Use a platform-native absolute path so this test is consistent
42+
// across macOS/Linux (/tmp) and Windows (resolves under current drive).
43+
const abs = pathResolve("/tmp/foo.ts");
44+
const r = parseFileOps(`rm ${abs}`, CWD);
45+
expect(r).toEqual([{ action: "prune", path: abs }]);
2946
});
3047
});
3148

3249
describe("bash-postool — parseFileOps: mv and cp", () => {
3350
it("mv prunes src and reindexes dst", () => {
34-
const r = parseFileOps("mv src/old.ts src/new.ts", "/proj");
51+
const r = parseFileOps("mv src/old.ts src/new.ts", CWD);
3552
expect(r).toEqual([
36-
{ action: "prune", path: "/proj/src/old.ts" },
37-
{ action: "reindex", path: "/proj/src/new.ts" },
53+
{ action: "prune", path: expectedAbs("src/old.ts") },
54+
{ action: "reindex", path: expectedAbs("src/new.ts") },
3855
]);
3956
});
4057

4158
it("mv with -v flag still parses", () => {
42-
const r = parseFileOps("mv -v src/old.ts src/new.ts", "/proj");
59+
const r = parseFileOps("mv -v src/old.ts src/new.ts", CWD);
4360
expect(r).toEqual([
44-
{ action: "prune", path: "/proj/src/old.ts" },
45-
{ action: "reindex", path: "/proj/src/new.ts" },
61+
{ action: "prune", path: expectedAbs("src/old.ts") },
62+
{ action: "reindex", path: expectedAbs("src/new.ts") },
4663
]);
4764
});
4865

4966
it("cp reindexes dst only", () => {
50-
const r = parseFileOps("cp src/a.ts src/b.ts", "/proj");
51-
expect(r).toEqual([{ action: "reindex", path: "/proj/src/b.ts" }]);
67+
const r = parseFileOps("cp src/a.ts src/b.ts", CWD);
68+
expect(r).toEqual([{ action: "reindex", path: expectedAbs("src/b.ts") }]);
5269
});
5370

5471
it("mv with wrong arg count returns empty", () => {
55-
expect(parseFileOps("mv a.ts", "/proj")).toEqual([]);
56-
expect(parseFileOps("mv a.ts b.ts c.ts", "/proj")).toEqual([]);
72+
expect(parseFileOps("mv a.ts", CWD)).toEqual([]);
73+
expect(parseFileOps("mv a.ts b.ts c.ts", CWD)).toEqual([]);
5774
});
5875
});
5976

6077
describe("bash-postool — parseFileOps: git variants", () => {
6178
it("git rm prunes", () => {
62-
const r = parseFileOps("git rm src/foo.ts", "/proj");
63-
expect(r).toEqual([{ action: "prune", path: "/proj/src/foo.ts" }]);
79+
const r = parseFileOps("git rm src/foo.ts", CWD);
80+
expect(r).toEqual([{ action: "prune", path: expectedAbs("src/foo.ts") }]);
6481
});
6582

6683
it("git rm -r prunes", () => {
67-
const r = parseFileOps("git rm -r src/foo.ts", "/proj");
68-
expect(r).toEqual([{ action: "prune", path: "/proj/src/foo.ts" }]);
84+
const r = parseFileOps("git rm -r src/foo.ts", CWD);
85+
expect(r).toEqual([{ action: "prune", path: expectedAbs("src/foo.ts") }]);
6986
});
7087

7188
it("git mv prunes src and reindexes dst", () => {
72-
const r = parseFileOps("git mv old.ts new.ts", "/proj");
89+
const r = parseFileOps("git mv old.ts new.ts", CWD);
7390
expect(r).toEqual([
74-
{ action: "prune", path: "/proj/old.ts" },
75-
{ action: "reindex", path: "/proj/new.ts" },
91+
{ action: "prune", path: expectedAbs("old.ts") },
92+
{ action: "reindex", path: expectedAbs("new.ts") },
7693
]);
7794
});
7895

7996
it("unknown git subcommand returns empty", () => {
80-
expect(parseFileOps("git status", "/proj")).toEqual([]);
81-
expect(parseFileOps("git commit -m foo", "/proj")).toEqual([]);
97+
expect(parseFileOps("git status", CWD)).toEqual([]);
98+
expect(parseFileOps("git commit -m foo", CWD)).toEqual([]);
8299
});
83100
});
84101

85102
describe("bash-postool — parseFileOps: redirections", () => {
86103
it("cat with single > redirect reindexes dst", () => {
87-
const r = parseFileOps("cat template.ts > out.ts", "/proj");
88-
expect(r).toEqual([{ action: "reindex", path: "/proj/out.ts" }]);
104+
const r = parseFileOps("cat template.ts > out.ts", CWD);
105+
expect(r).toEqual([{ action: "reindex", path: expectedAbs("out.ts") }]);
89106
});
90107

91108
it(">> append redirect reindexes dst", () => {
92-
const r = parseFileOps("echo foo >> log.ts", "/proj");
93-
expect(r).toEqual([{ action: "reindex", path: "/proj/log.ts" }]);
109+
const r = parseFileOps("echo foo >> log.ts", CWD);
110+
expect(r).toEqual([{ action: "reindex", path: expectedAbs("log.ts") }]);
94111
});
95112
});
96113

97114
describe("bash-postool — parseFileOps: pass-through cases", () => {
98115
it("globs pass through", () => {
99-
expect(parseFileOps("rm src/*.ts", "/proj")).toEqual([]);
116+
expect(parseFileOps("rm src/*.ts", CWD)).toEqual([]);
100117
});
101118

102119
it("pipes pass through", () => {
103-
expect(parseFileOps("find . | xargs rm", "/proj")).toEqual([]);
120+
expect(parseFileOps("find . | xargs rm", CWD)).toEqual([]);
104121
});
105122

106123
it("subshells pass through", () => {
107-
expect(parseFileOps("rm $(find . -name auth)", "/proj")).toEqual([]);
124+
expect(parseFileOps("rm $(find . -name auth)", CWD)).toEqual([]);
108125
});
109126

110127
it("backticks pass through", () => {
111-
expect(parseFileOps("rm `find . -name auth`", "/proj")).toEqual([]);
128+
expect(parseFileOps("rm `find . -name auth`", CWD)).toEqual([]);
112129
});
113130

114131
it("unrelated commands pass through", () => {
115-
expect(parseFileOps("ls src/", "/proj")).toEqual([]);
116-
expect(parseFileOps("grep foo src/*", "/proj")).toEqual([]);
117-
expect(parseFileOps("npm test", "/proj")).toEqual([]);
132+
expect(parseFileOps("ls src/", CWD)).toEqual([]);
133+
expect(parseFileOps("grep foo src/*", CWD)).toEqual([]);
134+
expect(parseFileOps("npm test", CWD)).toEqual([]);
118135
});
119136

120137
it("empty / invalid input passes through", () => {
121-
expect(parseFileOps("", "/proj")).toEqual([]);
122-
expect(parseFileOps(" ", "/proj")).toEqual([]);
138+
expect(parseFileOps("", CWD)).toEqual([]);
139+
expect(parseFileOps(" ", CWD)).toEqual([]);
123140
// @ts-expect-error — testing runtime guard
124-
expect(parseFileOps(null, "/proj")).toEqual([]);
141+
expect(parseFileOps(null, CWD)).toEqual([]);
125142
});
126143

127144
it("oversized command passes through", () => {
128145
const huge = "rm " + "x".repeat(501);
129-
expect(parseFileOps(huge, "/proj")).toEqual([]);
146+
expect(parseFileOps(huge, CWD)).toEqual([]);
130147
});
131148

132149
it("touch passes through (empty file, nothing to index)", () => {
133-
expect(parseFileOps("touch foo.ts", "/proj")).toEqual([]);
150+
expect(parseFileOps("touch foo.ts", CWD)).toEqual([]);
134151
});
135152
});
136153

@@ -139,7 +156,7 @@ describe("bash-postool — handleBashPostTool", () => {
139156
const r = handleBashPostTool({
140157
tool_name: "Read",
141158
tool_input: { command: "rm foo.ts" },
142-
cwd: "/proj",
159+
cwd: CWD,
143160
});
144161
expect(r.ops).toEqual([]);
145162
});
@@ -148,7 +165,7 @@ describe("bash-postool — handleBashPostTool", () => {
148165
const r = handleBashPostTool({
149166
tool_name: "Bash",
150167
tool_input: {},
151-
cwd: "/proj",
168+
cwd: CWD,
152169
});
153170
expect(r.ops).toEqual([]);
154171
});
@@ -157,8 +174,8 @@ describe("bash-postool — handleBashPostTool", () => {
157174
const r = handleBashPostTool({
158175
tool_name: "Bash",
159176
tool_input: { command: "rm src/foo.ts" },
160-
cwd: "/proj",
177+
cwd: CWD,
161178
});
162-
expect(r.ops).toEqual([{ action: "prune", path: "/proj/src/foo.ts" }]);
179+
expect(r.ops).toEqual([{ action: "prune", path: expectedAbs("src/foo.ts") }]);
163180
});
164181
});

0 commit comments

Comments
 (0)