"""US09-02: the installation manual, checked against the thing it describes. Documentation rots quietly. A setting is renamed, a command grows a flag, an exit code changes meaning — and the manual keeps confidently saying the old thing until somebody follows it into a bad afternoon. So every fact in it that the code also knows is compared with the code, in both directions where both directions are meaningful: a setting the manual invents fails, and a setting the manual forgot fails too. """ from __future__ import annotations import re import subprocess import sys from functools import lru_cache from pathlib import Path from photo_pipeline.config import ENV_PREFIX, LEGACY_ALIASES, Config REPO = Path(__file__).resolve().parents[2] MANUAL = REPO / "docs" / "installation.md" MAIN = REPO / "photo_pipeline" / "__main__.py" TEXT = MANUAL.read_text() ENV_NAMES = re.compile(rf"{ENV_PREFIX}[A-Z0-9_]+") # Read by docker-compose.yml, not by Config. They are configuration an installer sets, # so the manual documents them; they are simply not application settings. COMPOSITION_ONLY = { f"{ENV_PREFIX}IMAGE", f"{ENV_PREFIX}LIBRARY_HOST_PATH", f"{ENV_PREFIX}PUBLISH_ADDRESS", f"{ENV_PREFIX}UID", f"{ENV_PREFIX}GID", f"{ENV_PREFIX}ENV_FILE", # read before Config exists, so it is not a field } def documented_env_names() -> set[str]: return set(ENV_NAMES.findall(TEXT)) @lru_cache def cli_help(*args: str) -> str: """What the CLI says about itself, asked the way an operator asks.""" result = subprocess.run( [sys.executable, "-m", "photo_pipeline", *args, "--help"], cwd=REPO, capture_output=True, text=True, timeout=120, check=False, ) assert result.returncode == 0, result.stderr return result.stdout @lru_cache def cli_commands() -> frozenset[str]: listed = re.search(r"\{([a-z0-9,\-]+)\}", cli_help()) assert listed, f"the CLI listed no subcommands:\n{cli_help()}" return frozenset(listed.group(1).split(",")) def cli_flags(command: str) -> frozenset[str]: return frozenset(re.findall(r"--[a-z][a-z-]+", cli_help(command))) # ── settings ───────────────────────────────────────────────────────────────── def test_every_setting_is_documented(): """A setting nobody documents is a setting nobody configures deliberately.""" expected = {f"{ENV_PREFIX}{field.upper()}" for field in Config.model_fields} missing = sorted(expected - documented_env_names()) assert missing == [], f"undocumented settings: {missing}" def test_the_manual_invents_no_settings(): unknown = sorted( name for name in documented_env_names() if name.removeprefix(ENV_PREFIX).lower() not in Config.model_fields and name not in COMPOSITION_ONLY ) assert unknown == [], f"documented but not a real setting: {unknown}" def test_the_documented_defaults_are_the_real_defaults(): """Spot-checked where a wrong default is actively dangerous: the bind address, the trust boundary, and the gate that protects an irreplaceable library.""" assert Config.model_fields["host"].default == "127.0.0.1" assert "`PHOTO_PIPELINE_HOST` | `127.0.0.1`" in TEXT assert Config.model_fields["allowed_hosts"].default == () assert Config.model_fields["require_dry_run_approval"].default is False assert "`PHOTO_PIPELINE_REQUIRE_DRY_RUN_APPROVAL` | `false`" in TEXT def test_every_secret_is_marked_as_one(): for field in ("access_secret", "vision_api_key", "immich_api_key"): name = f"{ENV_PREFIX}{field.upper()}" row = next(line for line in TEXT.splitlines() if line.startswith(f"| `{name}`")) assert "secret" in row.lower(), f"{name} is not marked as a secret" def test_the_legacy_aliases_are_documented(): """An operator with an existing photo_analyzer.env needs to know it still works.""" for alias in LEGACY_ALIASES: assert alias in TEXT, f"legacy alias {alias} is undocumented" # ── commands ───────────────────────────────────────────────────────────────── # The manual is an installation and operations manual, not a command reference: these # are the commands an installer actually runs, and every one of them must exist. OPERATIONAL = ("migrate", "serve", "worker", "backup", "verify-backup", "restore", "diagnostics") def test_every_command_the_manual_tells_you_to_run_exists(): missing = [name for name in OPERATIONAL if name not in cli_commands()] assert missing == [], f"the manual names commands the CLI does not have: {missing}" undocumented = [name for name in OPERATIONAL if not re.search(rf"\b{re.escape(name)}\b", TEXT)] assert undocumented == [], f"operational commands the manual omits: {undocumented}" def test_every_documented_flag_exists_on_the_command_it_is_shown_with(): for command, flag in ( ("backup", "--reason"), ("backup", "--keep"), ("restore", "--into"), ("worker", "--allow-legacy"), ("serve", "--allow-legacy"), ): assert flag in cli_flags(command), f"{command} has no {flag}" assert flag in TEXT, f"{flag} is undocumented" # ── exit codes ─────────────────────────────────────────────────────────────── def test_every_documented_exit_code_is_one_the_cli_can_return(): documented = {int(code) for code in re.findall(r"^\| `(\d)` \|", TEXT, re.MULTILINE)} returned = {int(code) for code in re.findall(r"^\s+return (\d)$", MAIN.read_text(), re.MULTILINE)} assert documented, "no exit codes are documented" assert documented <= returned | {0}, f"documented but unreachable: {sorted(documented - returned)}" def test_every_refusal_exit_code_is_documented(): """0 is success and 1 is 'it said why'; every other code is a specific refusal an operator will meet at startup, and meeting an undocumented one is the worst case.""" returned = {int(code) for code in re.findall(r"^\s+return (\d)$", MAIN.read_text(), re.MULTILINE)} documented = {int(code) for code in re.findall(r"^\| `(\d)` \|", TEXT, re.MULTILINE)} missing = sorted(code for code in returned if code not in documented and code != 0) assert missing == [], f"undocumented exit codes: {missing}" # ── the promises the manual makes ──────────────────────────────────────────── def test_the_manual_carries_no_credential_shaped_example(): """The repository's own scanner refuses these in a commit; a manual is exactly where a real-looking one gets copied from.""" suspicious = re.findall( r"(?i)(api[_-]?key|secret|token|password)\s*[=:]\s*[\"']?([A-Za-z0-9_\-]{12,})", TEXT, ) assert suspicious == [], f"credential-shaped example: {suspicious}" def test_the_manual_states_the_invariants_an_installer_must_not_break(): for invariant in ("_IGNORE/", "One writer", "EXIF is verified before upload"): assert invariant in TEXT, f"the manual does not state: {invariant}" def test_the_manual_is_reachable_from_the_index(): assert "installation.md" in (REPO / "docs" / "index.md").read_text()