Skip to content

Commit b108a89

Browse files
committed
test(extraction): cover all branches and real document buffers
Reach exact per-file 100% coverage with meaningful decoder, path, workbook, and text-boundary tests; remove redundant conditions and align the backend coverage provider with Vitest. Add owned real PDF/DOCX fixtures with binary checkout attributes and real parser assertions. Load XLSX buffers directly through a precise dependency overload, preserving subarray boundaries and avoiding the stream concatenation copy. Remove four legacy lint suppressions.
1 parent 56d1869 commit b108a89

12 files changed

Lines changed: 406 additions & 36 deletions

‎pnpm-lock.yaml‎

Lines changed: 65 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/eslint-suppressions.json‎

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1184,11 +1184,6 @@
11841184
"count": 1
11851185
}
11861186
},
1187-
"integrations/misc/__tests__/extract-text.spec.ts": {
1188-
"@typescript-eslint/no-explicit-any": {
1189-
"count": 4
1190-
}
1191-
},
11921187
"integrations/misc/__tests__/line-counter.spec.ts": {
11931188
"@typescript-eslint/no-explicit-any": {
11941189
"count": 2
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import fs from "fs/promises"
2+
import path from "path"
3+
import { extractTextFromBuffer } from "../extract-text"
4+
5+
// Keep real parser fixtures separate from the mocked parser unit suite.
6+
describe("real document buffer decoding", () => {
7+
afterEach(() => vi.restoreAllMocks())
8+
9+
it("extracts numbered PDF text from fixture bytes without reopening the metadata pathname", async () => {
10+
const source = await fs.readFile(path.join(__dirname, "fixtures", "approved.pdf"))
11+
const access = vi.spyOn(fs, "access").mockRejectedValue(new Error("Unexpected pathname access"))
12+
const readFile = vi.spyOn(fs, "readFile").mockRejectedValue(new Error("Unexpected pathname read"))
13+
14+
// pdf-parse prefixes each page with two newlines, which retain their line numbers.
15+
expect(await extractTextFromBuffer(source, "metadata-only.PDF")).toBe(
16+
"1 | \n2 | \n3 | Approved PDF first line\n4 | Approved PDF second line\n",
17+
)
18+
expect(access).not.toHaveBeenCalled()
19+
expect(readFile).not.toHaveBeenCalled()
20+
})
21+
22+
it("extracts numbered DOCX paragraphs from fixture bytes without reopening the metadata pathname", async () => {
23+
const source = await fs.readFile(path.join(__dirname, "fixtures", "approved.docx"))
24+
const access = vi.spyOn(fs, "access").mockRejectedValue(new Error("Unexpected pathname access"))
25+
const readFile = vi.spyOn(fs, "readFile").mockRejectedValue(new Error("Unexpected pathname read"))
26+
27+
// Mammoth separates paragraphs with two newlines and decodes XML entities across runs.
28+
expect(await extractTextFromBuffer(source, "metadata-only.DOCX")).toBe(
29+
"1 | Approved DOCX first paragraph\n2 | \n3 | Approved DOCX second & final paragraph\n4 | \n",
30+
)
31+
expect(access).not.toHaveBeenCalled()
32+
expect(readFile).not.toHaveBeenCalled()
33+
})
34+
})

‎src/integrations/misc/__tests__/extract-text-from-xlsx.test.ts‎

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,47 @@
11
import ExcelJS from "exceljs"
2-
import { formatWorkbook } from "../extract-text-from-xlsx"
2+
import { extractTextFromXLSX, formatWorkbook } from "../extract-text-from-xlsx"
33
import { extractTextFromFile } from "../extract-text"
44

5+
describe("XLSX buffer decoding", () => {
6+
it("preserves empty columns in a populated row from real XLSX bytes", async () => {
7+
const workbook = new ExcelJS.Workbook()
8+
const worksheet = workbook.addWorksheet("Sparse")
9+
worksheet.getCell("A1").value = null
10+
worksheet.getCell("B1").value = "middle"
11+
worksheet.getCell("C1").value = undefined
12+
worksheet.getCell("D1").value = "end"
13+
14+
const expected = "--- Sheet: Sparse ---\n\tmiddle\t\tend"
15+
expect(formatWorkbook(workbook)).toBe(expected)
16+
const source = Buffer.from(await workbook.xlsx.writeBuffer())
17+
expect(await extractTextFromXLSX(source)).toBe(expected)
18+
})
19+
20+
it("decodes only the selected buffer view when another workbook follows in the backing buffer", async () => {
21+
const approved = new ExcelJS.Workbook()
22+
const worksheet = approved.addWorksheet("Selected")
23+
worksheet.getCell("A1").value = "approved cell"
24+
worksheet.getCell("B1").value = 42
25+
const approvedBytes = Buffer.from(await approved.xlsx.writeBuffer())
26+
27+
const unapproved = new ExcelJS.Workbook()
28+
unapproved.addWorksheet("Unapproved").getCell("A1").value = "outside selected bytes"
29+
const unapprovedBytes = Buffer.from(await unapproved.xlsx.writeBuffer())
30+
const prefix = Buffer.from("unapproved prefix")
31+
const backing = Buffer.concat([prefix, approvedBytes, unapprovedBytes])
32+
const source = backing.subarray(prefix.length, prefix.length + approvedBytes.length)
33+
34+
// Passing source.buffer would expose the later, unapproved ZIP archive to the decoder.
35+
expect(source.byteOffset).toBeGreaterThan(0)
36+
expect(source.byteLength).toBeLessThan(source.buffer.byteLength)
37+
expect(await extractTextFromXLSX(source)).toBe("--- Sheet: Selected ---\napproved cell\t42")
38+
})
39+
40+
it("rejects invalid workbook bytes instead of returning empty workbook text", async () => {
41+
await expect(extractTextFromXLSX(Buffer.from("not an XLSX archive"))).rejects.toThrow()
42+
})
43+
})
44+
545
describe("formatWorkbook", () => {
646
describe("basic functionality", () => {
747
it("should extract text with proper formatting", () => {
@@ -177,6 +217,19 @@ describe("formatWorkbook", () => {
177217
})
178218

179219
describe("edge cases", () => {
220+
it("includes row 50000, marks row 50001 as truncated, and still formats the next sheet", () => {
221+
const workbook = new ExcelJS.Workbook()
222+
const worksheet = workbook.addWorksheet("Large")
223+
worksheet.getCell("A50000").value = "last included row"
224+
worksheet.getCell("A50001").value = "excluded row content"
225+
workbook.addWorksheet("Next").getCell("A1").value = "next sheet content"
226+
227+
expect(formatWorkbook(workbook)).toBe(
228+
"--- Sheet: Large ---\nlast included row\n[... truncated at row 50001 ...]\n\n" +
229+
"--- Sheet: Next ---\nnext sheet content",
230+
)
231+
})
232+
180233
it("should handle empty workbook", () => {
181234
const workbook = new ExcelJS.Workbook()
182235
workbook.addWorksheet("Empty Sheet")

0 commit comments

Comments
 (0)