"""Freshness audit: stale documents, index drift and contract drift.""" from __future__ import annotations import datetime import unittest from corpus import freshness, pipeline, store as store_module from corpus.tests import support class AuditTest(unittest.TestCase): def setUp(self): self.root = support.temp_repo() self.cfg = support.make_config(self.root) self.addCleanup(_cleanup, self.root) with support.fake_qdrant(self.cfg): pipeline.run(self.cfg, mode="rebuild") def audit(self, today=None, cfg=None): with support.fake_qdrant(cfg or self.cfg): return freshness.audit(cfg or self.cfg, today=today) def test_clean_soon_after_rebuild(self): report = self.audit(today=datetime.date(2026, 10, 5)) self.assertTrue(report["ok"], report["findings"]) self.assertEqual(report["corpus"]["documents"], 3) def test_reports_stale_documents(self): report = self.audit(today=datetime.date(2027, 10, 5)) stale = {row["source_path"] for row in report["findings"]["stale"]} self.assertEqual( stale, { "content/ai/ai-overview.md", "content/edge/l3/edge-zigbee.md", "content/platform/l3/platform-containers.md", }, ) self.assertFalse(report["ok"]) text = freshness.render_text(report) self.assertIn("STALE", text) self.assertIn("platform-containers", text) def test_warns_before_a_document_goes_stale(self): # platform-containers was verified 2026-08-01. Horizon 180 days, warning # 30, so on 2027-01-20 it is 172 days old: due soon, not yet stale. report = self.audit(today=datetime.date(2027, 1, 20)) due = {row["source_path"] for row in report["findings"]["due_soon"]} self.assertEqual(due, {"content/platform/l3/platform-containers.md"}) self.assertEqual(report["findings"]["stale"], []) self.assertTrue(report["ok"]) def test_reports_index_drift_when_a_source_changes(self): support.write_doc( self.root, "ai/ai-overview.md", id="ai-overview", area="ai", level=1, title="Agent Systems", claim="Edited but not re-indexed.", confidence="running", last_verified="2026-10-05", tags="agents", body="New words for the corpus.", ) report = self.audit(today=datetime.date(2026, 10, 6)) problems = [row["problem"] for row in report["findings"]["index_drift"]] self.assertIn("source changed since it was embedded", problems) self.assertFalse(report["ok"]) def test_reports_contract_drift_when_the_model_changes(self): other = support.make_config(self.root, model="different-model") report = self.audit(today=datetime.date(2026, 10, 5), cfg=other) self.assertTrue(report["findings"]["contract_drift"]) def test_report_is_json_serialisable(self): import json report = self.audit(today=datetime.date(2026, 10, 5)) json.dumps(report) class AlertTest(unittest.TestCase): def test_no_url_means_no_alert(self): report = {"ok": True, "findings": {"stale": []}, "corpus": {}, "horizon": {}, "summary": {}} self.assertFalse(freshness.alert(report, "")) def test_posts_to_apprise(self): report = { "generated_at": "2026-10-26T00:00:00Z", "today": "2026-10-26", "horizon": {"max_age_days": 180, "warn_days": 30}, "corpus": { "documents": 1, "chunks": 1, "sources": 1, "collection": "c", "collection_points": 1, "provider": "p", "model": "m", "dim": 8, "chunker_version": "v", }, "findings": {"stale": [], "due_soon": [], "index_drift": [], "contract_drift": []}, "summary": {"stale": 0}, "ok": True, } with support.StubServer(lambda method, path, body: (200, "ok")) as server: self.assertTrue(freshness.alert(report, server.url)) self.assertIn("title", server.requests[0]["body"]) def _cleanup(root): import shutil shutil.rmtree(root, ignore_errors=True) if __name__ == "__main__": unittest.main()