"""ORCH-058 TC-01..05: staging-image provenance helpers (src/image_freshness.py). Covers the INV-FRESH building blocks in isolation: * TC-01/02/03 — the PURE provenance verdict (match / mismatch / fail-closed). * TC-04 — never-raise: docker/ssh/git errors -> safe verdict, no exception. * TC-05 — conditionality: non-self repo = no-op (N/A); self repo = real. """ import os import subprocess os.environ.setdefault("ORCH_PLANE_API_TOKEN", "test-token") os.environ.setdefault("ORCH_GITEA_TOKEN", "test-token") from src import image_freshness as imf # noqa: E402 # --------------------------------------------------------------------------- # TC-01: matching revisions -> fresh (PASS) # --------------------------------------------------------------------------- def test_tc01_matching_revisions_are_fresh(): ok, reason = imf.provenance_verdict("abc123def456", "abc123def456") assert ok is True assert "match" in reason.lower() # --------------------------------------------------------------------------- # TC-02: differing revisions -> NOT fresh (input for fail-fast) # --------------------------------------------------------------------------- def test_tc02_differing_revisions_are_not_fresh(): ok, reason = imf.provenance_verdict("aaaaaaaaaaaa", "bbbbbbbbbbbb") assert ok is False assert "mismatch" in reason.lower() # --------------------------------------------------------------------------- # TC-03: fail-closed — empty label OR empty expected -> never "fresh by default" # --------------------------------------------------------------------------- def test_tc03_empty_image_label_fails_closed(): ok, reason = imf.provenance_verdict("abc123", "") assert ok is False assert "fail-closed" in reason.lower() def test_tc03_empty_expected_revision_fails_closed(): ok, reason = imf.provenance_verdict("", "abc123") assert ok is False assert "fail-closed" in reason.lower() def test_tc03_both_empty_fails_closed(): ok, _ = imf.provenance_verdict("", "") assert ok is False # --------------------------------------------------------------------------- # TC-04: never-raise on docker/ssh/inspect/git errors -> safe verdict # --------------------------------------------------------------------------- def test_tc04_image_revision_inspect_error_returns_empty(monkeypatch): def _boom(*a, **k): raise OSError("docker not found") monkeypatch.setattr(imf.subprocess, "run", _boom) # Never raises; fail-closed empty -> downstream provenance mismatch. assert imf.image_revision("orchestrator-orchestrator-staging") == "" def test_tc04_image_revision_nonzero_rc_returns_empty(monkeypatch): monkeypatch.setattr( imf.subprocess, "run", lambda *a, **k: subprocess.CompletedProcess(a, 1, stdout="", stderr="no such image"), ) assert imf.image_revision("missing-image") == "" def test_tc04_image_revision_no_value_label_returns_empty(monkeypatch): # `docker inspect` prints "" when the label key is absent. monkeypatch.setattr( imf.subprocess, "run", lambda *a, **k: subprocess.CompletedProcess(a, 0, stdout="\n", stderr=""), ) assert imf.image_revision("unlabelled-image") == "" def test_tc04_validated_revision_missing_worktree_returns_empty(monkeypatch, tmp_path): # No worktree on disk -> fail-closed empty SHA, never raises. monkeypatch.setattr(imf.settings, "worktrees_dir", str(tmp_path / "nope")) monkeypatch.setattr(imf.settings, "repos_dir", str(tmp_path / "nope")) assert imf.validated_revision("orchestrator", "feature/ORCH-058-x") == "" def test_tc04_check_staging_image_fresh_never_raises(monkeypatch): # Self repo + enabled, but rebuild blows up -> caught -> safe (False) verdict. monkeypatch.setattr(imf.settings, "image_freshness_enabled", True) monkeypatch.setattr(imf.settings, "image_freshness_repos", "") monkeypatch.setattr(imf, "validated_revision", lambda r, b: "deadbeef") def _boom(*a, **k): raise RuntimeError("ssh exploded") monkeypatch.setattr(imf, "rebuild_staging_image", _boom) ok, reason = imf.check_staging_image_fresh("orchestrator", "ORCH-058", "feature/ORCH-058-x") assert ok is False assert "error" in reason.lower() # --------------------------------------------------------------------------- # TC-05: conditionality (self-hosting only) # --------------------------------------------------------------------------- def test_tc05_applies_only_to_self_hosting_by_default(monkeypatch): monkeypatch.setattr(imf.settings, "image_freshness_enabled", True) monkeypatch.setattr(imf.settings, "image_freshness_repos", "") assert imf.image_freshness_applies("orchestrator") is True assert imf.image_freshness_applies("enduro-trails") is False def test_tc05_applies_respects_repos_csv(monkeypatch): monkeypatch.setattr(imf.settings, "image_freshness_enabled", True) monkeypatch.setattr(imf.settings, "image_freshness_repos", "enduro-trails") assert imf.image_freshness_applies("enduro-trails") is True # CSV is authoritative: orchestrator not listed -> not real. assert imf.image_freshness_applies("orchestrator") is False def test_tc05_kill_switch_disables_for_everyone(monkeypatch): monkeypatch.setattr(imf.settings, "image_freshness_enabled", False) monkeypatch.setattr(imf.settings, "image_freshness_repos", "") assert imf.image_freshness_applies("orchestrator") is False def test_tc05_check_is_noop_for_non_self_repo(monkeypatch): monkeypatch.setattr(imf.settings, "image_freshness_enabled", True) monkeypatch.setattr(imf.settings, "image_freshness_repos", "") ok, reason = imf.check_staging_image_fresh("enduro-trails", "ET-001", "feature/ET-001-x") assert ok is True assert "N/A" in reason def test_tc05_check_disabled_is_pass(monkeypatch): monkeypatch.setattr(imf.settings, "image_freshness_enabled", False) ok, reason = imf.check_staging_image_fresh("orchestrator", "ORCH-058", "feature/ORCH-058-x") assert ok is True assert "disabled" in reason.lower() def test_tc05_check_real_for_self_repo_rebuilds(monkeypatch): # Self repo + enabled: validated commit resolved + rebuild OK -> fresh PASS. monkeypatch.setattr(imf.settings, "image_freshness_enabled", True) monkeypatch.setattr(imf.settings, "image_freshness_repos", "") monkeypatch.setattr(imf, "validated_revision", lambda r, b: "abc123def456") monkeypatch.setattr(imf, "rebuild_staging_image", lambda r, b, s: (True, "healthy")) ok, reason = imf.check_staging_image_fresh("orchestrator", "ORCH-058", "feature/ORCH-058-x") assert ok is True assert "abc123def456"[:12] in reason def test_tc05_check_fail_closed_when_no_validated_revision(monkeypatch): monkeypatch.setattr(imf.settings, "image_freshness_enabled", True) monkeypatch.setattr(imf.settings, "image_freshness_repos", "") monkeypatch.setattr(imf, "validated_revision", lambda r, b: "") ok, reason = imf.check_staging_image_fresh("orchestrator", "ORCH-058", "feature/ORCH-058-x") assert ok is False assert "fail-closed" in reason.lower() def test_tc05_check_fails_when_rebuild_fails(monkeypatch): monkeypatch.setattr(imf.settings, "image_freshness_enabled", True) monkeypatch.setattr(imf.settings, "image_freshness_repos", "") monkeypatch.setattr(imf, "validated_revision", lambda r, b: "abc123def456") monkeypatch.setattr(imf, "rebuild_staging_image", lambda r, b, s: (False, "build error")) ok, reason = imf.check_staging_image_fresh("orchestrator", "ORCH-058", "feature/ORCH-058-x") assert ok is False assert "rebuild failed" in reason.lower()