From 63efda734c9c575cb5c69dfda626b7d643f3ade6 Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Tue, 25 Aug 2026 20:17:19 +0800 Subject: [PATCH] test(knowledge_fs): restore parser branch coverage --- api/knowledge-fs-contract.lock.json | 2 +- ...-08-25-expanded-document-upload-formats.md | 6 +- .../packages/parsers/src/parser.test.ts | 125 ++++++++++++++++++ 3 files changed, 130 insertions(+), 3 deletions(-) diff --git a/api/knowledge-fs-contract.lock.json b/api/knowledge-fs-contract.lock.json index 511cb25c52d..bf3365307c4 100644 --- a/api/knowledge-fs-contract.lock.json +++ b/api/knowledge-fs-contract.lock.json @@ -1,6 +1,6 @@ { "schemaVersion": 5, - "subtreeTree": "3915987c133895325c3953f994c5e81ac7aafdfe", + "subtreeTree": "37121cd70481e76320d1f67060a3830c81450fdd", "openapiSha256": "2cf348c68bbe65dd51bbde9a0a4f91398beeebd79e89e9288c9386b26ae09796", "capabilityV2AuthManifestSha256": "fc0a47e23cce12544882f0298522b4933002e892b84ce1815df7e81d36a7a0c7", "capabilityV2AuthTestVectorSha256": "ae0de37b1ff05c40f905cf17a7b410d8971acacf64db07d5ee3d6fecfa559ce3", diff --git a/knowledge-fs/.harness/changes/2026-08-25-expanded-document-upload-formats.md b/knowledge-fs/.harness/changes/2026-08-25-expanded-document-upload-formats.md index 09c7560f9ae..6eca6272e41 100644 --- a/knowledge-fs/.harness/changes/2026-08-25-expanded-document-upload-formats.md +++ b/knowledge-fs/.harness/changes/2026-08-25-expanded-document-upload-formats.md @@ -26,6 +26,8 @@ - Added behavior tests for declared MIME types, octet-stream inference, native lightweight-text routing, and the browser file-picker contract. The new tests were observed failing before the implementation and passing afterward. +- Added deadline and reasonless-abort coverage for the bounded Unstructured request lifecycle after + the CI branch-coverage gate exposed the remaining paths. ## Why @@ -37,8 +39,8 @@ parser restores compatibility without adding a new parser, storage path, or netw - `pnpm --filter @knowledge/api exec vitest run src/document-upload-utils.test.ts` — passed (21 tests). - `pnpm --filter @knowledge/parsers exec vitest run src/parser.test.ts` — passed (55 tests). -- `pnpm --filter @knowledge/parsers test:coverage` — passed with 95.69% statements/lines, - 90.02% branches, and 97.52% functions. +- `pnpm --filter @knowledge/parsers test:coverage` — passed with 95.99% statements/lines, + 90.10% branches, and 98.34% functions (62 tests). - `pnpm --filter @knowledge/api-app exec vitest run src/parser-options.test.ts` — passed (5 tests). - `vp test run --project unit features/new-rag/__tests__/documents-page.spec.tsx` — passed (203 tests). - KnowledgeFS typechecks — passed; the full Turbo test pipeline passed (22 tasks), including the API diff --git a/knowledge-fs/packages/parsers/src/parser.test.ts b/knowledge-fs/packages/parsers/src/parser.test.ts index 9aa7b1a4760..47cc1ac28ad 100644 --- a/knowledge-fs/packages/parsers/src/parser.test.ts +++ b/knowledge-fs/packages/parsers/src/parser.test.ts @@ -1819,6 +1819,35 @@ describe("parser adapters", () => { expect(sleepCalls).toBe(0); }); + it.each([0, 1])( + "uses the bounded default delay before retrying a transient provider failure (%d ms)", + async (retryDelayMs) => { + let fetchCalls = 0; + const parser = createUnstructuredParserClient({ + endpoint: "https://unstructured.example.test", + fetch: async () => { + fetchCalls += 1; + return new Response(JSON.stringify([{ text: "Recovered", type: "NarrativeText" }]), { + status: fetchCalls === 1 ? 500 : 200, + }); + }, + maxRetries: 1, + retryDelayMs, + }); + + await expect( + parser.parse({ + body: new Uint8Array([1]), + documentAssetId, + filename: "delayed-retry.pdf", + mimeType: "application/pdf", + version: 1, + }), + ).resolves.toMatchObject({ parser: "unstructured" }); + expect(fetchCalls).toBe(2); + }, + ); + it("preserves caller cancellation while an Unstructured request is active", async () => { const controller = new AbortController(); let fetchStarted = false; @@ -1849,6 +1878,102 @@ describe("parser adapters", () => { await expect(pending).rejects.toMatchObject({ name: "AbortError" }); }); + it("rejects an Unstructured request whose caller signal is already aborted", async () => { + const controller = new AbortController(); + controller.abort(); + const parser = createUnstructuredParserClient({ + endpoint: "https://unstructured.example.test", + fetch: async () => new Response("[]"), + }); + + await expect( + parser.parse({ + body: new Uint8Array([1]), + documentAssetId, + filename: "already-aborted.pdf", + mimeType: "application/pdf", + signal: controller.signal, + version: 1, + }), + ).rejects.toMatchObject({ name: "AbortError" }); + }); + + it("aborts an active Unstructured request when its deadline expires", async () => { + const parser = createUnstructuredParserClient({ + endpoint: "https://unstructured.example.test", + fetch: async (input) => { + const request = input instanceof Request ? input : new Request(input); + return await new Promise((_resolve, reject) => { + request.signal.addEventListener("abort", () => reject(request.signal.reason), { + once: true, + }); + }); + }, + requestTimeoutMs: 1, + }); + + await expect( + parser.parse({ + body: new Uint8Array([1]), + documentAssetId, + filename: "timeout.pdf", + mimeType: "application/pdf", + version: 1, + }), + ).rejects.toThrow("Unstructured parser request timed out after requestTimeoutMs=1"); + }); + + it("rejects a successful Unstructured response completed after its deadline", async () => { + const parser = createUnstructuredParserClient({ + endpoint: "https://unstructured.example.test", + fetch: async () => { + await new Promise((resolve) => setTimeout(resolve, 5)); + return new Response(JSON.stringify([{ text: "Too late", type: "NarrativeText" }])); + }, + requestTimeoutMs: 1, + }); + + await expect( + parser.parse({ + body: new Uint8Array([1]), + documentAssetId, + filename: "late-success.pdf", + mimeType: "application/pdf", + version: 1, + }), + ).rejects.toThrow("Unstructured parser request timed out after requestTimeoutMs=1"); + }); + + it("uses the standard AbortError when an abort event has no signal reason", async () => { + const controller = new AbortController(); + let fetchStarted = false; + const parser = createUnstructuredParserClient({ + endpoint: "https://unstructured.example.test", + fetch: async (input) => { + const request = input instanceof Request ? input : new Request(input); + fetchStarted = true; + return await new Promise((_resolve, reject) => { + request.signal.addEventListener("abort", () => reject(request.signal.reason), { + once: true, + }); + }); + }, + }); + const pending = parser.parse({ + body: new Uint8Array([1]), + documentAssetId, + filename: "abort-event.pdf", + mimeType: "application/pdf", + signal: controller.signal, + version: 1, + }); + + await waitForCondition(() => fetchStarted); + controller.signal.dispatchEvent(new Event("abort")); + + await expect(pending).rejects.toMatchObject({ name: "AbortError" }); + }); + it("validates Unstructured retry and resource bounds", () => { const base = { endpoint: "https://unstructured.example.test",