import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; import { assessFreshness, STALE_AFTER_DAYS_DEFAULT, } from '../../scanners/lib/knowledge-refresh.mjs'; // Build a register with one entry verified on `verified`. const entry = (id, verified, extra = {}) => ({ id, claim: `claim for ${id}`, confidence: 'confirmed', source: { url: `https://example.com/${id}`, title: id, verified }, ...extra, }); const reg = (entries) => ({ version: 1, entries }); const REF = '2026-06-21'; describe('STALE_AFTER_DAYS_DEFAULT', () => { it('is 90 (quarterly re-verify cadence)', () => { assert.equal(STALE_AFTER_DAYS_DEFAULT, 90); }); }); describe('assessFreshness — classification', () => { it('classifies a recently-verified entry as fresh', () => { const r = assessFreshness(reg([entry('BP-A-001', '2026-06-01')]), { referenceDate: REF }); assert.equal(r.counts.fresh, 1); assert.equal(r.counts.stale, 0); assert.equal(r.fresh[0].id, 'BP-A-001'); }); it('classifies an entry older than the threshold as stale', () => { // 2026-01-01 → 2026-06-21 is 171 days > 90 const r = assessFreshness(reg([entry('BP-A-001', '2026-01-01')]), { referenceDate: REF }); assert.equal(r.counts.stale, 1); assert.equal(r.counts.fresh, 0); assert.equal(r.stale[0].id, 'BP-A-001'); assert.equal(r.stale[0].ageDays, 171); }); it('treats exactly staleAfterDays as still fresh (strictly-greater is stale)', () => { const r = assessFreshness(reg([entry('BP-A-001', '2026-03-23')]), { referenceDate: REF, staleAfterDays: 90, }); // 2026-03-23 → 2026-06-21 is exactly 90 days assert.equal(r.fresh[0].ageDays, 90); assert.equal(r.counts.fresh, 1); assert.equal(r.counts.stale, 0); }); it('treats one day past the threshold as stale', () => { const r = assessFreshness(reg([entry('BP-A-001', '2026-03-22')]), { referenceDate: REF, staleAfterDays: 90, }); assert.equal(r.stale[0].ageDays, 91); assert.equal(r.counts.stale, 1); }); it('respects a custom staleAfterDays', () => { const entries = [entry('BP-A-001', '2026-06-01')]; // 20 days old assert.equal(assessFreshness(reg(entries), { referenceDate: REF, staleAfterDays: 90 }).counts.stale, 0); assert.equal(assessFreshness(reg(entries), { referenceDate: REF, staleAfterDays: 10 }).counts.stale, 1); }); it('treats a future verified date as fresh (negative age)', () => { const r = assessFreshness(reg([entry('BP-A-001', '2026-12-31')]), { referenceDate: REF }); assert.equal(r.counts.fresh, 1); assert.ok(r.fresh[0].ageDays < 0); }); }); describe('assessFreshness — output shape', () => { it('stale entries carry id, verified, ageDays, url, claim', () => { const r = assessFreshness(reg([entry('BP-A-001', '2026-01-01')]), { referenceDate: REF }); const s = r.stale[0]; assert.equal(s.id, 'BP-A-001'); assert.equal(s.verified, '2026-01-01'); assert.equal(typeof s.ageDays, 'number'); assert.equal(s.url, 'https://example.com/BP-A-001'); assert.equal(s.claim, 'claim for BP-A-001'); }); it('counts.total equals stale + fresh and matches entry count', () => { const r = assessFreshness( reg([entry('BP-A-001', '2026-01-01'), entry('BP-A-002', '2026-06-10'), entry('BP-A-003', '2025-01-01')]), { referenceDate: REF } ); assert.equal(r.counts.total, 3); assert.equal(r.counts.stale + r.counts.fresh, 3); }); it('normalizes referenceDate + echoes staleAfterDays in the result', () => { const r = assessFreshness(reg([entry('BP-A-001', '2026-06-01')]), { referenceDate: REF, staleAfterDays: 42 }); assert.equal(r.referenceDate, '2026-06-21'); assert.equal(r.staleAfterDays, 42); }); }); describe('assessFreshness — referenceDate input forms', () => { it('accepts a Date and a YYYY-MM-DD string identically', () => { const entries = [entry('BP-A-001', '2026-01-01')]; const fromString = assessFreshness(reg(entries), { referenceDate: REF }); const fromDate = assessFreshness(reg(entries), { referenceDate: new Date('2026-06-21T00:00:00Z') }); assert.deepStrictEqual(fromDate.stale, fromString.stale); assert.equal(fromDate.referenceDate, '2026-06-21'); }); it('throws a TypeError on a missing or invalid referenceDate', () => { assert.throws(() => assessFreshness(reg([entry('BP-A-001', '2026-01-01')]), {}), TypeError); assert.throws( () => assessFreshness(reg([entry('BP-A-001', '2026-01-01')]), { referenceDate: 'not-a-date' }), TypeError ); }); }); describe('assessFreshness — defensive cases', () => { it('returns all-zero counts for an empty register', () => { const r = assessFreshness(reg([]), { referenceDate: REF }); assert.deepStrictEqual(r.counts, { total: 0, stale: 0, fresh: 0 }); assert.deepStrictEqual(r.stale, []); assert.deepStrictEqual(r.fresh, []); }); it('classifies an entry with an unparseable verified date as stale with ageDays null', () => { const bad = entry('BP-A-001', '2026-06-01'); bad.source.verified = 'nope'; const r = assessFreshness(reg([bad]), { referenceDate: REF }); assert.equal(r.counts.stale, 1); assert.equal(r.stale[0].ageDays, null); }); it('classifies an entry with a missing source as stale with ageDays null', () => { const r = assessFreshness(reg([{ id: 'BP-A-001', claim: 'x', confidence: 'confirmed' }]), { referenceDate: REF, }); assert.equal(r.counts.stale, 1); assert.equal(r.stale[0].ageDays, null); }); });