Skip to content

Commit 976e74d

Browse files
committed
feat: add tests for feedback message formatting and XML special character handling in read_file tool
1 parent 4a676fb commit 976e74d

File tree

1 file changed

+112
-2
lines changed

1 file changed

+112
-2
lines changed

src/core/__tests__/read-file-xml.test.ts

Lines changed: 112 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import { isBinaryFile } from "isbinaryfile"
1010
import { ReadFileToolUse } from "../../shared/tools"
1111
import { formatResponse } from "../prompts/responses"
1212

13+
jest.mock("../prompts/responses")
14+
1315
// Mock dependencies
1416
jest.mock("../../integrations/misc/line-counter")
1517
jest.mock("../../integrations/misc/read-lines")
@@ -25,7 +27,7 @@ jest.mock("../../integrations/misc/extract-text", () => {
2527
}),
2628
addLineNumbers: jest.fn().mockImplementation((text: string, startLine = 1) => {
2729
if (!text) return ""
28-
const lines = text.split("\n")
30+
const lines = typeof text === "string" ? text.split("\n") : [text]
2931
return lines.map((line: string, i: number) => `${startLine + i} | ${line}`).join("\n")
3032
}),
3133
}
@@ -64,6 +66,9 @@ jest.mock("path", () => {
6466
})
6567

