import { describe, expect, it } from "vitest"; import { applyDecay, computeTrust, householdTrustScore, worstTrustState } from "../src/trust.js"; const PINNED = new Date("2026-08-06T12:00:00.000Z"); describe("applyDecay", () => { it("returns original confidence when elapsed days is zero", () => { expect(applyDecay(0.8, 0)).toBe(0.8); }); it("halves confidence after halfLifeDays", () => { expect(applyDecay(1.0, 7, { halfLifeDays: 7, staleAfterDays: 30 })).toBeCloseTo(0.5, 3); }); it("returns zero when elapsed days reaches staleAfterDays", () => { expect(applyDecay(0.8, 30, { halfLifeDays: 7, staleAfterDays: 30 })).toBe(0); }); }); describe("computeTrust", () => { it("returns trusted for a fresh high-confidence item", () => { const result = computeTrust( { confidence: 0.8, verifiedByUser: false, lastVerifiedAt: null, quantity: 2, updatedAt: PINNED, }, PINNED, ); expect(result.state).toBe("trusted"); expect(result.score).toBeGreaterThanOrEqual(60); }); it("decays when last verification is old", () => { const sevenDaysAgo = new Date(PINNED.getTime() - 7 * 86_400_000); const result = computeTrust( { confidence: 0.8, verifiedByUser: true, lastVerifiedAt: sevenDaysAgo, quantity: 1, updatedAt: sevenDaysAgo, }, PINNED, ); expect(result.state).toBe("decaying"); }); it("is stale for very old unverified low-confidence items", () => { const sixtyDaysAgo = new Date(PINNED.getTime() - 60 * 86_400_000); const result = computeTrust( { confidence: 0.2, verifiedByUser: false, lastVerifiedAt: null, quantity: 1, updatedAt: sixtyDaysAgo, }, PINNED, ); expect(result.state).toBe("stale"); }); it("is trusted when user verified recently", () => { const result = computeTrust( { confidence: 0.7, verifiedByUser: true, lastVerifiedAt: PINNED, quantity: 1, updatedAt: PINNED, }, PINNED, ); expect(result.state).toBe("trusted"); expect(result.score).toBeGreaterThanOrEqual(70); }); it("uses custom decay profile to accelerate staleness", () => { const tenDaysAgo = new Date(PINNED.getTime() - 10 * 86_400_000); const fast = { halfLifeDays: 3, staleAfterDays: 10 }; const result = computeTrust( { confidence: 0.9, verifiedByUser: true, lastVerifiedAt: tenDaysAgo, quantity: 1, updatedAt: tenDaysAgo, }, PINNED, fast, ); expect(result.state).toBe("stale"); }); }); describe("householdTrustScore", () => { it("returns 100 / up_to_date for empty inventory", () => { const result = householdTrustScore({ items: [], correctionCount30d: 0, transactionCount30d: 0 }, PINNED); expect(result.score).toBe(100); expect(result.status).toBe("up_to_date"); }); it("scores high when all items are verified and recent", () => { const result = householdTrustScore( { items: [ { confidence: 1, verifiedByUser: true, lastVerifiedAt: PINNED, quantity: 2, updatedAt: PINNED }, { confidence: 1, verifiedByUser: true, lastVerifiedAt: PINNED, quantity: 3, updatedAt: PINNED }, ], correctionCount30d: 0, transactionCount30d: 0, }, PINNED, ); expect(result.score).toBeGreaterThanOrEqual(80); expect(result.status).toBe("up_to_date"); }); it("drops to uncertain when items are old and unverified", () => { const old = new Date(PINNED.getTime() - 60 * 86_400_000); const result = householdTrustScore( { items: [ { confidence: 0.3, verifiedByUser: false, lastVerifiedAt: null, quantity: 0.1, updatedAt: old }, ], correctionCount30d: 5, transactionCount30d: 10, }, PINNED, ); expect(result.status).toBe("uncertain"); expect(result.score).toBeLessThan(50); }); it("flags needs_check for partly verified household", () => { const weekAgo = new Date(PINNED.getTime() - 7 * 86_400_000); const result = householdTrustScore( { items: [ { confidence: 0.8, verifiedByUser: true, lastVerifiedAt: weekAgo, quantity: 1, updatedAt: weekAgo }, { confidence: 0.5, verifiedByUser: false, lastVerifiedAt: null, quantity: 1, updatedAt: weekAgo }, ], correctionCount30d: 1, transactionCount30d: 5, }, PINNED, ); expect(result.status).toBe("needs_check"); }); }); describe("worstTrustState", () => { it("returns stale if present", () => { expect(worstTrustState(["trusted", "stale", "decaying"])).toBe("stale"); }); it("falls back to unverified", () => { expect(worstTrustState(["trusted", "unverified"])).toBe("unverified"); }); });