import { test } from "node:test"; import assert from "node:assert/strict"; import { excerptResearch, RESEARCH_EXCERPT, expandQueries, researchSummary, diversityFollowups, gapQueries } from "../src/ai/research.js"; test("short research passes through unchanged", () => { const text = "## One\n\nSome notes under the cap."; assert.equal(excerptResearch(text), text); }); test("empty research stays empty", () => { assert.equal(excerptResearch(""), ""); assert.equal(excerptResearch(undefined), ""); }); test("oversized research is capped and ends on a line boundary", () => { const text = `## A\n\n${"word ".repeat(RESEARCH_EXCERPT)}tail-line\n`; const out = excerptResearch(text); assert.ok(out.length <= RESEARCH_EXCERPT); // The cap never slices mid-line. assert.ok(out.endsWith("\n")); }); test("the cap stays well under the author role's 32K context", () => { // ~3.5 chars per English token: 80K chars ≈ 23K tokens, leaving headroom for // the schema and output inside a 32768-token window. assert.ok(RESEARCH_EXCERPT < 100_000); }); test("expandQueries uses the model's angle queries, brief first", async () => { const chat = async () => ({ data: { queries: ["ray tracing pipeline", "ray tracing GPU hardware", "ray tracing movies", "ray tracing india", "ray tracing benchmark data", "ray tracing limitations"] } }); const out = await expandQueries("Ray tracing", { chat }); assert.equal(out[0], "Ray tracing"); assert.ok(out.length >= 5, "the angle expansion yields several queries"); assert.deepEqual(out.slice(1), ["ray tracing pipeline", "ray tracing GPU hardware", "ray tracing movies", "ray tracing india", "ray tracing benchmark data", "ray tracing limitations"]); }); test("expandQueries falls back to the brief alone when the model is unavailable", async () => { const chat = async () => { throw new Error("Ollama unreachable"); }; assert.deepEqual(await expandQueries("Ray tracing", { chat }), ["Ray tracing"]); }); test("expandQueries drops malformed expansions but keeps the brief", async () => { const chat = async () => ({ data: { queries: ["ok query", "x", 7] } }); assert.deepEqual(await expandQueries("Ray tracing", { chat }), ["Ray tracing", "ok query"]); }); test("diversityFollowups suggests queries when a pass is one-domain or has no academic voice", async () => { const oneDomain = await diversityFollowups("topic", [ { url: "https://a.example/x", title: "X" }, { url: "https://a.example/y", title: "Y" }, ]); assert.ok(oneDomain.length >= 1); assert.ok(oneDomain.some((q) => /different sources/i.test(q))); }); test("gapQueries returns examiner-gap searches from the model", async () => { const chat = async () => ({ data: { gaps: ["missing economic analysis", "missing comparison"] } }); const out = await gapQueries("topic", "notes", { chat }); assert.deepEqual(out, ["missing economic analysis", "missing comparison"]); }); test("gapQueries returns [] when the model is unavailable", async () => { const chat = async () => { throw new Error("down"); }; const out = await gapQueries("topic", "notes", { chat }); assert.deepEqual(out, []); }); test("researchSummary counts sources, domains, and academic vs listicle", () => { const sources = [ { url: "https://www.nature.com/articles/x", title: "Nature", words: 400 }, { url: "https://arxiv.org/abs/2301.00001", title: "arXiv", words: 300, kind: "paper" }, { url: "https://en.wikipedia.org/wiki/Electrolysis", title: "Wikipedia", words: 500 }, { url: "https://medium.com/posts/xyz", title: "Medium", words: 100 }, { url: "https://www.nature.com/articles/y", title: "Nature again", words: 200 }, ]; const s = researchSummary(sources, "some notes words here"); assert.equal(s.total, 5); // nature.com, arxiv.org, wikipedia.org, medium.com assert.equal(s.distinctDomains, 4); // arxiv (kind=paper) + wikipedia look academic; medium is listicle; nature.com is a plain .com. assert.equal(s.paperCount, 1); assert.equal(s.academicCount, 2); assert.equal(s.listicleCount, 1); assert.equal(s.notesWords, 4); }); test("researchSummary flags single-source domains", () => { const sources = [ { url: "https://a.example/page1", title: "A1" }, { url: "https://a.example/page2", title: "A2" }, { url: "https://b.example/only", title: "B" }, ]; const s = researchSummary(sources); assert.deepEqual(s.singleSourceDomains, ["b.example"]); assert.equal(s.distinctDomains, 2); });