6668
describe("read_file tool XML output structure", () => {
69+
// Add new test data for feedback messages
70+
const feedbackMessage = "Test feedback message"
71+
const feedbackImages = ["image1.png", "image2.png"]
6772
// Test data
6873
const testFilePath = "test/file.txt"
6974
const absoluteFilePath = "/test/file.txt"
@@ -184,6 +189,57 @@ describe("read_file tool XML output structure", () => {
184189
}
185190

186191
describe("Basic XML Structure Tests", () => {
192+
it("should format feedback messages correctly in XML", async () => {
193+
// Setup
194+
mockCline.ask.mockResolvedValueOnce({
195+
response: "yesButtonClicked",
196+
text: feedbackMessage,
197+
images: feedbackImages,
198+
})
199+
mockedExtractTextFromFile.mockResolvedValue("1 | Test content")
200+
mockedCountFileLines.mockResolvedValue(1)
201+
;(formatResponse.toolApprovedWithFeedback as jest.Mock).mockReturnValue(
202+
`The tool was approved with feedback:\n<feedback>\n${feedbackMessage}\n</feedback>`,
203+
)
204+
;(formatResponse.toolResult as jest.Mock).mockReturnValue(
205+
`The tool was approved with feedback:\n<feedback>\n${feedbackMessage}\n</feedback>\n<files>\n<file><path>test/file.txt</path>\n<content lines="1-1">\n1 | Test content</content>\n</file>\n</files>`,
206+
)
207+
208+
// Execute
209+
const result = await executeReadFileTool()
210+
211+
// Verify feedback is properly formatted
212+
expect(result).toContain(
213+
`The tool was approved with feedback:\n<feedback>\n${feedbackMessage}\n</feedback>`,
214+
)
215+
expect(result).toContain('<content lines="1-1">\n1 | Test content</content>')
216+
})
217+
218+
it("should handle XML special characters in feedback", async () => {
219+
// Setup
220+
const feedbackWithSpecialChars = "Feedback with <tags> & symbols"
221+
mockCline.ask.mockResolvedValueOnce({
222+
response: "yesButtonClicked",
223+
text: feedbackWithSpecialChars,
224+
images: feedbackImages,
225+
})
226+
mockedExtractTextFromFile.mockResolvedValue("1 | Test content")
227+
mockedCountFileLines.mockResolvedValue(1)
228+
;(formatResponse.toolApprovedWithFeedback as jest.Mock).mockReturnValue(
229+
`The tool was approved with feedback:\n<feedback>\n${feedbackWithSpecialChars}\n</feedback>`,
230+
)
231+
;(formatResponse.toolResult as jest.Mock).mockReturnValue(
232+
`The tool was approved with feedback:\n<feedback>\n${feedbackWithSpecialChars}\n</feedback>`,
233+
)
234+
235+
// Execute
236+
const result = await executeReadFileTool()
237+
238+
// Verify special characters are preserved
239+
expect(result).toContain(
240+
`The tool was approved with feedback:\n<feedback>\n${feedbackWithSpecialChars}\n</feedback>`,
241+
)
242+
})
187243
it("should produce XML output with no unnecessary indentation", async () => {
188244
// Setup
189245
const numberedContent = "1 | Line 1\n2 | Line 2\n3 | Line 3\n4 | Line 4\n5 | Line 5"
@@ -426,6 +482,57 @@ describe("read_file tool XML output structure", () => {
426482
})
427483

428484
describe("Error Handling Tests", () => {
485+
it("should format status tags correctly", async () => {
486+
// Setup
487+
mockCline.ask.mockResolvedValueOnce({
488+
response: "noButtonClicked",
489+
text: "Access denied",
490+
})
491+
492+
// Execute
493+
const result = await executeReadFileTool({}, { validateAccess: true })
494+
495+
// Verify status tag format
496+
expect(result).toContain("<status>Denied by user</status>")
497+
expect(result).toMatch(/<file>.*<status>.*<\/status>.*<\/file>/s)
498+
})
499+
500+
it("should format multiple status types correctly", async () => {
501+
// Setup
502+
// Mock different status responses
503+
mockCline.ask
504+
.mockResolvedValueOnce({
505+
response: "yesButtonClicked",
506+
text: "First approved",
507+
})
508+
.mockResolvedValueOnce({
509+
response: "noButtonClicked",
510+
text: "Second denied",
511+
})
512+
513+
mockedExtractTextFromFile.mockImplementation((path) => {
514+
if (path.includes("file1.txt")) {
515+
return Promise.resolve("1 | Content 1")
516+
}
517+
throw new Error("Second file error")
518+
})
519+
;(formatResponse.toolDeniedWithFeedback as jest.Mock).mockReturnValue(
520+
`The user denied this operation and provided the following feedback:\n<feedback>\nSecond denied\n</feedback>`,
521+
)
522+
;(formatResponse.toolResult as jest.Mock).mockReturnValue(
523+
`The user denied this operation and provided the following feedback:\n<feedback>\nSecond denied\n</feedback>\n<files>\n<file><path>file1.txt</path><error>Error reading file: Second file error</error></file>\n<file><path>file2.txt</path><status>Denied by user</status></file>\n</files>`,
524+
)
525+
526+
// Execute
527+
const result = await executeReadFileTool({
528+
args: `<file><path>file1.txt</path></file><file><path>file2.txt</path></file>`,
529+
})
530+
531+
// Verify multiple status formats
532+
expect(result).toContain("The user denied this operation and provided the following feedback:")
533+
expect(result).toContain("<feedback>\nSecond denied\n</feedback>")
534+
expect(result).toMatch(/<file>.*<status>Denied by user<\/status>.*<\/file>/s) // Denied file
535+
})
429536
it("should include error tag for invalid path", async () => {
430537
// Setup - missing path parameter
431538
const toolUse: ReadFileToolUse = {
@@ -488,6 +595,9 @@ describe("read_file tool XML output structure", () => {
488595

489596
it("should include error tag for RooIgnore error", async () => {
490597
// Execute - skip addLineNumbers check as it returns early with an error
598+
;(formatResponse.rooIgnoreError as jest.Mock).mockReturnValue(
599+
`Access to ${testFilePath} is blocked by the .rooignore file settings. You must try to continue in the task without using this file, or ask the user to update the .rooignore file.`,
600+
)
491601
const result = await executeReadFileTool({}, { validateAccess: false })
492602

493603
// Verify
@@ -770,7 +880,7 @@ describe("read_file tool XML output structure", () => {
770880
// Setup
771881
const xmlContent = "<root><child>Test</child></root>"
772882
mockInputContent = xmlContent
773-
mockedExtractTextFromFile.mockResolvedValue(xmlContent)
883+
mockedExtractTextFromFile.mockResolvedValue(`1 | ${xmlContent}`)
774884

775885
// Execute
776886
const result = await executeReadFileTool()

0 commit comments

Comments
 (0)