/** * Bridge registry error-path tests for document extractor and renderer plugins. * * These tests cover the observable behaviour of register/unregister/clear at * the TypeScript/Node layer. register_document_extractor and register_renderer * are generated by the alef trait-bridge codegen and present on the native * kreuzberg module at runtime; they do not appear in the curated index.d.ts * re-export because the TypeScript wrapper only surfaces a subset of the API. * * A DocumentExtractor bridge object must expose: * name(): string * extract_bytes(content, mimeType, configJson): string (JSON InternalDocument) * supported_mime_types(): string[] * * A Renderer bridge object must expose: * name(): string * render(docJson): string */ import { describe, it, expect } from "vitest"; import { extractBytesSync, listDocumentExtractors, listRenderers } from "kreuzberg"; // The register/unregister/clear functions are exported by the native module but // not re-typed in the public TypeScript wrapper. Import the native binding // directly so we can reach the full API surface without 'any' sprawl. import kreuzberg from "kreuzberg"; const native = kreuzberg as unknown as Record unknown>; function registerDocumentExtractor(obj: unknown): void { (native["registerDocumentExtractor"] as (o: unknown) => void)(obj); } function unregisterDocumentExtractor(name: string): void { (native["unregisterDocumentExtractor"] as (n: string) => void)(name); } function clearDocumentExtractors(): void { (native["clearDocumentExtractors"] as () => void)(); } function registerRenderer(obj: unknown): void { (native["registerRenderer"] as (o: unknown) => void)(obj); } function unregisterRenderer(name: string): void { (native["unregisterRenderer"] as (n: string) => void)(name); } function clearRenderers(): void { (native["clearRenderers"] as () => void)(); } // --------------------------------------------------------------------------- // Minimal stub factory helpers // --------------------------------------------------------------------------- function makeExtractor(name: string, mimeType = "application/x-test"): object { return { name: (): string => name, version: (): string => "0.0.1", initialize: (): void => { /* no-op */ }, shutdown: (): void => { /* no-op */ }, supported_mime_types: (): string[] => [mimeType], extract_bytes: (_content: Uint8Array, _mimeType: string, _configJson: string): string => JSON.stringify({ source_format: "plain", mime_type: "text/plain", elements: [], relationships: [], images: [], tables: [], }), }; } function makeRenderer(name: string): object { return { name: (): string => name, version: (): string => "0.0.1", initialize: (): void => { /* no-op */ }, shutdown: (): void => { /* no-op */ }, render: (_docJson: string): string => "rendered", }; } // --------------------------------------------------------------------------- // DocumentExtractor tests // --------------------------------------------------------------------------- describe("plugins: document extractor registry", () => { it("register_duplicate_extractor_replaces: second registration silently replaces first", () => { const name = "_test_ts_dup_extractor"; try { registerDocumentExtractor(makeExtractor(name, "application/x-ts-dup1")); registerDocumentExtractor(makeExtractor(name, "application/x-ts-dup2")); const listed = listDocumentExtractors(); const count = listed.filter((n) => n === name).length; expect(count).toBe(1); } finally { unregisterDocumentExtractor(name); } }); it("unregister_unknown_extractor_returns_ok: unregistering unknown name is a no-op", () => { // Must not throw expect(() => { unregisterDocumentExtractor("_test_ts_never_registered_extractor_xyz"); }).not.toThrow(); }); it("clear_then_list_extractor_empty: list is empty after clear", () => { registerDocumentExtractor(makeExtractor("_test_ts_clear_a", "application/x-ts-clear-a")); registerDocumentExtractor(makeExtractor("_test_ts_clear_b", "application/x-ts-clear-b")); clearDocumentExtractors(); const listed = listDocumentExtractors(); expect(listed).toEqual([]); }); it("extract_after_unregister_uses_builtin: built-in extractor is used after custom removed", () => { const name = "_test_ts_unreg_plain"; registerDocumentExtractor(makeExtractor(name, "text/plain")); unregisterDocumentExtractor(name); // Must not throw; falls back to the built-in plain-text extractor. const encoded = new TextEncoder().encode("hello world"); const result = extractBytesSync(encoded, "text/plain", undefined); expect(result).toBeDefined(); }); }); // --------------------------------------------------------------------------- // Renderer tests // --------------------------------------------------------------------------- describe("plugins: renderer registry", () => { it("register_duplicate_renderer_replaces: second registration silently replaces first", () => { const name = "_test_ts_dup_renderer"; try { registerRenderer(makeRenderer(name)); registerRenderer(makeRenderer(name)); const listed = listRenderers(); const count = listed.filter((n) => n === name).length; expect(count).toBe(1); } finally { unregisterRenderer(name); } }); it("unregister_unknown_renderer_returns_ok: unregistering unknown name is a no-op", () => { expect(() => { unregisterRenderer("_test_ts_never_registered_renderer_xyz"); }).not.toThrow(); }); it("clear_then_list_renderer_empty: list is empty after clear", () => { registerRenderer(makeRenderer("_test_ts_renderer_clear_a")); registerRenderer(makeRenderer("_test_ts_renderer_clear_b")); clearRenderers(); const listed = listRenderers(); expect(listed).toEqual([]); }); it("list_renderers_after_unregister_does_not_include_removed: name absent after unregister", () => { const name = "_test_ts_unregister_renderer_check"; registerRenderer(makeRenderer(name)); expect(listRenderers()).toContain(name); unregisterRenderer(name); expect(listRenderers()).not.toContain(name); }); });