test / unit.test.ts
test / unit.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import { applyRules, classifySearchPage, layoutSample, resolveResultUrl } from "../src/parse";
import { ENGINES, RuleStore, checkSelectors } from "../src/rules";
import { ReplayGuard, seal, unseal } from "../src/protocol";
import { chunkText, focusPassages, outline } from "../src/focus";
import { ResearchTracker, annotate, sameTask } from "../src/research";
import { LONG_GUIDE } from "./fake-extension";
const FIX = path.join(process.cwd(), "test", "fixtures");
const fixture = (name: string) => fs.readFileSync(path.join(FIX, name), "utf8");
const page = (name: string, url: string) => {
const html = fixture(name);
return { url, title: "", html, text: html.replace(/<[^>]+>/g, " ") };
};
const DDG_URL = "https://html.duckduckgo.com/html/?q=open%20source%20software";
test("duckduckgo defaults extract organic results, unwrap redirects, skip ads", () => {
const def = ENGINES.duckduckgo;
const results = applyRules(fixture("ddg-results.html"), DDG_URL, def.defaults, def.domains);
assert.equal(results.length, 4);
assert.equal(results[0].url, "https://opensource.org/osd");
assert.match(results[0].title, /Open Source Definition/);
assert.ok(results[0].snippet.length > 40);
assert.ok(!results.some((r) => r.url.includes("ads.example.net")));
});
test("startpage and mojeek defaults extract results", () => {
for (const [engine, file, url] of [
["startpage", "startpage-results.html", "https://www.startpage.com/sp/search?query=x"],
["mojeek", "mojeek-results.html", "https://www.mojeek.com/search?q=x"],
] as const) {
const def = ENGINES[engine];
const results = applyRules(fixture(file), url, def.defaults, def.domains);
assert.equal(results.length, 4, engine);
assert.ok(results.every((r) => r.snippet.length > 40), engine);
assert.ok(!results.some((r) => r.url.includes("startpage.com")), engine);
}
});
test("classification: captcha, no results, parser failure with fallback links", () => {
const def = ENGINES.duckduckgo;
assert.equal(classifySearchPage(page("ddg-captcha.html", DDG_URL), def.defaults, def.domains).kind, "captcha");
assert.equal(classifySearchPage(page("ddg-noresults.html", DDG_URL), def.defaults, def.domains, def.noResults).kind, "no_results");
const failed = classifySearchPage(page("ddg-redesigned.html", DDG_URL), def.defaults, def.domains, def.noResults);
assert.equal(failed.kind, "parser_failed");
assert.equal(failed.results.length, 4);
assert.ok(!failed.results.some((r) => r.url.includes("duckduckgo.com")), "page chrome links are skipped");
});
test("layout sample points at the repeated result wrapper", () => {
const sample = layoutSample(fixture("ddg-redesigned.html"), DDG_URL, ENGINES.duckduckgo.domains);
assert.match(sample, /- article\.yQDlj3B5DI5YO8c8Ulio \(2 level/);
assert.doesNotMatch(sample, /- h2\./, "a heading that does not hold the description is not offered");
assert.match(sample, /one per result/);
});
test("a repaired rule set works on the redesigned page", () => {
const rules = { container: "article[data-testid=result]", title: "h2 a", link: "h2 a", snippet: "[data-result=snippet]" };
assert.deepEqual(checkSelectors(rules), []);
const results = applyRules(fixture("ddg-redesigned.html"), DDG_URL, rules, ENGINES.duckduckgo.domains);
assert.equal(results.length, 4);
});
test("url resolution rejects engine-internal and non-http links", () => {
const d = ["duckduckgo.com"];
assert.equal(resolveResultUrl("/settings", DDG_URL, d), null);
assert.equal(resolveResultUrl("javascript:alert(1)", DDG_URL, d), null);
assert.equal(resolveResultUrl("//duckduckgo.com/l/?uddg=https%3A%2F%2Fexample.org%2Fa%23frag", DDG_URL, d), "https://example.org/a");
});
test("selector checks reject bad input", () => {
assert.ok(checkSelectors({ container: "div[", title: "a", link: "a" }).length > 0);
assert.ok(checkSelectors({ container: "div", title: "a", link: "a", urlParam: "u d" }).length > 0);
assert.ok(checkSelectors({ container: "", title: "a", link: "a" } as never).length > 0);
});
test("rule store: seed, propose, approve, rollback, reset", () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "ss-rules-"));
const store = new RuleStore(dir);
assert.equal(store.get("duckduckgo").source, "default");
const repaired = { container: "article", title: "h2 a", link: "h2 a" };
store.setPending("duckduckgo", repaired, "test");
assert.deepEqual(store.listPending(), ["duckduckgo"]);
assert.match(store.approve("duckduckgo"), /Approved/);
assert.equal(store.get("duckduckgo").container, "article");
assert.equal(store.get("duckduckgo").source, "repaired");
assert.deepEqual(store.listPending(), []);
assert.match(store.rollback("duckduckgo"), /Rolled back/);
assert.equal(store.get("duckduckgo").container, ENGINES.duckduckgo.defaults.container);
store.apply("duckduckgo", repaired, "test");
assert.match(store.reset("duckduckgo"), /Reset/);
assert.equal(store.get("duckduckgo").source, "default");
assert.match(fs.readFileSync(path.join(dir, "changes.log"), "utf8"), /approved by user/);
fs.writeFileSync(path.join(dir, "rules", "mojeek.json"), "{ not json");
assert.equal(store.get("mojeek").source, "default", "corrupt files fall back to defaults");
});
test("signed messages: round trip, tampering, replay, staleness", () => {
const secret = "a".repeat(64);
const guard = new ReplayGuard();
const sealed = seal(secret, { type: "load", url: "https://example.org" });
const msg = unseal(secret, sealed, guard);
assert.equal(msg?.type, "load");
assert.equal(unseal(secret, sealed, guard), null, "replay rejected");
assert.equal(unseal("b".repeat(64), seal(secret, { type: "x" }), new ReplayGuard()), null, "wrong secret rejected");
const env = JSON.parse(seal(secret, { type: "x" }));
env.p = env.p.replace('"x"', '"y"');
assert.equal(unseal(secret, JSON.stringify(env), new ReplayGuard()), null, "tampering rejected");
const old = JSON.parse(seal(secret, { type: "x" }));
const payload = JSON.parse(old.p);
payload.ts -= 10 * 60_000;
const p = JSON.stringify(payload);
const { hmacHex } = require("../src/protocol");
assert.equal(unseal(secret, JSON.stringify({ p, s: hmacHex(secret, p) }), new ReplayGuard()), null, "stale rejected");
});
test("focused reading picks the passages that match the goal", () => {
const f = focusPassages(LONG_GUIDE, "Which license includes a patent grant?", 5000);
assert.ok(f.matched);
assert.ok(f.chars <= 6000 && f.chars < LONG_GUIDE.length / 2, "much shorter than the page");
const joined = f.passages.map((p) => p.text).join(" ");
assert.match(joined, /explicit patent grant/);
const positions = f.passages.map((p) => p.start);
assert.deepEqual(positions, [...positions].sort((a, b) => a - b), "passages stay in page order");
assert.equal(focusPassages(LONG_GUIDE, "zebra migration", 5000).matched, false);
assert.match(outline(LONG_GUIDE), /## Patent grants/);
assert.ok(chunkText(LONG_GUIDE).every((c) => c.text.length <= 1600));
});
test("one run of calls is one task: new goals become steps, a pause starts a new task", () => {
assert.ok(sameTask("latest LM Studio release notes", "LM Studio latest release"));
assert.ok(!sameTask("latest LM Studio release", "weather in Lisbon tomorrow"));
const t = new ResearchTracker(24000);
const first = t.resolve(undefined, "lm studio release");
assert.ok(first?.inferred);
const named = t.resolve("What changed in the latest LM Studio release?");
assert.equal(named, first, "naming the task keeps its counters");
assert.equal(named?.inferred, false);
const step = t.resolve("Find the default context length in the server args file");
assert.equal(step, first, "a narrower goal in the same run is a step, not a new task");
assert.equal(step?.goal, "What changed in the latest LM Studio release?");
assert.equal(step?.step, "Find the default context length in the server args file");
assert.equal(t.focusGoal(step), "Find the default context length in the server args file");
first!.lastAt = Date.now() - 100_000;
const later = t.resolve("Best hiking trails in the Alps");
assert.notEqual(later, first, "after a long pause, a new task starts");
assert.equal(later?.step, null);
});
test("budget lines and wrap-up instructions", () => {
const t = new ResearchTracker(2000);
const r = t.resolve("test goal")!;
const small = annotate("status: ok\nresults:\nnext: Read the results.", r, t, "search");
assert.match(small, /^status: ok\ntask: test goal\n/);
assert.match(small, /budget: 1 search\(es\) and 0 page read\(s\)/);
assert.match(small, /next: Read the results\./);
const big = annotate("status: ok\n" + "x".repeat(9000) + "\nnext: To continue reading, call read_webpage.", r, t, "page");
assert.match(big, /next: You have already gathered a lot/);
const huge = annotate("status: ok\n" + "x".repeat(9000) + "\nnext: To continue reading.", r, t, "page");
assert.match(huge, /research budget for this reply is used up/);
const captcha = annotate("status: captcha\nnext: Ask the user to solve it.", r, t, "search");
assert.match(captcha, /next: Ask the user to solve it\./, "captcha instructions are never overridden");
});
test("a spent budget is enforced, and renaming the task right away does not reset it", () => {
const logs: string[] = [];
const t = new ResearchTracker(2000, true, (m) => logs.push(m));
const r = t.resolve("first goal about licenses")!;
annotate("status: ok\n" + "x".repeat(16000) + "\nnext: more", r, t, "page");
assert.ok(t.isBlocked(r), "blocked after the stop instruction was delivered");
assert.match(t.refuse(r, "page"), /^status: budget_exhausted\ntask: first goal about licenses/);
const renamed = t.resolve("completely different wording about weather");
assert.equal(renamed, r, "a new goal in the same run is the same task");
assert.ok(t.isBlocked(renamed!));
r.lastAt = Date.now() - 100_000;
const fresh = t.resolve("completely different wording about weather");
assert.notEqual(fresh, r, "after a pause, a new goal is a new task");
assert.ok(!t.isBlocked(fresh));
assert.ok(logs.some((l) => /budget used up/.test(l)) && logs.every((l) => !/licenses|weather/.test(l)), "logs carry counts, never goals");
const lenient = new ResearchTracker(2000, false);
const r2 = lenient.resolve("goal")!;
annotate("status: ok\n" + "x".repeat(16000), r2, lenient, "page");
assert.ok(!lenient.isBlocked(r2), "enforcement can be switched off");
});
test("a quick new request gets a new budget; the same request keeps its own", () => {
const t = new ResearchTracker(2000);
const a = t.resolve("Research vLLM against our requirements")!;
annotate("status: ok\n" + "x".repeat(16000) + "\nnext: more", a, t, "page");
assert.ok(t.isBlocked(a));
a.lastAt = Date.now() - 10_000;
assert.equal(t.resolve("Research SGLang against our requirements"), a, "a new goal moments later is the same reply");
a.lastAt = Date.now() - 40_000;
assert.equal(t.resolve("Research vLLM against our requirements"), a, "the same goal after a pause, such as a compaction, is the same task");
a.lastAt = Date.now() - 40_000;
assert.equal(t.resolve("Research SGLang against our requirements"), a, "so does any goal already named in this run");
a.lastAt = Date.now() - 40_000;
const b = t.resolve("Research llama-server against our requirements")!;
assert.notEqual(b, a, "a different goal after the user had time to ask is a new task");
assert.ok(!t.isBlocked(b));
});
test("repeated refusals say plainly that every call will be refused", () => {
const t = new ResearchTracker(2000);
const r = t.resolve("goal")!;
annotate("status: ok\n" + "x".repeat(16000) + "\nnext: more", r, t, "page");
assert.match(t.refuse(r, "search"), /the user's next message gets a new budget/);
assert.match(t.refuse(r, "search"), /Refused again \(2 times now\)/);
});
import { test } from "node:test";
import assert from "node:assert/strict";
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import { applyRules, classifySearchPage, layoutSample, resolveResultUrl } from "../src/parse";
import { ENGINES, RuleStore, checkSelectors } from "../src/rules";
import { ReplayGuard, seal, unseal } from "../src/protocol";
import { chunkText, focusPassages, outline } from "../src/focus";
import { ResearchTracker, annotate, sameTask } from "../src/research";
import { LONG_GUIDE } from "./fake-extension";
const FIX = path.join(process.cwd(), "test", "fixtures");
const fixture = (name: string) => fs.readFileSync(path.join(FIX, name), "utf8");
const page = (name: string, url: string) => {
const html = fixture(name);
return { url, title: "", html, text: html.replace(/<[^>]+>/g, " ") };
};
const DDG_URL = "https://html.duckduckgo.com/html/?q=open%20source%20software";
test("duckduckgo defaults extract organic results, unwrap redirects, skip ads", () => {
const def = ENGINES.duckduckgo;
const results = applyRules(fixture("ddg-results.html"), DDG_URL, def.defaults, def.domains);
assert.equal(results.length, 4);
assert.equal(results[0].url, "https://opensource.org/osd");
assert.match(results[0].title, /Open Source Definition/);
assert.ok(results[0].snippet.length > 40);
assert.ok(!results.some((r) => r.url.includes("ads.example.net")));
});
test("startpage and mojeek defaults extract results", () => {
for (const [engine, file, url] of [
["startpage", "startpage-results.html", "https://www.startpage.com/sp/search?query=x"],
["mojeek", "mojeek-results.html", "https://www.mojeek.com/search?q=x"],
] as const) {
const def = ENGINES[engine];
const results = applyRules(fixture(file), url, def.defaults, def.domains);
assert.equal(results.length, 4, engine);
assert.ok(results.every((r) => r.snippet.length > 40), engine);
assert.ok(!results.some((r) => r.url.includes("startpage.com")), engine);
}
});
test("classification: captcha, no results, parser failure with fallback links", () => {
const def = ENGINES.duckduckgo;
assert.equal(classifySearchPage(page("ddg-captcha.html", DDG_URL), def.defaults, def.domains).kind, "captcha");
assert.equal(classifySearchPage(page("ddg-noresults.html", DDG_URL), def.defaults, def.domains, def.noResults).kind, "no_results");
const failed = classifySearchPage(page("ddg-redesigned.html", DDG_URL), def.defaults, def.domains, def.noResults);
assert.equal(failed.kind, "parser_failed");
assert.equal(failed.results.length, 4);
assert.ok(!failed.results.some((r) => r.url.includes("duckduckgo.com")), "page chrome links are skipped");
});
test("layout sample points at the repeated result wrapper", () => {
const sample = layoutSample(fixture("ddg-redesigned.html"), DDG_URL, ENGINES.duckduckgo.domains);
assert.match(sample, /- article\.yQDlj3B5DI5YO8c8Ulio \(2 level/);
assert.doesNotMatch(sample, /- h2\./, "a heading that does not hold the description is not offered");
assert.match(sample, /one per result/);
});
test("a repaired rule set works on the redesigned page", () => {
const rules = { container: "article[data-testid=result]", title: "h2 a", link: "h2 a", snippet: "[data-result=snippet]" };
assert.deepEqual(checkSelectors(rules), []);
const results = applyRules(fixture("ddg-redesigned.html"), DDG_URL, rules, ENGINES.duckduckgo.domains);
assert.equal(results.length, 4);
});
test("url resolution rejects engine-internal and non-http links", () => {
const d = ["duckduckgo.com"];
assert.equal(resolveResultUrl("/settings", DDG_URL, d), null);
assert.equal(resolveResultUrl("javascript:alert(1)", DDG_URL, d), null);
assert.equal(resolveResultUrl("//duckduckgo.com/l/?uddg=https%3A%2F%2Fexample.org%2Fa%23frag", DDG_URL, d), "https://example.org/a");
});
test("selector checks reject bad input", () => {
assert.ok(checkSelectors({ container: "div[", title: "a", link: "a" }).length > 0);
assert.ok(checkSelectors({ container: "div", title: "a", link: "a", urlParam: "u d" }).length > 0);
assert.ok(checkSelectors({ container: "", title: "a", link: "a" } as never).length > 0);
});
test("rule store: seed, propose, approve, rollback, reset", () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "ss-rules-"));
const store = new RuleStore(dir);
assert.equal(store.get("duckduckgo").source, "default");
const repaired = { container: "article", title: "h2 a", link: "h2 a" };
store.setPending("duckduckgo", repaired, "test");
assert.deepEqual(store.listPending(), ["duckduckgo"]);
assert.match(store.approve("duckduckgo"), /Approved/);
assert.equal(store.get("duckduckgo").container, "article");
assert.equal(store.get("duckduckgo").source, "repaired");
assert.deepEqual(store.listPending(), []);
assert.match(store.rollback("duckduckgo"), /Rolled back/);
assert.equal(store.get("duckduckgo").container, ENGINES.duckduckgo.defaults.container);
store.apply("duckduckgo", repaired, "test");
assert.match(store.reset("duckduckgo"), /Reset/);
assert.equal(store.get("duckduckgo").source, "default");
assert.match(fs.readFileSync(path.join(dir, "changes.log"), "utf8"), /approved by user/);
fs.writeFileSync(path.join(dir, "rules", "mojeek.json"), "{ not json");
assert.equal(store.get("mojeek").source, "default", "corrupt files fall back to defaults");
});
test("signed messages: round trip, tampering, replay, staleness", () => {
const secret = "a".repeat(64);
const guard = new ReplayGuard();
const sealed = seal(secret, { type: "load", url: "https://example.org" });
const msg = unseal(secret, sealed, guard);
assert.equal(msg?.type, "load");
assert.equal(unseal(secret, sealed, guard), null, "replay rejected");
assert.equal(unseal("b".repeat(64), seal(secret, { type: "x" }), new ReplayGuard()), null, "wrong secret rejected");
const env = JSON.parse(seal(secret, { type: "x" }));
env.p = env.p.replace('"x"', '"y"');
assert.equal(unseal(secret, JSON.stringify(env), new ReplayGuard()), null, "tampering rejected");
const old = JSON.parse(seal(secret, { type: "x" }));
const payload = JSON.parse(old.p);
payload.ts -= 10 * 60_000;
const p = JSON.stringify(payload);
const { hmacHex } = require("../src/protocol");
assert.equal(unseal(secret, JSON.stringify({ p, s: hmacHex(secret, p) }), new ReplayGuard()), null, "stale rejected");
});
test("focused reading picks the passages that match the goal", () => {
const f = focusPassages(LONG_GUIDE, "Which license includes a patent grant?", 5000);
assert.ok(f.matched);
assert.ok(f.chars <= 6000 && f.chars < LONG_GUIDE.length / 2, "much shorter than the page");
const joined = f.passages.map((p) => p.text).join(" ");
assert.match(joined, /explicit patent grant/);
const positions = f.passages.map((p) => p.start);
assert.deepEqual(positions, [...positions].sort((a, b) => a - b), "passages stay in page order");
assert.equal(focusPassages(LONG_GUIDE, "zebra migration", 5000).matched, false);
assert.match(outline(LONG_GUIDE), /## Patent grants/);
assert.ok(chunkText(LONG_GUIDE).every((c) => c.text.length <= 1600));
});
test("one run of calls is one task: new goals become steps, a pause starts a new task", () => {
assert.ok(sameTask("latest LM Studio release notes", "LM Studio latest release"));
assert.ok(!sameTask("latest LM Studio release", "weather in Lisbon tomorrow"));
const t = new ResearchTracker(24000);
const first = t.resolve(undefined, "lm studio release");
assert.ok(first?.inferred);
const named = t.resolve("What changed in the latest LM Studio release?");
assert.equal(named, first, "naming the task keeps its counters");
assert.equal(named?.inferred, false);
const step = t.resolve("Find the default context length in the server args file");
assert.equal(step, first, "a narrower goal in the same run is a step, not a new task");
assert.equal(step?.goal, "What changed in the latest LM Studio release?");
assert.equal(step?.step, "Find the default context length in the server args file");
assert.equal(t.focusGoal(step), "Find the default context length in the server args file");
first!.lastAt = Date.now() - 100_000;
const later = t.resolve("Best hiking trails in the Alps");
assert.notEqual(later, first, "after a long pause, a new task starts");
assert.equal(later?.step, null);
});
test("budget lines and wrap-up instructions", () => {
const t = new ResearchTracker(2000);
const r = t.resolve("test goal")!;
const small = annotate("status: ok\nresults:\nnext: Read the results.", r, t, "search");
assert.match(small, /^status: ok\ntask: test goal\n/);
assert.match(small, /budget: 1 search\(es\) and 0 page read\(s\)/);
assert.match(small, /next: Read the results\./);
const big = annotate("status: ok\n" + "x".repeat(9000) + "\nnext: To continue reading, call read_webpage.", r, t, "page");
assert.match(big, /next: You have already gathered a lot/);
const huge = annotate("status: ok\n" + "x".repeat(9000) + "\nnext: To continue reading.", r, t, "page");
assert.match(huge, /research budget for this reply is used up/);
const captcha = annotate("status: captcha\nnext: Ask the user to solve it.", r, t, "search");
assert.match(captcha, /next: Ask the user to solve it\./, "captcha instructions are never overridden");
});
test("a spent budget is enforced, and renaming the task right away does not reset it", () => {
const logs: string[] = [];
const t = new ResearchTracker(2000, true, (m) => logs.push(m));
const r = t.resolve("first goal about licenses")!;
annotate("status: ok\n" + "x".repeat(16000) + "\nnext: more", r, t, "page");
assert.ok(t.isBlocked(r), "blocked after the stop instruction was delivered");
assert.match(t.refuse(r, "page"), /^status: budget_exhausted\ntask: first goal about licenses/);
const renamed = t.resolve("completely different wording about weather");
assert.equal(renamed, r, "a new goal in the same run is the same task");
assert.ok(t.isBlocked(renamed!));
r.lastAt = Date.now() - 100_000;
const fresh = t.resolve("completely different wording about weather");
assert.notEqual(fresh, r, "after a pause, a new goal is a new task");
assert.ok(!t.isBlocked(fresh));
assert.ok(logs.some((l) => /budget used up/.test(l)) && logs.every((l) => !/licenses|weather/.test(l)), "logs carry counts, never goals");
const lenient = new ResearchTracker(2000, false);
const r2 = lenient.resolve("goal")!;
annotate("status: ok\n" + "x".repeat(16000), r2, lenient, "page");
assert.ok(!lenient.isBlocked(r2), "enforcement can be switched off");
});
test("a quick new request gets a new budget; the same request keeps its own", () => {
const t = new ResearchTracker(2000);
const a = t.resolve("Research vLLM against our requirements")!;
annotate("status: ok\n" + "x".repeat(16000) + "\nnext: more", a, t, "page");
assert.ok(t.isBlocked(a));
a.lastAt = Date.now() - 10_000;
assert.equal(t.resolve("Research SGLang against our requirements"), a, "a new goal moments later is the same reply");
a.lastAt = Date.now() - 40_000;
assert.equal(t.resolve("Research vLLM against our requirements"), a, "the same goal after a pause, such as a compaction, is the same task");
a.lastAt = Date.now() - 40_000;
assert.equal(t.resolve("Research SGLang against our requirements"), a, "so does any goal already named in this run");
a.lastAt = Date.now() - 40_000;
const b = t.resolve("Research llama-server against our requirements")!;
assert.notEqual(b, a, "a different goal after the user had time to ask is a new task");
assert.ok(!t.isBlocked(b));
});
test("repeated refusals say plainly that every call will be refused", () => {
const t = new ResearchTracker(2000);
const r = t.resolve("goal")!;
annotate("status: ok\n" + "x".repeat(16000) + "\nnext: more", r, t, "page");
assert.match(t.refuse(r, "search"), /the user's next message gets a new budget/);
assert.match(t.refuse(r, "search"), /Refused again \(2 times now\)/);
});