135 lines
3.9 KiB
TypeScript
135 lines
3.9 KiB
TypeScript
|
|
import { describe, expect, it } from "vitest";
|
||
|
|
|
||
|
|
import { extractMutatedFilePaths, runLspPostToolUseHook } from "../src/codex-hook.js";
|
||
|
|
|
||
|
|
describe("codex PostToolUse hook", () => {
|
||
|
|
it("extracts files from Codex apply_patch command payloads", () => {
|
||
|
|
const paths = extractMutatedFilePaths({
|
||
|
|
tool_name: "apply_patch",
|
||
|
|
tool_input: {
|
||
|
|
command: [
|
||
|
|
"*** Begin Patch",
|
||
|
|
"*** Add File: src/new.ts",
|
||
|
|
"+export const value = 1;",
|
||
|
|
"*** Update File: src/existing.ts",
|
||
|
|
"@@",
|
||
|
|
"-export const old = true;",
|
||
|
|
"+export const old = false;",
|
||
|
|
"*** End Patch",
|
||
|
|
].join("\n"),
|
||
|
|
},
|
||
|
|
tool_response: "Success. Updated files.",
|
||
|
|
});
|
||
|
|
|
||
|
|
expect(paths).toEqual(["src/new.ts", "src/existing.ts"]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("extracts files from edit-style tool input aliases", () => {
|
||
|
|
const paths = extractMutatedFilePaths({
|
||
|
|
tool_name: "Edit",
|
||
|
|
tool_input: { file_path: "src/edit.ts" },
|
||
|
|
tool_response: { ok: true },
|
||
|
|
});
|
||
|
|
|
||
|
|
expect(paths).toEqual(["src/edit.ts"]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("returns blocking feedback when post-edit diagnostics contain errors", async () => {
|
||
|
|
const output = await runLspPostToolUseHook(
|
||
|
|
{
|
||
|
|
tool_name: "apply_patch",
|
||
|
|
tool_input: {
|
||
|
|
command: "*** Begin Patch\n*** Update File: src/broken.ts\n@@\n+missing();\n*** End Patch\n",
|
||
|
|
},
|
||
|
|
tool_response: "Success. Updated files.",
|
||
|
|
},
|
||
|
|
async (filePath) => {
|
||
|
|
expect(filePath).toBe("src/broken.ts");
|
||
|
|
return "error[typescript] (2304) at 1:1: Cannot find name 'missing'.";
|
||
|
|
},
|
||
|
|
);
|
||
|
|
|
||
|
|
expect(JSON.parse(output)).toEqual({
|
||
|
|
decision: "block",
|
||
|
|
hookSpecificOutput: {
|
||
|
|
hookEventName: "PostToolUse",
|
||
|
|
additionalContext:
|
||
|
|
"LSP diagnostics after editing src/broken.ts:\n" +
|
||
|
|
"error[typescript] (2304) at 1:1: Cannot find name 'missing'.",
|
||
|
|
},
|
||
|
|
reason:
|
||
|
|
"LSP diagnostics after editing src/broken.ts:\n" +
|
||
|
|
"error[typescript] (2304) at 1:1: Cannot find name 'missing'.",
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it("injects only files with diagnostics when multiple files are edited", async () => {
|
||
|
|
const checkedFilePaths: string[] = [];
|
||
|
|
const output = await runLspPostToolUseHook(
|
||
|
|
{
|
||
|
|
tool_name: "MultiEdit",
|
||
|
|
tool_input: {
|
||
|
|
file_paths: ["src/clean.ts", "README.md", "src/broken.ts", "src/broken.ts"],
|
||
|
|
},
|
||
|
|
tool_response: { ok: true },
|
||
|
|
},
|
||
|
|
async (filePath) => {
|
||
|
|
checkedFilePaths.push(filePath);
|
||
|
|
if (filePath === "src/broken.ts") {
|
||
|
|
return "error[typescript] (2322) at 1:7: Type 'number' is not assignable to type 'string'.";
|
||
|
|
}
|
||
|
|
if (filePath === "README.md") {
|
||
|
|
return "No LSP server configured for extension: .md";
|
||
|
|
}
|
||
|
|
return "No diagnostics found";
|
||
|
|
},
|
||
|
|
);
|
||
|
|
|
||
|
|
const expectedDiagnostics =
|
||
|
|
"LSP diagnostics after editing src/broken.ts:\n" +
|
||
|
|
"error[typescript] (2322) at 1:7: Type 'number' is not assignable to type 'string'.";
|
||
|
|
|
||
|
|
expect(checkedFilePaths).toEqual(["src/clean.ts", "README.md", "src/broken.ts"]);
|
||
|
|
expect(JSON.parse(output)).toEqual({
|
||
|
|
decision: "block",
|
||
|
|
hookSpecificOutput: {
|
||
|
|
hookEventName: "PostToolUse",
|
||
|
|
additionalContext: expectedDiagnostics,
|
||
|
|
},
|
||
|
|
reason: expectedDiagnostics,
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it("does not run diagnostics for failed mutation tool responses", async () => {
|
||
|
|
const output = await runLspPostToolUseHook(
|
||
|
|
{
|
||
|
|
tool_name: "apply_patch",
|
||
|
|
tool_input: {
|
||
|
|
command: "*** Begin Patch\n*** Update File: src/broken.ts\n@@\n+missing();\n*** End Patch\n",
|
||
|
|
},
|
||
|
|
tool_response: { isError: true },
|
||
|
|
},
|
||
|
|
async () => {
|
||
|
|
throw new Error("diagnostics should not run after failed mutations");
|
||
|
|
},
|
||
|
|
);
|
||
|
|
|
||
|
|
expect(output).toBe("");
|
||
|
|
});
|
||
|
|
|
||
|
|
it("is silent for clean diagnostics and unsupported extensions", async () => {
|
||
|
|
const output = await runLspPostToolUseHook(
|
||
|
|
{
|
||
|
|
tool_name: "apply_patch",
|
||
|
|
tool_input: {
|
||
|
|
command: "*** Begin Patch\n*** Update File: README.md\n@@\n+hello\n*** End Patch\n",
|
||
|
|
},
|
||
|
|
tool_response: "Success. Updated files.",
|
||
|
|
},
|
||
|
|
async () => "No LSP server configured for extension: .md",
|
||
|
|
);
|
||
|
|
|
||
|
|
expect(output).toBe("");
|
||
|
|
});
|
||
|
|
});
|