Files
RepoPulse-Public/backend/tests/test_store.py
T

162 lines
6.6 KiB
Python

import json
from datetime import UTC, datetime, timedelta
from pathlib import Path
import app.store as store_module
from app.scanner import scan_repository
from app.store import ScanStore
def test_scan_history_comparison_and_trend(tmp_path: Path) -> None:
repository = tmp_path / "repo"
repository.mkdir()
(repository / "README.md").write_text("# Demo\n", encoding="utf-8")
(repository / ".gitignore").write_text("*.db\n", encoding="utf-8")
store = ScanStore(str(tmp_path / "repopulse.db"))
first = store.save(scan_repository(str(repository)))
(repository / "app.py").write_text("print('hello')\n", encoding="utf-8")
(repository / "tests").mkdir()
(repository / "tests" / "test_app.py").write_text("def test_ok():\n assert True\n", encoding="utf-8")
second = store.save(scan_repository(str(repository)))
assert store.get(second.id) == second
assert store.recent(1) == [second]
assert store.previous_for_repository(second.repository_path, second.id) == first
comparison = store.compare(second)
assert comparison.previous_scan_id == first.id
assert comparison.health_score_delta == second.health_score - first.health_score
assert comparison.metric_deltas["source_files"] == 2
trend = store.trend(second.repository_path)
assert [item.scan_id for item in trend] == [first.id, second.id]
assert trend[-1].health_score == second.health_score
def test_comparison_uses_the_scan_before_the_selected_scan(tmp_path: Path) -> None:
repository = tmp_path / "repo"
repository.mkdir()
(repository / "README.md").write_text("# Demo\n", encoding="utf-8")
(repository / ".gitignore").write_text("*.db\n", encoding="utf-8")
store = ScanStore(str(tmp_path / "repopulse.db"))
first = store.save(scan_repository(str(repository)))
second = store.save(scan_repository(str(repository)))
third = store.save(scan_repository(str(repository)))
assert store.previous_for_repository(second.repository_path, second.id) == first
assert store.previous_for_repository(third.repository_path, third.id) == second
def test_repository_summary_exposes_compact_remediation_context(tmp_path: Path) -> None:
repository = tmp_path / "repo"
repository.mkdir()
(repository / "README.md").write_text("# Demo\n", encoding="utf-8")
(repository / ".gitignore").write_text(".env\n", encoding="utf-8")
(repository / ".env").write_text("TOKEN=actual-secret-value-123456\n", encoding="utf-8")
store = ScanStore(str(tmp_path / "repopulse.db"))
store.save(scan_repository(str(repository)))
(repository / ".env").unlink()
(repository / "app.py").write_text("api_key = 'actual-secret-value-123456'\n", encoding="utf-8")
store.save(scan_repository(str(repository)))
summary = store.list_repositories()[0]
assert summary.last_scan_blockers == 1
assert summary.last_scan_primary_signal is not None
assert summary.last_scan_primary_signal.code == "security.secret-assignment"
assert summary.last_scan_primary_signal.count == 1
assert summary.last_scan_added_findings >= 1
assert summary.last_scan_resolved_findings == 1
def test_corrupt_database_is_quarantined_and_recreated(tmp_path: Path) -> None:
database = tmp_path / "repopulse.db"
database.write_bytes(b"not a sqlite database")
store = ScanStore(str(database))
assert store.recent() == []
assert list(tmp_path.glob("repopulse.db.corrupt-*"))
assert store.database_recovery_status()["status"] == "degraded_recovered"
def test_schema_migration_records_the_durable_backup_artifact(tmp_path: Path) -> None:
database = tmp_path / "repopulse.db"
initial_store = ScanStore(str(database))
with initial_store._connection() as db:
db.execute("PRAGMA user_version = 3")
migrated_store = ScanStore(str(database))
recorded = migrated_store.get_state("last_schema_migration_backup")
assert recorded is not None
metadata = json.loads(recorded)
assert metadata["from_version"] == 3
assert metadata["to_version"] == store_module.SCHEMA_VERSION
assert (tmp_path / metadata["backup_file"]).is_file()
assert migrated_store.storage_stats()["last_schema_migration_backup"] == recorded
def test_history_is_bounded_per_repository(tmp_path: Path, monkeypatch) -> None:
repository = tmp_path / "repo"
repository.mkdir()
(repository / "README.md").write_text("# Demo\n", encoding="utf-8")
monkeypatch.setattr(store_module, "MAX_HISTORY_PER_REPOSITORY", 2)
store = ScanStore(str(tmp_path / "repopulse.db"))
first = store.save(scan_repository(str(repository)))
second = store.save(scan_repository(str(repository)))
third = store.save(scan_repository(str(repository)))
assert [item.id for item in store.recent_for_repository(third.repository_path)] == [third.id, second.id]
assert store.get(first.id) is None
def test_retention_compacts_old_detailed_payloads_and_expires_old_snapshots(tmp_path: Path, monkeypatch) -> None:
repository = tmp_path / "repo"
repository.mkdir()
(repository / "README.md").write_text("# Demo\n", encoding="utf-8")
(repository / ".gitignore").write_text("*.db\n", encoding="utf-8")
monkeypatch.setenv("REPOPULSE_FULL_REPORTS_PER_REPOSITORY", "1")
monkeypatch.setenv("REPOPULSE_REPORT_RETENTION_DAYS", "30")
store = ScanStore(str(tmp_path / "repopulse.db"))
first = store.save(scan_repository(str(repository)))
second = store.save(scan_repository(str(repository)))
compacted = store.get(first.id)
assert compacted is not None and compacted.compacted is True
assert compacted.findings == []
assert store.get(second.id) is not None
expired = second.model_copy(
update={"id": "expired-scan", "scanned_at": datetime.now(UTC) - timedelta(days=31)}
)
store.save(expired)
assert store.get("expired-scan") is None
def test_catalog_summary_uses_denormalized_fields_without_report_json_parsing(tmp_path: Path, monkeypatch) -> None:
repository = tmp_path / "repo"
repository.mkdir()
(repository / "README.md").write_text("# Demo\n", encoding="utf-8")
(repository / ".gitignore").write_text("*.db\n", encoding="utf-8")
store = ScanStore(str(tmp_path / "repopulse.db"))
store.save(scan_repository(str(repository)))
def fail_if_called(_row):
raise AssertionError("catalog list must not parse report_json")
monkeypatch.setattr(store, "_report_from_row", fail_if_called)
summary = store.list_repositories()[0]
assert summary.last_scan_id
storage = store.storage_stats()
assert storage["scan_count"] == 1
assert storage["mirror_count"] >= 0
assert storage["worktree_count"] >= 0