import { describe, expect, it } from "vitest"; import { diffJson, diffList, diffText } from "./diff"; import { classifyChange, classifyLine, extractFieldChanges, isCosmeticPair, isTimestampPair } from "./semantic"; import { parseSearch, stringifySearch } from "./search"; import { breakingState, computeImpact, computeSignalScore, dailyAnomaly, velocityScore } from "./scoring"; import { evaluateChange } from "./heuristics"; // ---- Source simulator fixtures (spec §89) -------------------------------------------------------- const PRICING_BEFORE = `API Pricing\nInput tokens: $10.00 / million tokens\nOutput tokens: $30.00 / million tokens\nContext window: 128k tokens\nStatus: beta\n© 2025 Example AI. All rights reserved.`; const PRICING_AFTER = `API Pricing\nInput tokens: $8.00 / million tokens\nOutput tokens: $24.00 / million tokens\nContext window: 256k tokens\nStatus: general availability\n© 2026 Example AI. All rights reserved.`; describe("semantic line classification", () => { it("recognizes noise lines", () => { expect(classifyLine("© 2026 Example Inc. All rights reserved.")).toBe("boilerplate"); expect(classifyLine("2026")).toBe("boilerplate"); expect(classifyLine("Updated 3 minutes ago")).toBe("timestamp"); expect(classifyLine("2026-09-11T04:12:00Z")).toBe("timestamp"); expect(classifyLine("5 min read")).toBe("timestamp"); expect(classifyLine("1,204 views")).toBe("timestamp"); expect(classifyLine("Skip to main content")).toBe("navigation"); expect(classifyLine("Home › Products › Pricing")).toBe("navigation"); expect(classifyLine("Subscribe now to our newsletter and save 20%")).toBe("advertisement"); expect(classifyLine("We use cookies to improve your experience. Accept all")).toBe("boilerplate"); }); it("keeps real content", () => { expect(classifyLine("Input tokens: $8.00 / million tokens")).toBe("content"); expect(classifyLine("The company appointed Jane Doe as Chief Financial Officer effective October 1.")).toBe("content"); }); it("detects cosmetic and timestamp-only pairs", () => { expect(isCosmeticPair("Hello, World!", "hello world")).toBe(true); expect(isCosmeticPair("Price: $10", "Price: $12")).toBe(false); expect(isTimestampPair("Last updated 2026-09-10 12:00 UTC", "Last updated 2026-09-11 08:30 UTC")).toBe(true); expect(isTimestampPair("Rate: 4.25%", "Rate: 4.00%")).toBe(false); }); }); describe("classifyChange", () => { it("classifies a footer-year bump as boilerplate noise", () => { const d = diffText("Welcome\n© 2025 Acme", "Welcome\n© 2026 Acme"); const r = classifyChange(d, { url: "https://acme.com/", sensorType: "HTML" }); expect(["boilerplate", "cosmetic"]).toContain(r.class); expect(r.contentLines).toBe(0); }); it("classifies a relative-time bump as timestamp noise", () => { const d = diffText("Status\nUpdated 2 minutes ago\nAll systems operational", "Status\nUpdated 7 minutes ago\nAll systems operational"); const r = classifyChange(d, { url: "https://status.acme.com/", sensorType: "HTML" }); expect(r.class).toBe("timestamp"); }); it("classifies a pricing change as pricing and extracts fields with deltas", () => { const d = diffText(PRICING_BEFORE, PRICING_AFTER); const r = classifyChange(d, { url: "https://example-ai.com/pricing", sensorType: "HTML", title: "API Pricing" }); expect(r.class).toBe("pricing"); const input = r.fieldChanges.find((f) => f.kind === "price" && f.before?.includes("10.00")); expect(input).toBeTruthy(); expect(input!.after).toContain("8.00"); expect(input!.deltaPct).toBe(-20); const ctx = r.fieldChanges.find((f) => f.label.toLowerCase().includes("context")); expect(ctx?.before).toContain("128k"); expect(ctx?.after).toContain("256k"); const status = r.fieldChanges.find((f) => f.kind === "status"); expect(status?.before).toBe("beta"); expect(status?.after).toBe("general availability"); }); it("classifies a leadership change as personnel", () => { const d = diffText("Leadership\nCEO: John Smith\nCFO: Ana Ruiz", "Leadership\nCEO: Maria Chen\nCFO: Ana Ruiz"); const r = classifyChange(d, { url: "https://acme.com/about/leadership", sensorType: "HTML" }); expect(r.class).toBe("personnel"); expect(r.fieldChanges.some((f) => f.label === "CEO" && f.before === "John Smith" && f.after === "Maria Chen")).toBe(true); }); it("treats a redesign burst of short fragments as navigation churn", () => { const before = Array.from({ length: 30 }, (_, i) => `Item ${i}`).join("\n"); const after = Array.from({ length: 30 }, (_, i) => `Nav ${i}`).join("\n"); const r = classifyChange(diffText(before, after), { url: "https://acme.com/", sensorType: "HTML" }); expect(r.reasons.some((x) => /template|navigation/.test(x))).toBe(true); }); it("classifies JSON volatile paths as timestamp noise and real fields as meaningful", () => { const noise = classifyChange(diffJson({ updated_at: 1, a: 1 }, { updated_at: 2, a: 1 }), { url: "https://api.acme.com/x", sensorType: "JSON" }); expect(noise.class).toBe("timestamp"); const real = classifyChange(diffJson({ limit: 100, status: "beta" }, { limit: 200, status: "ga" }), { url: "https://api.acme.com/x", sensorType: "JSON" }); expect(["meaningful", "product"]).toContain(real.class); expect(real.fieldChanges.find((f) => f.label.includes("limit"))?.deltaPct).toBe(100); }); it("treats new feed items as meaningful", () => { const d = diffList([{ key: "a", title: "Old post" }], [{ key: "a", title: "Old post" }, { key: "b", title: "Acme launches Widget 2", summary: "Today we introduce…" }]); const r = classifyChange(d, { url: "https://acme.com/feed.xml", sensorType: "RSS" }); expect(["meaningful", "product"]).toContain(r.class); }); }); describe("heuristics: new event classes", () => { const ctx = { sensorType: "RSS", url: "https://vendor.example/advisories.xml", sourceCategories: ["cyber"] }; it("detects active exploitation from KEV-like records", () => { const d = diffList([], [{ key: "CVE-2026-1234", title: "Acme Router Command Injection", cveID: "CVE-2026-1234", dateAdded: "2026-09-11", knownRansomwareCampaignUse: "Known" }]); const h = evaluateChange(d, { ...ctx, url: "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json", sensorType: "REST_API" }); expect(h.eventType).toBe("active_exploitation"); }); it("detects zero-day wording", () => { const d = diffList([], [{ key: "x", title: "Zero-day in WidgetOS exploited in the wild, no patch available", summary: "…" }]); expect(["zero_day", "active_exploitation"]).toContain(evaluateChange(d, ctx).eventType); }); it("detects supply-chain compromise", () => { const d = diffList([], [{ key: "x", title: "Malicious npm package typosquatting popular library steals tokens", summary: "supply-chain attack" }]); expect(evaluateChange(d, ctx).eventType).toBe("supply_chain_attack"); }); it("detects guidance and buyback in finance feeds", () => { const fin = { ...ctx, sourceCategories: ["finance"], url: "https://ir.example.com/rss" }; expect(evaluateChange(diffList([], [{ key: "1", title: "Acme raises full-year guidance after strong quarter" }]), fin).eventType).toBe("guidance"); expect(evaluateChange(diffList([], [{ key: "2", title: "Acme announces $5 billion share repurchase program" }]), fin).eventType).toBe("buyback"); }); it("detects emergency notices and outbreaks", () => { const gov = { ...ctx, sourceCategories: ["government"], url: "https://gov.example/news.rss" }; expect(evaluateChange(diffList([], [{ key: "1", title: "State of emergency declared as wildfire evacuation order expands" }]), gov).eventType).toBe("emergency_notice"); expect(evaluateChange(diffList([], [{ key: "2", title: "Measles outbreak: 42 cases confirmed in three regions" }]), { ...gov, sourceCategories: ["health"] }).eventType).toBe("outbreak"); }); it("keeps sports results in their own class", () => { const sp = { ...ctx, sourceCategories: ["sports"], url: "https://league.example/news.rss" }; expect(["sports_result", "sports_transaction", "announcement"]).toContain(evaluateChange(diffList([], [{ key: "1", title: "Final score: Montréal defeats Toronto 4-2 in overtime" }]), sp).eventType); }); }); describe("search syntax", () => { it("parses keys, quotes, relative dates and free text", () => { const p = parseSearch('openai pricing entity:openai type:pricing_change,terms_change after:7d silent:true importance:>70 source:"bank of canada" country:ca lang:FR is:confirmed'); expect(p.text).toBe("openai pricing"); expect(p.filters.entity).toBe("org_openai"); expect(p.filters.event_type).toBe("pricing_change,terms_change"); expect(p.filters.silent_change).toBe(true); expect(p.filters.importance_min).toBe(70); expect(p.filters.source).toBe("bank-of-canada"); expect(p.filters.country).toBe("CA"); expect(p.filters.language).toBe("fr"); expect(p.filters.confirmed).toBe(true); expect(new Date(p.filters.after!).getTime()).toBeLessThan(Date.now()); }); it("round-trips through stringifySearch", () => { const s = stringifySearch({ text: "gpu", filters: { entity: "org_nvidia", importance_min: 60, silent_change: false } }); const p = parseSearch(s); expect(p.text).toBe("gpu"); expect(p.filters.entity).toBe("org_nvidia"); expect(p.filters.importance_min).toBe(60); expect(p.filters.silent_change).toBe(false); }); }); describe("scores", () => { it("impact grows with numeric magnitude", () => { const small = computeImpact({ eventType: "pricing_change", magnitude: 30, entityImportance: 80, maxDeltaPct: 5, fieldChanges: 1, firstParty: true }); const big = computeImpact({ eventType: "pricing_change", magnitude: 30, entityImportance: 80, maxDeltaPct: 400, fieldChanges: 1, firstParty: true }); expect(big).toBeGreaterThan(small); }); it("velocity grows with signals and sources", () => { expect(velocityScore({ signals: 1, windowHours: 1, uniqueSources: 1, firstPartySignals: 1 })).toBeLessThanOrEqual(15); expect(velocityScore({ signals: 17, windowHours: 1, uniqueSources: 9, firstPartySignals: 3 })).toBeGreaterThan(60); }); it("signal score is explainable and penalizes noise classes", () => { const good = computeSignalScore({ importance: 88, confidence: 90, novelty: 85, velocity: 55, impact: 80, anomaly: 65, confirmations: 3, firstParty: true, silent: false, sourceTier: "S" }); expect(good.score).toBeGreaterThan(80); expect(good.reasons.some((r) => r.sign === "+" && /confirmation/.test(r.text))).toBe(true); const noise = computeSignalScore({ importance: 60, confidence: 60, novelty: 60, velocity: 0, impact: 40, anomaly: 0, confirmations: 0, firstParty: true, silent: false, changeClass: "timestamp" }); expect(noise.score).toBeLessThanOrEqual(20); }); it("breaking state depends on strength, confirmation and age", () => { expect(breakingState({ signal: 85, importance: 90, velocity: 40, confirmations: 2, firstPartyCount: 1, ageMinutes: 30 })).toBe("breaking"); expect(breakingState({ signal: 85, importance: 90, velocity: 40, confirmations: 2, firstPartyCount: 1, ageMinutes: 20 * 60 })).toBe("confirmed"); expect(breakingState({ signal: 65, importance: 60, velocity: 40, confirmations: 0, firstPartyCount: 1, ageMinutes: 60 })).toBe("developing"); expect(breakingState({ signal: 40, importance: 40, velocity: 5, confirmations: 0, firstPartyCount: 1, ageMinutes: 60 })).toBe("watching"); expect(breakingState({ signal: 95, importance: 95, velocity: 90, confirmations: 5, firstPartyCount: 3, ageMinutes: 5 * 24 * 60 })).toBe("closed"); }); it("daily anomaly reports percent versus baseline", () => { const a = dailyAnomaly(38, 2, 24); expect(a.pct).toBeGreaterThan(1000); expect(a.score).toBeGreaterThan(80); expect(dailyAnomaly(1, 2, 24).score).toBeLessThan(30); }); }); describe("field extraction on structured lists", () => { it("extracts status flips in keyed lists", () => { const d = diffList([{ key: "NCT1", title: "Trial A", status: "Recruiting" }], [{ key: "NCT1", title: "Trial A", status: "Terminated" }], ["status"]); const f = extractFieldChanges(d); expect(f[0]?.before).toBe("Recruiting"); expect(f[0]?.after).toBe("Terminated"); expect(f[0]?.kind).toBe("status"); }); }); describe("describeChange titles", () => { it("humanizes URL-only sitemap items and lists batch labels", async () => { const { humanizeUrl, describeChange, evaluateChange } = await import("./heuristics"); expect(humanizeUrl("https://www.daiichisankyo.com/sustainability/performance-reports/news/detail/index_4751.html")).toBe("performance reports / news / detail / index 4751 (daiichisankyo.com)"); const d = diffList([], [{ key: "a", url: "https://acme.com/news/new-widget-launch.html" }]); const h = evaluateChange(d, { sensorType: "SITEMAP", url: "https://acme.com/sitemap.xml", sourceCategories: [] }); const t = describeChange(h, d, { sourceName: "Acme", url: "https://acme.com/sitemap.xml", sensorName: "sitemap" }); expect(t.title).toBe("Acme: news / new widget launch (acme.com)"); const many = diffList([], Array.from({ length: 12 }, (_, i) => ({ key: `CVE-2026-${1000 + i}`, title: `CVE-2026-${1000 + i}` }))); const h2 = evaluateChange(many, { sensorType: "REST_API", url: "https://services.nvd.nist.gov/rest/json/cves/2.0", sourceCategories: ["cyber"] }); const t2 = describeChange(h2, many, { sourceName: "NVD", url: "https://services.nvd.nist.gov/rest/json/cves/2.0", sensorName: "recent cves" }); expect(t2.title.startsWith("NVD: 12 new CVEs in recent cves — CVE-2026-1000")).toBe(true); expect(h2.eventType).toBe("vulnerability"); }); });