Skip to content

Commit 96858e8

Browse files
committed
test: add comprehensive tests for JSON file truncation
- Added tests to verify JSON files are properly truncated when exceeding maxReadFileLine limit - Tests confirm that the truncation feature is working correctly for JSON files - Addresses concerns raised in issue #8149 about JSON file truncation
1 parent 87b45de commit 96858e8

File tree

1 file changed

+243
-0
lines changed

1 file changed

+243
-0
lines changed
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
// Test for JSON file truncation issue #8149
2+
// npx vitest run integrations/misc/__tests__/extract-text-json-truncation.spec.ts
3+
4+
import * as fs from "fs/promises"
5+
import { extractTextFromFile } from "../extract-text"
6+
import { countFileLines } from "../line-counter"
7+
import { readLines } from "../read-lines"
8+
import { isBinaryFile } from "isbinaryfile"
9+
10+
// Mock all dependencies
11+
vi.mock("fs/promises")
12+
vi.mock("../line-counter")
13+
vi.mock("../read-lines")
14+
vi.mock("isbinaryfile")
15+
16+
describe("extractTextFromFile - JSON File Truncation (Issue #8149)", () => {
17+
// Type the mocks
18+
const mockedFs = vi.mocked(fs)
19+
const mockedCountFileLines = vi.mocked(countFileLines)
20+
const mockedReadLines = vi.mocked(readLines)
21+
const mockedIsBinaryFile = vi.mocked(isBinaryFile)
22+
23+
beforeEach(() => {
24+
vi.clearAllMocks()
25+
// Set default mock behavior
26+
mockedFs.access.mockResolvedValue(undefined)
27+
mockedIsBinaryFile.mockResolvedValue(false)
28+
})
29+
30+
it("should truncate large JSON files that exceed maxReadFileLine limit", async () => {
31+
// Create a large JSON file content with 150 lines
32+
const largeJsonLines = [
33+
"{",
34+
' "name": "test-package",',
35+
' "version": "1.0.0",',
36+
' "description": "A test package with many dependencies",',
37+
' "dependencies": {',
38+
]
39+
40+
// Add 140 dependency lines to make it exceed 50 lines
41+
for (let i = 1; i <= 140; i++) {
42+
const comma = i < 140 ? "," : ""
43+
largeJsonLines.push(` "package-${i}": "^1.0.0"${comma}`)
44+
}
45+
46+
largeJsonLines.push(" },")
47+
largeJsonLines.push(' "devDependencies": {}')
48+
largeJsonLines.push("}")
49+
50+
const largeJsonContent = largeJsonLines.join("\n")
51+
52+
// Mock that the file has 148 lines total
53+
mockedCountFileLines.mockResolvedValue(148)
54+
55+
// Mock reading only the first 50 lines
56+
const first50Lines = largeJsonLines.slice(0, 50).join("\n")
57+
mockedReadLines.mockResolvedValue(first50Lines)
58+
59+
// Test with maxReadFileLine = 50 (as mentioned in the issue)
60+
const result = await extractTextFromFile("/test/large-package.json", 50)
61+
62+
// Should only include first 50 lines with line numbers
63+
expect(result).toContain(" 1 | {")
64+
expect(result).toContain(' 2 | "name": "test-package",')
65+
expect(result).toContain('50 | "package-45": "^1.0.0",')
66+
67+
// Should NOT include lines beyond 50
68+
expect(result).not.toContain("51 |")
69+
expect(result).not.toContain("package-46")
70+
71+
// Should include truncation message
72+
expect(result).toContain(
73+
"[File truncated: showing 50 of 148 total lines. The file is too large and may exhaust the context window if read in full.]",
74+
)
75+
76+
// Verify that readLines was called with correct parameters
77+
expect(mockedReadLines).toHaveBeenCalledWith("/test/large-package.json", 49, 0) // 0-indexed, so 49 for line 50
78+
})
79+
80+
it("should not truncate small JSON files within the maxReadFileLine limit", async () => {
81+
const smallJsonContent = JSON.stringify(
82+
{
83+
name: "small-package",
84+
version: "1.0.0",
85+
dependencies: {
86+
"package-1": "^1.0.0",
87+
"package-2": "^2.0.0",
88+
},
89+
},
90+
null,
91+
2,
92+
)
93+
94+
const lineCount = smallJsonContent.split("\n").length
95+
96+
mockedCountFileLines.mockResolvedValue(lineCount)
97+
mockedFs.readFile.mockResolvedValue(smallJsonContent as any)
98+
99+
const result = await extractTextFromFile("/test/small-package.json", 50)
100+
101+
// Should include all content with line numbers
102+
expect(result).toContain("1 | {")
103+
expect(result).toContain('"name": "small-package"')
104+
expect(result).toContain('"dependencies": {')
105+
106+
// Should NOT include truncation message
107+
expect(result).not.toContain("[File truncated:")
108+
109+
// Should use fs.readFile for small files, not readLines
110+
expect(mockedFs.readFile).toHaveBeenCalledWith("/test/small-package.json", "utf8")
111+
expect(mockedReadLines).not.toHaveBeenCalled()
112+
})
113+
114+
it("should handle very large JSON files (>2MB) with proper truncation", async () => {
115+
// Simulate a very large JSON file with thousands of lines
116+
const veryLargeJsonLines = ["{"]
117+
118+
// Create a JSON with 10,000 items
119+
for (let i = 1; i <= 10000; i++) {
120+
const comma = i < 10000 ? "," : ""
121+
veryLargeJsonLines.push(` "item_${i}": "value_${i}"${comma}`)
122+
}
123+
veryLargeJsonLines.push("}")
124+
125+
mockedCountFileLines.mockResolvedValue(10002) // 10,000 items + opening and closing braces
126+
127+
// Mock reading only the first 50 lines
128+
const first50Lines = veryLargeJsonLines.slice(0, 50).join("\n")
129+
mockedReadLines.mockResolvedValue(first50Lines)
130+
131+
const result = await extractTextFromFile("/test/very-large.json", 50)
132+
133+
// Should only include first 50 lines
134+
expect(result).toContain(" 1 | {")
135+
expect(result).toContain('50 | "item_49": "value_49",')
136+
137+
// Should NOT include lines beyond 50
138+
expect(result).not.toContain("51 |")
139+
expect(result).not.toContain("item_50")
140+
141+
// Should include truncation message with correct counts
142+
expect(result).toContain(
143+
"[File truncated: showing 50 of 10002 total lines. The file is too large and may exhaust the context window if read in full.]",
144+
)
145+
})
146+
147+
it("should handle JSON files with complex nested structures and truncate correctly", async () => {
148+
// Create a complex nested JSON structure
149+
const complexJsonLines = [
150+
"{",
151+
' "config": {',
152+
' "database": {',
153+
' "host": "localhost",',
154+
' "port": 5432,',
155+
' "credentials": {',
156+
' "username": "admin",',
157+
' "password": "secret"',
158+
" },",
159+
' "options": {',
160+
' "ssl": true,',
161+
' "poolSize": 10,',
162+
' "timeout": 30000,',
163+
' "retryAttempts": 3,',
164+
' "retryDelay": 1000',
165+
" }",
166+
" },",
167+
' "server": {',
168+
' "port": 3000,',
169+
' "host": "0.0.0.0",',
170+
' "middleware": [',
171+
]
172+
173+
// Add many middleware entries to exceed 50 lines
174+
for (let i = 1; i <= 50; i++) {
175+
const comma = i < 50 ? "," : ""
176+
complexJsonLines.push(` "middleware-${i}"${comma}`)
177+
}
178+
179+
complexJsonLines.push(" ],")
180+
complexJsonLines.push(' "routes": [')
181+
182+
// Add more routes
183+
for (let i = 1; i <= 30; i++) {
184+
const comma = i < 30 ? "," : ""
185+
complexJsonLines.push(` "/route-${i}"${comma}`)
186+
}
187+
188+
complexJsonLines.push(" ]")
189+
complexJsonLines.push(" }")
190+
complexJsonLines.push(" }")
191+
complexJsonLines.push("}")
192+
193+
const totalLines = complexJsonLines.length
194+
mockedCountFileLines.mockResolvedValue(totalLines)
195+
196+
// Mock reading only the first 50 lines
197+
const first50Lines = complexJsonLines.slice(0, 50).join("\n")
198+
mockedReadLines.mockResolvedValue(first50Lines)
199+
200+
const result = await extractTextFromFile("/test/complex-config.json", 50)
201+
202+
// Should truncate at line 50
203+
expect(result).toContain(" 1 | {")
204+
expect(result).toContain(' 2 | "config": {')
205+
expect(result).toContain('50 | "middleware-29",')
206+
207+
// Should NOT include lines beyond 50
208+
expect(result).not.toContain("51 |")
209+
expect(result).not.toContain("middleware-31")
210+
211+
// Should include truncation message
212+
expect(result).toContain(`[File truncated: showing 50 of ${totalLines} total lines`)
213+
})
214+
215+
it("should handle JSON files with exactly maxReadFileLine lines", async () => {
216+
// Create a JSON with exactly 50 lines
217+
const exactJsonLines = ["{"]
218+
219+
for (let i = 1; i <= 48; i++) {
220+
const comma = i < 48 ? "," : ""
221+
exactJsonLines.push(` "field_${i}": "value_${i}"${comma}`)
222+
}
223+
exactJsonLines.push("}")
224+
225+
const exactJsonContent = exactJsonLines.join("\n")
226+
227+
mockedCountFileLines.mockResolvedValue(50)
228+
mockedFs.readFile.mockResolvedValue(exactJsonContent as any)
229+
230+
const result = await extractTextFromFile("/test/exact-50-lines.json", 50)
231+
232+
// Should include all 50 lines
233+
expect(result).toContain(" 1 | {")
234+
expect(result).toContain("50 | }")
235+
236+
// Should NOT include truncation message since it's exactly at the limit
237+
expect(result).not.toContain("[File truncated:")
238+
239+
// Should use fs.readFile since it's within the limit
240+
expect(mockedFs.readFile).toHaveBeenCalledWith("/test/exact-50-lines.json", "utf8")
241+
expect(mockedReadLines).not.toHaveBeenCalled()
242+
})
243+
})

0 commit comments

Comments
 (0)