Skip to content

Commit 1dc79ce

Browse files
committed
test: enhance image memory limit checks and add new test for skipping images
1 parent d018c30 commit 1dc79ce

File tree

1 file changed

+60
-12
lines changed

1 file changed

+60
-12
lines changed

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

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,11 @@ describe("read_file tool XML output structure", () => {
489489
const mockedExtractTextFromFile = vi.mocked(extractTextFromFile)
490490
const mockedIsBinaryFile = vi.mocked(isBinaryFile)
491491
const mockedPathResolve = vi.mocked(path.resolve)
492+
const mockedFsReadFile = vi.mocked(fsPromises.readFile)
493+
const imageBuffer = Buffer.from(
494+
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==",
495+
"base64",
496+
)
492497

493498
let mockCline: any
494499
let mockProvider: any
@@ -679,6 +684,11 @@ describe("read_file tool XML output structure", () => {
679684
},
680685
(_: ToolParamName, content?: string) => content ?? "",
681686
)
687+
// In multi-image scenarios, the result is pushed to pushToolResult, not returned directly.
688+
// We need to check the mock's calls to get the result.
689+
if (mockCline.pushToolResult.mock.calls.length > 0) {
690+
return mockCline.pushToolResult.mock.calls[0][0]
691+
}
682692

683693
return localResult
684694
}
@@ -822,8 +832,8 @@ describe("read_file tool XML output structure", () => {
822832
expect(Array.isArray(result)).toBe(true)
823833
const parts = result as any[]
824834

825-
const textPart = parts.find((p) => p.type === "text")?.text
826-
const imageParts = parts.filter((p) => p.type === "image")
835+
const textPart = Array.isArray(result) ? result.find((p) => p.type === "text")?.text : result
836+
const imageParts = Array.isArray(result) ? result.filter((p) => p.type === "image") : []
827837

828838
expect(textPart).toBeDefined()
829839

@@ -836,9 +846,9 @@ describe("read_file tool XML output structure", () => {
836846
expect(imageParts).toHaveLength(4) // First 4 images should be included (~19.6MB total)
837847

838848
// Verify memory limit notice for the fifth image
839-
expect(textPart).toContain("Total image memory would exceed 20MB limit")
840-
expect(textPart).toContain("current: 19.6MB") // 4 * 4.9MB
841-
expect(textPart).toContain("this file: 4.9MB")
849+
expect(textPart).toContain(
850+
"Image skipped to avoid memory limit (20MB). Current: 19.6MB + this file: 4.9MB. Try fewer or smaller images.",
851+
)
842852
})
843853

844854
it("should track memory usage correctly across multiple images", async () => {
@@ -907,15 +917,13 @@ describe("read_file tool XML output structure", () => {
907917
const result = await executeReadMultipleImagesTool(exactLimitImages.map((img) => img.path))
908918

909919
// Verify
910-
expect(Array.isArray(result)).toBe(true)
911-
const parts = result as any[]
912-
913-
const textPart = parts.find((p) => p.type === "text")?.text
914-
const imageParts = parts.filter((p) => p.type === "image")
920+
const textPart = Array.isArray(result) ? result.find((p) => p.type === "text")?.text : result
921+
const imageParts = Array.isArray(result) ? result.filter((p) => p.type === "image") : []
915922

916923
expect(imageParts).toHaveLength(2) // First 2 images should fit
917-
expect(textPart).toContain("Total image memory would exceed 20MB limit")
918-
expect(textPart).toContain("current: 20.0MB") // Should show exactly 20MB used
924+
expect(textPart).toContain(
925+
"Image skipped to avoid memory limit (20MB). Current: 20.0MB + this file: 1.0MB. Try fewer or smaller images.",
926+
)
919927
})
920928

921929
it("should handle individual image size limit and total memory limit together", async () => {
@@ -1000,6 +1008,46 @@ describe("read_file tool XML output structure", () => {
10001008
expect(textPart).toContain("Image file is too large (6.0 MB). The maximum allowed size is 5 MB.")
10011009
})
10021010

1011+
it("should correctly calculate total memory and skip the last image", async () => {
1012+
// Setup
1013+
const testImages = [
1014+
{ path: "test/image1.png", sizeMB: 8 },
1015+
{ path: "test/image2.png", sizeMB: 8 },
1016+
{ path: "test/image3.png", sizeMB: 8 }, // This one should be skipped
1017+
]
1018+
1019+
mockProvider.getState.mockResolvedValue({
1020+
maxReadFileLine: -1,
1021+
maxImageFileSize: 10, // 10MB per image
1022+
maxTotalImageMemory: 20, // 20MB total
1023+
})
1024+
1025+
mockedIsBinaryFile.mockResolvedValue(true)
1026+
mockedCountFileLines.mockResolvedValue(0)
1027+
mockedFsReadFile.mockResolvedValue(imageBuffer)
1028+
1029+
fsPromises.stat.mockImplementation(async (filePath) => {
1030+
const file = testImages.find((f) => filePath.toString().includes(f.path))
1031+
if (file) {
1032+
return { size: file.sizeMB * 1024 * 1024 }
1033+
}
1034+
return { size: 1024 * 1024 } // Default 1MB
1035+
})
1036+
1037+
const imagePaths = testImages.map((img) => img.path)
1038+
const result = await executeReadMultipleImagesTool(imagePaths)
1039+
1040+
expect(Array.isArray(result)).toBe(true)
1041+
const parts = result as any[]
1042+
const textPart = parts.find((p) => p.type === "text")?.text
1043+
const imageParts = parts.filter((p) => p.type === "image")
1044+
1045+
expect(imageParts).toHaveLength(2) // First two images should be processed
1046+
expect(textPart).toContain("Image skipped to avoid memory limit (20MB)")
1047+
expect(textPart).toContain("Current: 16.0MB")
1048+
expect(textPart).toContain("this file: 8.0MB")
1049+
})
1050+
10031051
it("should reset total memory tracking for each tool invocation", async () => {
10041052
// Setup mocks (don't clear all mocks)
10051053

0 commit comments

Comments
 (0)