{"version":3,"file":"research.test.d.ts","sourceRoot":"","sources":["../../../src/core/web-research/research.test.ts"],"names":[],"mappings":"","sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport { DeepResearchEngine } from \"./research.js\";\nimport { WebSearchProviderRegistry } from \"./search.js\";\nimport type {\n\tWebEvidenceRecord,\n\tWebFetchRequest,\n\tWebFetchResponse,\n\tWebSearchProvider,\n\tWebSearchResult,\n} from \"./types.js\";\n\nconst RESULTS: WebSearchResult[] = [\n\t{\n\t\ttitle: \"Vendor primary documentation\",\n\t\turl: \"https://docs.example.com/primary\",\n\t\tsnippet: \"The feature is enabled and supported.\",\n\t\tprovider: \"searxng\",\n\t\tengine: \"bing\",\n\t\trank: 1,\n\t},\n\t{\n\t\ttitle: \"Government assessment\",\n\t\turl: \"https://agency.gov/report\",\n\t\tsnippet: \"Independent assessment of the feature.\",\n\t\tprovider: \"searxng\",\n\t\tengine: \"brave\",\n\t\trank: 2,\n\t},\n\t{\n\t\ttitle: \"Contrary analysis\",\n\t\turl: \"https://analysis.example.net/report\",\n\t\tsnippet: \"The feature is not supported in old deployments.\",\n\t\tprovider: \"searxng\",\n\t\tengine: \"bing\",\n\t\trank: 3,\n\t},\n\t{\n\t\ttitle: \"Duplicate\",\n\t\turl: \"https://docs.example.com/primary#copy\",\n\t\tsnippet: \"duplicate\",\n\t\tprovider: \"searxng\",\n\t\trank: 4,\n\t},\n];\n\nfunction provider(): WebSearchProvider {\n\treturn {\n\t\tid: \"searxng\",\n\t\tcapabilities: { freshness: true, language: true, region: false, categories: true, safeSearch: true },\n\t\tsearch: async (request) => ({\n\t\t\tprovider: \"searxng\",\n\t\t\tquery: request.query,\n\t\t\tresults: RESULTS,\n\t\t\trawResultCount: RESULTS.length,\n\t\t\tdeduplicatedCount: 0,\n\t\t\tdurationMs: 1,\n\t\t}),\n\t\thealthCheck: async () => ({ provider: \"searxng\", status: \"healthy\" }),\n\t};\n}\n\nfunction evidence(url: string): WebEvidenceRecord {\n\tconst negative = url.includes(\"analysis\");\n\tconst content = negative\n\t\t? \"The deployed feature is not enabled and is never supported for old deployments according to the assessment.\"\n\t\t: \"The deployed feature is enabled and is supported for old deployments according to the assessment.\";\n\tconst id = negative ? \"web-negative\" : url.includes(\"agency\") ? \"web-agency\" : \"web-primary\";\n\treturn {\n\t\tevidenceId: id,\n\t\tsourceType: \"web\",\n\t\trequestedUrl: url,\n\t\tfinalUrl: url,\n\t\tcanonicalUrl: url,\n\t\ttitle: id,\n\t\tretrievedAt: \"2026-08-02T00:00:00.000Z\",\n\t\tcontentType: \"text/html\",\n\t\textractor: \"readability\",\n\t\tcontentSha256: id.padEnd(64, \"0\"),\n\t\tcompleteContentLocation: `session:tool-result:${id}`,\n\t\tcompleteContent: content,\n\t\trelevantPassages: [{ id: \"passage-1-1\", text: content, startLine: 1, endLine: 1 }],\n\t\toutboundLinks: [],\n\t\tbytesDownloaded: 100,\n\t\tbytesExtracted: content.length,\n\t\ttruncated: false,\n\t\tuntrusted: true,\n\t};\n}\n\nfunction fetcher(failUrl?: string): { fetch(request: WebFetchRequest): Promise<WebFetchResponse> } {\n\treturn {\n\t\tfetch: async (request) => {\n\t\t\tif (request.url === failUrl) throw new Error(\"fixture source failed\");\n\t\t\tconst record = evidence(request.url);\n\t\t\treturn { evidence: record, content: record.completeContent, rendered: false, durationMs: 1 };\n\t\t},\n\t};\n}\n\nfunction engine(failUrl?: string): DeepResearchEngine {\n\tconst fallback = { ...provider(), id: \"duckduckgo-lite\" as const };\n\treturn new DeepResearchEngine(new WebSearchProviderRegistry([provider(), fallback]), \"searxng\", fetcher(failUrl), {\n\t\tmaxQueries: 4,\n\t\tmaxSources: 3,\n\t\tmaxBytes: 10_000,\n\t\tmaxBrowserRenders: 0,\n\t\tmaxElapsedMs: 5000,\n\t\tmaxParallelFetches: 2,\n\t});\n}\n\ndescribe(\"bounded deep research\", () => {\n\tit(\"plans bounded distinct queries and collects parallel results in declared order\", async () => {\n\t\tconst result = await engine().run({\n\t\t\tobjective: \"Compare feature support and deployment behavior\",\n\t\t\tmaxQueries: 3,\n\t\t\tmaxSources: 3,\n\t\t});\n\t\texpect(result.queries.length).toBeGreaterThanOrEqual(2);\n\t\texpect(result.queries.length).toBeLessThanOrEqual(3);\n\t\texpect(new Set(result.queries).size).toBe(result.queries.length);\n\t\texpect(result.providerResponses.map((response) => response.query)).toEqual(result.queries);\n\t\texpect(result.events.map((event) => event.sequence)).toEqual(result.events.map((_event, index) => index + 1));\n\t});\n\n\tit(\"reserves query budget for a bounded follow-up when initial evidence is insufficient\", async () => {\n\t\tconst result = await engine(\"https://docs.example.com/primary\").run({\n\t\t\tobjective: \"Compare feature support and deployment behavior\",\n\t\t\tmaxQueries: 3,\n\t\t\tmaxSources: 1,\n\t\t});\n\t\texpect(result.queries).toHaveLength(3);\n\t\texpect(result.events.filter((event) => event.type === \"FOLLOWUP_QUERY_PLANNED\")).toHaveLength(1);\n\t\texpect(result.providerResponses.map((response) => response.query)).toEqual(result.queries);\n\t});\n\n\tit(\"deduplicates sources, prefers primary domains, and creates traceable claims\", async () => {\n\t\tconst result = await engine().run({\n\t\t\tobjective: \"Compare feature support and deployment behavior\",\n\t\t\tmaxQueries: 2,\n\t\t\tmaxSources: 3,\n\t\t\tpreferredDomains: [\"docs.example.com\"],\n\t\t});\n\t\texpect(result.bundle.evidence[0].canonicalUrl).toBe(\"https://docs.example.com/primary\");\n\t\texpect(new Set(result.bundle.evidence.map((item) => item.canonicalUrl)).size).toBe(result.bundle.evidence.length);\n\t\texpect(result.bundle.claims.every((claim) => claim.citations[0]?.evidenceId)).toBe(true);\n\t\texpect(result.bundle.claims[0].citations[0]).toMatchObject({\n\t\t\tclaimId: \"claim-1\",\n\t\t\tcontentSha256: expect.stringMatching(/^web-primary/),\n\t\t\tpassageId: \"passage-1-1\",\n\t\t});\n\t});\n\n\tit(\"detects potential contradictions and marks affected claims\", async () => {\n\t\tconst result = await engine().run({\n\t\t\tobjective: \"Compare feature support and deployment behavior\",\n\t\t\tmaxSources: 3,\n\t\t});\n\t\texpect(result.bundle.contradictions.length).toBeGreaterThan(0);\n\t\texpect(result.events.some((event) => event.type === \"CONTRADICTION_FOUND\")).toBe(true);\n\t\texpect(result.bundle.claims.some((claim) => claim.support === \"contradicted\")).toBe(true);\n\t});\n\n\tit(\"recovers partial evidence when one selected source fails\", async () => {\n\t\tconst result = await engine(\"https://agency.gov/report\").run({\n\t\t\tobjective: \"Compare feature support and deployment behavior\",\n\t\t\tmaxSources: 3,\n\t\t});\n\t\texpect(result.partial).toBe(true);\n\t\texpect(result.bundle.evidence.length).toBe(2);\n\t\texpect(result.events).toContainEqual(expect.objectContaining({ type: \"SOURCE_REJECTED\" }));\n\t});\n\n\tit(\"is deterministic for evidence identity and replay-relevant event ordering\", async () => {\n\t\tconst first = await engine().run({ objective: \"Compare feature support and deployment behavior\", maxQueries: 2 });\n\t\tconst second = await engine().run({\n\t\t\tobjective: \"Compare feature support and deployment behavior\",\n\t\t\tmaxQueries: 2,\n\t\t});\n\t\texpect(first.bundle.id).toBe(second.bundle.id);\n\t\texpect(first.bundle.contentSha256).toBe(second.bundle.contentSha256);\n\t\texpect(first.events.map((event) => event.type)).toEqual(second.events.map((event) => event.type));\n\t});\n\n\tit(\"honors cancellation before any provider work\", async () => {\n\t\tconst controller = new AbortController();\n\t\tcontroller.abort();\n\t\tawait expect(engine().run({ objective: \"research this\", signal: controller.signal })).rejects.toMatchObject({\n\t\t\tcode: \"ABORTED\",\n\t\t});\n\t});\n});\n"]}