8b44d3b7b5
Three hooks crashed with TypeError when MCP tools returned results where output.output is undefined. Added type guards to all affected hooks: - comment-checker/hook.ts: guard before toLowerCase() - edit-error-recovery/hook.ts: guard before toLowerCase() - task-resume-info/hook.ts: guard before startsWith()/includes()/trimEnd() - Added test for undefined output.output in edit-error-recovery Fixes #1746
138 lines
4.8 KiB
TypeScript
138 lines
4.8 KiB
TypeScript
import { describe, it, expect, beforeEach } from "bun:test"
|
|
import { createEditErrorRecoveryHook, EDIT_ERROR_REMINDER, EDIT_ERROR_PATTERNS } from "./index"
|
|
|
|
describe("createEditErrorRecoveryHook", () => {
|
|
let hook: ReturnType<typeof createEditErrorRecoveryHook>
|
|
|
|
beforeEach(() => {
|
|
hook = createEditErrorRecoveryHook({} as any)
|
|
})
|
|
|
|
describe("tool.execute.after", () => {
|
|
const createInput = (tool: string) => ({
|
|
tool,
|
|
sessionID: "test-session",
|
|
callID: "test-call-id",
|
|
})
|
|
|
|
const createOutput = (outputText: string) => ({
|
|
title: "Edit",
|
|
output: outputText,
|
|
metadata: {},
|
|
})
|
|
|
|
describe("#given output.output is undefined", () => {
|
|
//#when tool.execute.after is called
|
|
//#then should return without throwing
|
|
it("#then should not throw", async () => {
|
|
const input = createInput("Edit")
|
|
const output = { title: "Edit", output: undefined as unknown as string, metadata: {} }
|
|
|
|
await expect(hook["tool.execute.after"](input, output)).resolves.toBeUndefined()
|
|
})
|
|
})
|
|
|
|
describe("#given Edit tool with oldString/newString same error", () => {
|
|
describe("#when the error message is detected", () => {
|
|
it("#then should append the recovery reminder", async () => {
|
|
const input = createInput("Edit")
|
|
const output = createOutput("Error: oldString and newString must be different")
|
|
|
|
await hook["tool.execute.after"](input, output)
|
|
|
|
expect(output.output).toContain(EDIT_ERROR_REMINDER)
|
|
expect(output.output).toContain("oldString and newString must be different")
|
|
})
|
|
})
|
|
|
|
describe("#when the error appears without Error prefix", () => {
|
|
it("#then should still detect and append reminder", async () => {
|
|
const input = createInput("Edit")
|
|
const output = createOutput("oldString and newString must be different")
|
|
|
|
await hook["tool.execute.after"](input, output)
|
|
|
|
expect(output.output).toContain(EDIT_ERROR_REMINDER)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe("#given Edit tool with oldString not found error", () => {
|
|
describe("#when oldString not found in content", () => {
|
|
it("#then should append the recovery reminder", async () => {
|
|
const input = createInput("Edit")
|
|
const output = createOutput("Error: oldString not found in content")
|
|
|
|
await hook["tool.execute.after"](input, output)
|
|
|
|
expect(output.output).toContain(EDIT_ERROR_REMINDER)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe("#given Edit tool with multiple matches error", () => {
|
|
describe("#when oldString found multiple times", () => {
|
|
it("#then should append the recovery reminder", async () => {
|
|
const input = createInput("Edit")
|
|
const output = createOutput(
|
|
"Error: oldString found multiple times and requires more code context to uniquely identify the intended match"
|
|
)
|
|
|
|
await hook["tool.execute.after"](input, output)
|
|
|
|
expect(output.output).toContain(EDIT_ERROR_REMINDER)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe("#given non-Edit tool", () => {
|
|
describe("#when tool is not Edit", () => {
|
|
it("#then should not modify output", async () => {
|
|
const input = createInput("Read")
|
|
const originalOutput = "some output"
|
|
const output = createOutput(originalOutput)
|
|
|
|
await hook["tool.execute.after"](input, output)
|
|
|
|
expect(output.output).toBe(originalOutput)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe("#given Edit tool with successful output", () => {
|
|
describe("#when no error in output", () => {
|
|
it("#then should not modify output", async () => {
|
|
const input = createInput("Edit")
|
|
const originalOutput = "File edited successfully"
|
|
const output = createOutput(originalOutput)
|
|
|
|
await hook["tool.execute.after"](input, output)
|
|
|
|
expect(output.output).toBe(originalOutput)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe("#given case insensitive tool name", () => {
|
|
describe("#when tool is 'edit' lowercase", () => {
|
|
it("#then should still detect and append reminder", async () => {
|
|
const input = createInput("edit")
|
|
const output = createOutput("oldString and newString must be different")
|
|
|
|
await hook["tool.execute.after"](input, output)
|
|
|
|
expect(output.output).toContain(EDIT_ERROR_REMINDER)
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
describe("EDIT_ERROR_PATTERNS", () => {
|
|
it("#then should contain all known Edit error patterns", () => {
|
|
expect(EDIT_ERROR_PATTERNS).toContain("oldString and newString must be different")
|
|
expect(EDIT_ERROR_PATTERNS).toContain("oldString not found")
|
|
expect(EDIT_ERROR_PATTERNS).toContain("oldString found multiple times")
|
|
})
|
|
})
|
|
})
|