Skip to content
Closed
715 changes: 715 additions & 0 deletions src/core/tools/__tests__/contextValidator.test.ts

Large diffs are not rendered by default.

78 changes: 74 additions & 4 deletions src/core/tools/__tests__/readFileTool.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,19 @@ import * as path from "path"

import { countFileLines } from "../../../integrations/misc/line-counter"
import { readLines } from "../../../integrations/misc/read-lines"
import { extractTextFromFile } from "../../../integrations/misc/extract-text"
import { extractTextFromFile, addLineNumbers } from "../../../integrations/misc/extract-text"
import { parseSourceCodeDefinitionsForFile } from "../../../services/tree-sitter"
import { isBinaryFile } from "isbinaryfile"
import { ReadFileToolUse, ToolParamName, ToolResponse } from "../../../shared/tools"
import { readFileTool } from "../readFileTool"
import { formatResponse } from "../../prompts/responses"
import * as contextValidatorModule from "../contextValidator"
import { DEFAULT_MAX_IMAGE_FILE_SIZE_MB, DEFAULT_MAX_TOTAL_IMAGE_SIZE_MB } from "../helpers/imageHelpers"

vi.mock("../../../i18n", () => ({
t: vi.fn((key: string) => key),
}))

vi.mock("path", async () => {
const originalPath = await vi.importActual("path")
return {
Expand All @@ -27,6 +32,7 @@ vi.mock("isbinaryfile")

vi.mock("../../../integrations/misc/line-counter")
vi.mock("../../../integrations/misc/read-lines")
vi.mock("../contextValidator")

// Mock fs/promises readFile for image tests
const fsPromises = vi.hoisted(() => ({
Expand Down Expand Up @@ -263,6 +269,12 @@ describe("read_file tool with maxReadFileLine setting", () => {
mockedPathResolve.mockReturnValue(absoluteFilePath)
mockedIsBinaryFile.mockResolvedValue(false)

// Default mock for validateFileSizeForContext - no limit
vi.mocked(contextValidatorModule.validateFileSizeForContext).mockResolvedValue({
shouldLimit: false,
safeMaxLines: -1,
})

mockInputContent = fileContent

// Setup the extractTextFromFile mock implementation with the current mockInputContent
Expand Down Expand Up @@ -382,8 +394,7 @@ describe("read_file tool with maxReadFileLine setting", () => {
expect(result).toContain(`<list_code_definition_names>`)

// Verify XML structure
expect(result).toContain("<notice>Showing only 0 of 5 total lines")
expect(result).toContain("</notice>")
expect(result).toContain("<notice>tools:readFile.showingOnlyLines</notice>")
expect(result).toContain("<list_code_definition_names>")
expect(result).toContain(sourceCodeDef.trim())
expect(result).toContain("</list_code_definition_names>")
Expand All @@ -409,7 +420,7 @@ describe("read_file tool with maxReadFileLine setting", () => {
expect(result).toContain(`<file><path>${testFilePath}</path>`)
expect(result).toContain(`<content lines="1-3">`)
expect(result).toContain(`<list_code_definition_names>`)
expect(result).toContain("<notice>Showing only 3 of 5 total lines")
expect(result).toContain("<notice>tools:readFile.showingOnlyLines</notice>")
})
})

Expand Down Expand Up @@ -1326,6 +1337,65 @@ describe("read_file tool XML output structure", () => {
)
})
})

describe("line range instructions", () => {
beforeEach(() => {
// Reset mocks
vi.clearAllMocks()

// Mock file system functions
vi.mocked(isBinaryFile).mockResolvedValue(false)
vi.mocked(countFileLines).mockResolvedValue(10000) // Large file
vi.mocked(readLines).mockResolvedValue("line content")
vi.mocked(extractTextFromFile).mockResolvedValue("file content")

// Mock addLineNumbers
vi.mocked(addLineNumbers).mockImplementation((content, start) => `${start || 1} | ${content}`)
})

it("should always include inline line_range instructions when shouldLimit is true", async () => {
// Mock a large file
vi.mocked(countFileLines).mockResolvedValue(10000)

// Mock contextValidator to return shouldLimit true
vi.mocked(contextValidatorModule.validateFileSizeForContext).mockResolvedValue({
shouldLimit: true,
safeMaxLines: 2000,
reason: "File exceeds available context space",
})

// Mock readLines to return truncated content
vi.mocked(readLines).mockResolvedValue("Line 1\nLine 2\n...truncated...")

const result = await executeReadFileTool(
{ args: `<file><path>large-file.ts</path></file>` },
{ totalLines: 10000, maxReadFileLine: -1 },
)

// Verify the result contains the inline instructions
expect(result).toContain("<notice>")
expect(result).toContain("File exceeds available context space")
expect(result).toContain("tools:readFile.contextLimitInstructions</notice>")
})

it("should not show any special notice when file fits in context", async () => {
// Mock small file that fits in context
vi.mocked(countFileLines).mockResolvedValue(100)
vi.mocked(contextValidatorModule.validateFileSizeForContext).mockResolvedValue({
shouldLimit: false,
safeMaxLines: -1,
})

const result = await executeReadFileTool({ args: `<file><path>small-file.ts</path></file>` })

// Should have file content but no notice about limits
expect(result).toContain("<file>")
expect(result).toContain("<path>small-file.ts</path>")
expect(result).toContain("<content")
expect(result).not.toContain("Use line_range")
expect(result).not.toContain("File exceeds available context space")
})
})
})

describe("read_file tool with image support", () => {
Expand Down
Loading