84 lines
2.8 KiB
Python
84 lines
2.8 KiB
Python
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
from dataclasses import dataclass
|
|
from datetime import datetime, timedelta
|
|
from pathlib import Path
|
|
from types import ModuleType
|
|
|
|
|
|
def load_cleanup_module() -> ModuleType:
|
|
script = Path(__file__).resolve().parents[2] / "scripts" / "cleanup_demo_artifacts.py"
|
|
spec = importlib.util.spec_from_file_location("cleanup_demo_artifacts", script)
|
|
assert spec is not None
|
|
assert spec.loader is not None
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
@dataclass
|
|
class ExportRow:
|
|
id: str
|
|
created_at: datetime | None
|
|
storage_path: str
|
|
|
|
|
|
def test_cleanup_candidate_selection_keeps_newest_exports() -> None:
|
|
cleanup = load_cleanup_module()
|
|
base = datetime(2026, 1, 1, 12, 0, 0)
|
|
exports = [
|
|
ExportRow("old", base, "/tmp/old.json"),
|
|
ExportRow("new", base + timedelta(days=2), "/tmp/new.json"),
|
|
ExportRow("middle", base + timedelta(days=1), "/tmp/middle.json"),
|
|
ExportRow("unknown", None, "/tmp/unknown.json"),
|
|
]
|
|
|
|
kept, candidates = cleanup.select_cleanup_candidates(exports, keep_latest=2)
|
|
|
|
assert [export.id for export in kept] == ["new", "middle"]
|
|
assert [export.id for export in candidates] == ["old", "unknown"]
|
|
|
|
|
|
def test_cleanup_candidate_selection_rejects_negative_keep_latest() -> None:
|
|
cleanup = load_cleanup_module()
|
|
|
|
try:
|
|
cleanup.select_cleanup_candidates([], keep_latest=-1)
|
|
except ValueError as exc:
|
|
assert "keep_latest" in str(exc)
|
|
else:
|
|
raise AssertionError("negative keep_latest should fail")
|
|
|
|
|
|
def test_cleanup_path_safety_requires_storage_root_containment(tmp_path: Path) -> None:
|
|
cleanup = load_cleanup_module()
|
|
storage_root = tmp_path / "storage"
|
|
safe_export = storage_root / "exports" / "project" / "report.html"
|
|
unsafe_export = tmp_path / "outside" / "report.html"
|
|
|
|
safe_export.parent.mkdir(parents=True)
|
|
unsafe_export.parent.mkdir(parents=True)
|
|
|
|
assert cleanup.is_within_storage_root(safe_export, storage_root) is True
|
|
assert cleanup.is_within_storage_root(unsafe_export, storage_root) is False
|
|
|
|
|
|
def test_cleanup_script_defaults_to_explicit_demo_project() -> None:
|
|
cleanup = load_cleanup_module()
|
|
parser = cleanup.build_parser()
|
|
|
|
args = parser.parse_args([])
|
|
|
|
assert args.project_name == cleanup.DEMO_PROJECT_NAME
|
|
assert args.keep_latest == 3
|
|
assert args.apply is False
|
|
|
|
|
|
def test_cleanup_script_reports_dry_run_candidates_separately() -> None:
|
|
script = Path(__file__).resolve().parents[2] / "backend" / "scripts" / "cleanup_demo_artifacts.py"
|
|
content = script.read_text(encoding="utf-8")
|
|
|
|
assert '"candidate_files": []' in content
|
|
assert 'summary["candidate_files"].append(str(path))' in content
|