"""ORCH-058 TC-07/08: static + caller-contract guarantees of the provenance plumbing. These assert the *shape* of the deploy artefacts that can't be unit-tested by running them (they shell out to docker/ssh on the host): * TC-07 — the deploy hook fail-closes BEFORE `docker tag` when the staging image's git-revision label != EXPECTED_REVISION (exit 1), and the new `--build-staging` rebuild mode (a) stamps GIT_SHA into the image, (b) uses $BUILD_CONTEXT as the build context, (c) recreates 8501 + health-checks, (d) runs staging_check against the FRESH image (Strategy A step 3, AC-4), and (e) never recomputes GIT_SHA from $REPO. * TC-08 — the Dockerfile declares `ARG GIT_SHA` and stamps it into the `org.opencontainers.image.revision` OCI label (the anchor B reads). * TC-09 — the caller↔hook contract: `rebuild_staging_image` invokes the hook in `--build-staging` mode with BUILD_CONTEXT=, GIT_SHA=, and an EXPLICIT staging target (never prod). """ import pathlib _ROOT = pathlib.Path(__file__).resolve().parents[1] _HOOK = _ROOT / "scripts" / "orchestrator-deploy-hook.sh" _DOCKERFILE = _ROOT / "Dockerfile" # --------------------------------------------------------------------------- # TC-07: hook fail-closed provenance guard + --build-staging rebuild mode # --------------------------------------------------------------------------- def test_tc07_hook_has_fail_closed_provenance_guard(): text = _HOOK.read_text(encoding="utf-8") # The label key the hook inspects must be the OCI revision label. assert 'REVISION_LABEL="org.opencontainers.image.revision"' in text # EXPECTED_REVISION is read (default unset -> backward compatible). assert 'EXPECTED_REVISION="${EXPECTED_REVISION:-}"' in text # The guard must inspect the source image's label and normalise . assert "docker image inspect --format" in text assert '""' in text # Fail-closed: empty OR mismatch -> abort with exit 1. assert '-z "$IMG_REV" || "$IMG_REV" != "$EXPECTED_REVISION"' in text def test_tc07_provenance_guard_precedes_docker_tag(): """The fail-closed `exit 1` must sit BEFORE the `docker tag` retag line.""" text = _HOOK.read_text(encoding="utf-8") guard = text.index("$EXPECTED_REVISION") retag = text.index('docker tag "$SOURCE_IMAGE" "$TARGET_IMAGE"') assert guard < retag, "provenance guard must run before the prod retag" def test_tc07_build_staging_mode_stamps_git_sha(): text = _HOOK.read_text(encoding="utf-8") # The new Strategy-A rebuild mode exists and is keyed on --build-staging. assert '"${1:-}" == "--build-staging"' in text # It rebuilds the staging image stamping the validated commit as a build-arg. assert 'docker build --build-arg GIT_SHA="$GIT_SHA"' in text def test_tc07_build_staging_uses_build_context_and_recreates_8501(): """The rebuild must use $BUILD_CONTEXT as the docker build context and recreate the staging service with a health-check (not a bare build).""" text = _HOOK.read_text(encoding="utf-8") # $BUILD_CONTEXT is the build context of the rebuild (validated worktree). assert 'docker build --build-arg GIT_SHA="$GIT_SHA" -t "$TARGET_IMAGE" "$BUILD_CONTEXT"' in text # Recreate the staging service on the fresh image (no-build) + health-check. assert 'up -d --no-build "$TARGET_SERVICE"' in text assert 'health_check 10 6 "build-staging-health"' in text def test_tc07_build_staging_does_not_recompute_git_sha_from_repo(): """Regression guard (root cause of the silent-stale-promote class): the --build-staging mode must NOT derive GIT_SHA itself from the prod $REPO clone — it must consume the GIT_SHA passed in by the caller (the validated commit).""" text = _HOOK.read_text(encoding="utf-8") # Anchor on the actual block guard (not the header comment mentions). after = text[text.index('"${1:-}" == "--build-staging"'):] assert 'GIT_SHA="${GIT_SHA:-}"' in after assert "git rev-parse" not in after, "GIT_SHA must come from the caller, not the prod clone" def test_tc07_build_staging_runs_staging_check_against_fresh_image(): """Strategy A step 3 (ADR-001, AC-4): after recreate+health, the FRESH image is validated by staging_check.py (not health-only). This is the P1 the reviewer flagged: validate exactly the artefact later retagged to prod.""" text = _HOOK.read_text(encoding="utf-8") # Anchor on the actual block guard (not the header comment mentions). after = text[text.index('"${1:-}" == "--build-staging"'):] # staging_check is invoked, inside the staging container, --mode stub by default. assert "staging_check.py" in after assert 'docker exec "$STAGING_CONTAINER"' in after assert '--mode "$STAGING_CHECK_MODE"' in after assert 'STAGING_CHECK_MODE="${STAGING_CHECK_MODE:-stub}"' in after # The staging_check run must come AFTER the health-check (health gates readiness). assert after.index('health_check 10 6 "build-staging-health"') < after.index("staging_check.py") # --------------------------------------------------------------------------- # TC-08: Dockerfile stamps the OCI revision label from a build-arg # --------------------------------------------------------------------------- def test_tc08_dockerfile_stamps_revision_label(): text = _DOCKERFILE.read_text(encoding="utf-8") assert "ARG GIT_SHA" in text assert "LABEL org.opencontainers.image.revision=$GIT_SHA" in text # --------------------------------------------------------------------------- # TC-09: caller↔hook contract — rebuild_staging_image builds the right command # --------------------------------------------------------------------------- def test_tc09_rebuild_staging_image_passes_validated_context_and_staging_target(monkeypatch): """`rebuild_staging_image` must invoke the hook `--build-staging` over ssh with BUILD_CONTEXT=, GIT_SHA=, and an EXPLICIT staging target (service/port/profile/container) — never the prod 8500 target. The absence of this contract test is what hid the earlier P0s (review P2).""" import src.image_freshness as imgf captured = {} class _FakeCompleted: returncode = 0 stdout = "" stderr = "" def _fake_run(cmd, *a, **kw): captured["cmd"] = cmd return _FakeCompleted() monkeypatch.setattr(imgf, "_ssh_target", lambda: "slin@host") monkeypatch.setattr(imgf, "_host_worktree_path", lambda repo, branch: "/home/slin/repos/_wt/orchestrator/feature_X") monkeypatch.setattr(imgf.subprocess, "run", _fake_run) ok, msg = imgf.rebuild_staging_image("orchestrator", "feature/ORCH-058", "abc123def456") assert ok, msg cmd = captured["cmd"] assert cmd[0] == "ssh" inner = cmd[-1] # the remote shell command string # Validated commit + validated worktree as build context. assert "GIT_SHA=abc123def456" in inner assert "BUILD_CONTEXT=/home/slin/repos/_wt/orchestrator/feature_X" in inner # Explicit STAGING target — never the prod 8500 service/port. assert "TARGET_SERVICE=orchestrator-staging" in inner assert "TARGET_PORT=8501" in inner assert "COMPOSE_PROFILE=staging" in inner assert "STAGING_CONTAINER=orchestrator-staging" in inner assert "orchestrator-orchestrator-staging" in inner # staging TARGET_IMAGE assert "--build-staging" in inner # Hard safety: the prod service/port must NOT leak into the staging rebuild. assert "TARGET_PORT=8500" not in inner assert "TARGET_SERVICE=orchestrator " not in inner def test_tc09_rebuild_staging_image_no_ssh_host_fails_closed(monkeypatch): """No ssh host configured -> never-raise, fail-closed (False), no command run.""" import src.image_freshness as imgf monkeypatch.setattr(imgf, "_ssh_target", lambda: None) ok, reason = imgf.rebuild_staging_image("orchestrator", "feature/ORCH-058", "abc123") assert ok is False assert "ssh host" in reason