Skip to content

Commit 93be9ac

Browse files
committed
test(read_file): add coverage for structured FILE_NOT_FOUND handling
1 parent 011126a commit 93be9ac

File tree

1 file changed

+86
-0
lines changed

1 file changed

+86
-0
lines changed

src/core/tools/__tests__/readFileTool.spec.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1667,3 +1667,89 @@ describe("read_file tool with image support", () => {
16671667
})
16681668
})
16691669
})
1670+
1671+
// Additional coverage for unified FILE_NOT_FOUND handling
1672+
describe("read_file tool - FILE_NOT_FOUND unified error", () => {
1673+
it("emits structured FILE_NOT_FOUND and suppresses per-file XML for ENOENT", async () => {
1674+
// Reuse shared mocks/utilities from this spec file
1675+
const mockedCountFileLines = vi.mocked(countFileLines)
1676+
const mockedIsBinaryFile = vi.mocked(isBinaryFile)
1677+
const mockedPathResolve = vi.mocked(path.resolve)
1678+
const mockedExtractTextFromFile = vi.mocked(extractTextFromFile)
1679+
1680+
// Create a fresh cline
1681+
const { mockCline, mockProvider } = (global as any).createMockCline
1682+
? (global as any).createMockCline()
1683+
: (function () {
1684+
// fallback: if hoisted reference isn't globally accessible for some reason
1685+
const provider = { getState: vi.fn(), deref: vi.fn().mockReturnThis() }
1686+
const cline: any = {
1687+
cwd: "/",
1688+
task: "Test",
1689+
providerRef: provider,
1690+
rooIgnoreController: { validateAccess: vi.fn().mockReturnValue(true) },
1691+
say: vi.fn().mockResolvedValue(undefined),
1692+
ask: vi.fn().mockResolvedValue({ response: "yesButtonClicked" }),
1693+
presentAssistantMessage: vi.fn(),
1694+
handleError: vi.fn().mockResolvedValue(undefined),
1695+
pushToolResult: vi.fn(),
1696+
removeClosingTag: vi.fn((tag: string, content?: string) => content ?? ""),
1697+
fileContextTracker: { trackFileContext: vi.fn().mockResolvedValue(undefined) },
1698+
recordToolUsage: vi.fn().mockReturnValue(undefined),
1699+
recordToolError: vi.fn().mockReturnValue(undefined),
1700+
api: { getModel: vi.fn().mockReturnValue({ info: { supportsImages: false } }) },
1701+
}
1702+
return { mockCline: cline, mockProvider: provider }
1703+
})()
1704+
1705+
// Configure state to allow full read path
1706+
mockProvider.getState.mockResolvedValue({ maxReadFileLine: -1, maxImageFileSize: 20, maxTotalImageSize: 20 })
1707+
1708+
// Basic path/line setup
1709+
const relPath = "test/file.txt"
1710+
const absPath = "/test/file.txt"
1711+
mockedPathResolve.mockReturnValue(absPath)
1712+
mockedIsBinaryFile.mockResolvedValue(false)
1713+
mockedCountFileLines.mockResolvedValue(5)
1714+
1715+
// Cause ENOENT on actual read
1716+
const enoent = new Error("ENOENT: no such file or directory") as any
1717+
enoent.code = "ENOENT"
1718+
mockedExtractTextFromFile.mockRejectedValueOnce(enoent)
1719+
1720+
// Build tool_use call (single-file via args)
1721+
const argsContent = `<file><path>${relPath}</path></file>`
1722+
let pushed: ToolResponse | undefined
1723+
await readFileTool(
1724+
mockCline,
1725+
{
1726+
type: "tool_use",
1727+
name: "read_file",
1728+
params: { args: argsContent },
1729+
partial: false,
1730+
} as any,
1731+
mockCline.ask,
1732+
vi.fn(), // handleError (should not be called for ENOENT in unified mode)
1733+
(result: ToolResponse) => {
1734+
pushed = result
1735+
},
1736+
(_: ToolParamName, content?: string) => content ?? "",
1737+
)
1738+
1739+
// Assert unified error was emitted with structured payload
1740+
const sayCalls = mockCline.say.mock.calls
1741+
const errorCall = sayCalls.find((c: any[]) => c[0] === "error")
1742+
expect(errorCall).toBeDefined()
1743+
const payload = JSON.parse(errorCall?.[1] || "{}")
1744+
expect(payload.code).toBe("FILE_NOT_FOUND")
1745+
expect(Array.isArray(payload.filePaths)).toBe(true)
1746+
expect(payload.filePaths).toContain(relPath)
1747+
1748+
// Assert per-file XML error was suppressed for ENOENT (no <error> block for this file)
1749+
expect(typeof pushed).toBe("string")
1750+
const xml = String(pushed)
1751+
expect(xml).toContain("<files>")
1752+
// no per-file error tags for the not-found case
1753+
expect(xml).not.toMatch(/<error>.*File not found.*<\/error>/i)
1754+
})
1755+
})

0 commit comments

Comments
 (0)