Files
photoanalyzer/tests/unit/test_legacy_archive.py

174 lines
7.4 KiB
Python

"""Archive lint (US07-01): the frozen CLI archive is complete, honest, and inert.
Freezing the donors is only worth something if three things stay true: the archive
still holds exactly the bytes it claims, it holds no secret, and production cannot
reach it. Each of those is a one-line mistake away — a helpful `sys.path` insert, a
copied `.env`, an edited "just this once" source — so each is asserted here.
"""
from __future__ import annotations
import hashlib
import re
import subprocess
import sys
from pathlib import Path
import pytest
import yaml
REPO = Path(__file__).resolve().parents[2]
ARCHIVE = REPO / "legacy_cli_archive"
SOURCES = ARCHIVE / "src"
LEDGER = ARCHIVE / "donor_ledger.yaml"
CHECKSUMS = ARCHIVE / "CHECKSUMS.sha256"
# Everything the concept requires an archive to carry (§3 "Donor-first CLI
# migration and archival"): the sources, their docs, a dependency lock, schema
# notes, a redacted sample configuration, the ledger, and recorded checksums.
REQUIRED_ARTIFACTS = (
"README.md",
"donor_ledger.yaml",
"CHECKSUMS.sha256",
"requirements-lock.txt",
"photo_analyzer.env.sample",
)
ARCHIVED_MODULES = ("photo_analyzer", "nsfwtag", "webapp", "nsfw_tag", "compare_models")
# Only the two suites that compare against the donors may put the archive on the
# import path; every other tree must not name it at all.
IMPORT_ALLOWED = {
REPO / "tests" / "characterization" / "conftest.py",
REPO / "tests" / "characterization" / "test_donor_ledger.py", # lints the ledger there
REPO / "tests" / "integration" / "test_safety_parity.py",
Path(__file__),
}
SECRET_PATTERNS = (
re.compile(r"sk-(?!REPLACE_WITH_YOUR_KEY)[A-Za-z0-9_\-]{16,}"),
re.compile(r"AIza[0-9A-Za-z_\-]{20,}"), # Google API keys
re.compile(r"(?i)api[_-]?key\s*[=:]\s*['\"][A-Za-z0-9_\-]{16,}['\"]"),
)
def _archived_files() -> list[Path]:
return sorted(p for p in SOURCES.rglob("*") if p.is_file() and "__pycache__" not in p.parts)
# ── completeness ─────────────────────────────────────────────────────────────
def test_the_archive_carries_every_required_artifact():
for artifact in REQUIRED_ARTIFACTS:
path = ARCHIVE / artifact
assert path.is_file(), f"archive is missing {artifact}"
assert path.stat().st_size > 0, f"archive artifact {artifact} is empty"
assert (SOURCES / "photo_analyzer.py").is_file()
assert (SOURCES / "nsfwtag" / "README.md").is_file(), "donor docs must be archived too"
assert (SOURCES / "webapp" / "README.md").is_file()
def test_every_ledger_source_is_present_in_the_archive():
rows = yaml.safe_load(LEDGER.read_text(encoding="utf-8"))["rows"]
for row in rows:
source = SOURCES / row["source"]["file"]
assert source.is_file(), f"{row['id']}: {row['source']['file']} is not archived"
def test_the_readme_records_provenance_and_the_no_import_rule():
readme = (ARCHIVE / "README.md").read_text(encoding="utf-8")
for expected in ("CHECKSUMS.sha256", "requirements-lock.txt", "donor_ledger.yaml"):
assert expected in readme, f"README does not point at {expected}"
assert "Schema notes" in readme, "the donor's schema must be documented"
assert "nsfw_scores.csv" in readme, "the CSV's fate must be documented"
# ── integrity ────────────────────────────────────────────────────────────────
def test_every_archived_source_matches_its_checksum():
"""A frozen archive that silently drifts is not evidence of anything."""
recorded = {}
for line in CHECKSUMS.read_text(encoding="utf-8").splitlines():
digest, _, rel = line.partition(" ")
if rel:
recorded[rel.strip()] = digest
actual = {
str(path.relative_to(ARCHIVE)): hashlib.sha256(path.read_bytes()).hexdigest()
for path in _archived_files()
}
assert actual == recorded, "archived sources and CHECKSUMS.sha256 disagree"
# ── redaction ────────────────────────────────────────────────────────────────
def test_the_archive_contains_no_credential():
for path in [*_archived_files(), *(ARCHIVE / a for a in REQUIRED_ARTIFACTS)]:
if path.suffix in (".png", ".jpg", ".webp"):
continue
text = path.read_text(encoding="utf-8", errors="replace")
for pattern in SECRET_PATTERNS:
assert not pattern.search(text), f"possible secret in {path.relative_to(REPO)}"
def test_the_sample_configuration_is_a_placeholder_only():
sample = (ARCHIVE / "photo_analyzer.env.sample").read_text(encoding="utf-8")
assert "REDACTED" in sample
# Placeholders are not key-shaped, so neither a scanner nor a reader can
# mistake the sample for a credential.
assert "LLM_API_KEY=<" in sample
# No real env file, database, log, or CSV may ride along in the archive.
strays = [
p.name
for p in ARCHIVE.rglob("*")
if p.is_file() and p.suffix in (".env", ".db", ".sqlite", ".sqlite3", ".log", ".csv")
]
assert strays == [], f"unexpected runtime files archived: {strays}"
# ── inertness ────────────────────────────────────────────────────────────────
def test_production_code_never_imports_an_archived_module():
for path in (REPO / "photo_pipeline").rglob("*.py"):
text = path.read_text(encoding="utf-8")
for module in ARCHIVED_MODULES:
assert not re.search(rf"^\s*(import|from)\s+{module}\b", text, re.MULTILINE), \
f"{path.relative_to(REPO)} imports the archived {module}"
assert "legacy_cli_archive" not in text, \
f"{path.relative_to(REPO)} names the archive"
def test_only_the_parity_suites_put_the_archive_on_the_import_path():
for path in (REPO / "tests").rglob("*.py"):
if path in IMPORT_ALLOWED or "__pycache__" in path.parts:
continue
assert "legacy_cli_archive" not in path.read_text(encoding="utf-8"), \
f"{path.relative_to(REPO)} reaches into the archive"
def test_the_archived_modules_are_unimportable_from_a_clean_interpreter():
"""The real check: a fresh process with the repo on its path cannot load them."""
script = (
"import importlib.util, sys; "
f"sys.path.insert(0, {str(REPO)!r}); "
"print([m for m in "
f"{list(ARCHIVED_MODULES)!r}"
" if importlib.util.find_spec(m) is not None])"
)
result = subprocess.run(
[sys.executable, "-c", script], capture_output=True, text=True, cwd=str(REPO), check=True
)
assert result.stdout.strip() == "[]", f"still importable: {result.stdout.strip()}"
def test_the_application_starts_without_the_archive(tmp_path):
"""Nothing in the runtime path may need the frozen sources to exist."""
pytest.importorskip("fastapi")
from photo_pipeline.api.app import create_app
from photo_pipeline.config import Config
config = Config.from_env(
{"PHOTO_PIPELINE_DATA_DIR": str(tmp_path / "data"), "PHOTO_PIPELINE_LIBRARY_ROOTS": ""}
)
assert create_app(config) is not None