#!/usr/bin/env python3 """Tests for legacy report artifact backfill.""" from __future__ import annotations import os import tempfile import unittest from pathlib import Path from app.db import connect from app.db.backfill import backfill_report_artifacts from app.db import repositories as repo class ReportBackfillTest(unittest.TestCase): def setUp(self): self.temp_dir = tempfile.TemporaryDirectory() self.root = Path(self.temp_dir.name) self.conn = connect(str(self.root / "test.db")) self.roots = { "copyparty": str(self.root / "copyparty"), "npm": str(self.root / "npm"), "app": str(self.root / "app"), "mikrotik": str(self.root / "mikrotik"), } for path in self.roots.values(): Path(path).mkdir(parents=True) def tearDown(self): self.conn.close() self.temp_dir.cleanup() def test_dry_run_and_import_are_idempotent(self): Path(self.roots["copyparty"], "2026-11-06-11-30-10.txt").write_text( "copyparty summary\n", encoding="utf-8") Path(self.roots["npm"], "2026-11-06.csv").write_text( "ip,count\n192.0.2.1,3\n", encoding="utf-8") Path(self.roots["app"], "2026-11-06.json").write_text( '{"summary":{"blocked":1}}', encoding="utf-8") Path(self.roots["app"], "data").mkdir() Path(self.roots["app"], "data", "requests.csv").write_text( "ip\n192.0.2.2\n", encoding="utf-8") preview = backfill_report_artifacts( self.conn, dry_run=True, roots=self.roots)["reports"] self.assertEqual(preview["would_import"], 3) self.assertEqual(preview["skipped_canonical"], [ {"source": "app", "path": "data/requests.csv"}]) self.assertEqual(preview["by_source"]["copyparty"]["would_import"], 1) self.assertEqual(preview["by_format"]["csv"]["would_import"], 1) self.assertEqual(repo.list_report_artifacts(self.conn)["items"], []) imported = backfill_report_artifacts( self.conn, roots=self.roots)["reports"] self.assertEqual(imported["imported"], 3) self.assertEqual(imported["unmapped"], []) repeated = backfill_report_artifacts( self.conn, roots=self.roots)["reports"] self.assertEqual(repeated["imported"], 0) self.assertEqual(repeated["already_imported"], 3) items = repo.list_report_artifacts( self.conn, source="copyparty")["items"] self.assertEqual(items[0]["original_path"], "copyparty/2026-11-06-11-30-10.txt") self.assertEqual(items[0]["generated_at"], "2026-11-06T11:30:10+00:00") def test_malformed_json_is_preserved_with_parse_error(self): Path(self.roots["app"], "2026-11-06.json").write_text( '{"incomplete":', encoding="utf-8") result = backfill_report_artifacts( self.conn, roots=self.roots)["reports"] self.assertEqual(result["imported"], 1) artifact = repo.list_report_artifacts( self.conn, source="app")["items"][0] detail = repo.get_report_artifact(self.conn, artifact["id"]) self.assertEqual(detail["payload"]["content"], '{"incomplete":') self.assertIn("parse_error", detail["payload"]) def test_artifacts_survive_sqlite_backup_and_restore(self): artifact_id = repo.insert_report_artifact( self.conn, source="app", report_type="summary", generated_at="2026-11-06T00:00:00+00:00", payload={"summary": {"count": 4}}, original_path="app/2026-11-06.json", content_sha256="c" * 64, ) restored = connect(str(self.root / "restored.db")) try: self.conn.backup(restored) artifact = repo.get_report_artifact(restored, artifact_id) self.assertEqual(artifact["payload"], {"summary": {"count": 4}}) finally: restored.close() def test_oversized_file_is_reported_without_deleting_source(self): source = Path(self.roots["npm"], "large.txt") source.write_text("keep", encoding="utf-8") from unittest.mock import patch with patch("app.db.backfill.MAX_REPORT_ARTIFACT_BYTES", 1): result = backfill_report_artifacts( self.conn, roots=self.roots)["reports"] self.assertEqual(len(result["unmapped"]), 1) self.assertTrue(source.exists()) self.assertEqual(result["unmapped"][0]["path"], "large.txt") if __name__ == "__main__": unittest.main()