"""Unit tests for runner/harness/deps.py (Phase 2 §4.2 / Q2.3). Pure-Python: no real deploys. Tests the declarative parts of the dep resolver — DEPS declaration (read through the single meta loader since rcust P1), the per-dep domain derivation, and write/load of the run state file. The deploy_deps + teardown_deps integration is exercised by real e2e against cc-ci (Q2.4 acceptance). """ from __future__ import annotations import os import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "runner")) from harness import deps # noqa: E402 from harness import meta as meta_mod # noqa: E402 def test_declared_deps_empty_for_no_meta(monkeypatch, tmp_path): """A recipe with no recipe_meta.py declares no deps (rcust P1: DEPS via meta.load).""" monkeypatch.setattr(meta_mod, "TESTS_DIR", str(tmp_path / "tests")) assert meta_mod.load("ccci-no-meta").DEPS == [] def test_declared_deps_reads_DEPS_list(tmp_path, monkeypatch): """A recipe_meta.py with `DEPS = [...]` surfaces the list on the loaded meta (the orchestrator reads meta.DEPS — the successor of the deleted deps.declared_deps loader).""" recipe_dir = tmp_path / "tests" / "ccci-with-deps" recipe_dir.mkdir(parents=True) (recipe_dir / "recipe_meta.py").write_text('HEALTH_PATH = "/"\nDEPS = ["keycloak", "redis"]\n') monkeypatch.setattr(meta_mod, "TESTS_DIR", str(tmp_path / "tests")) assert meta_mod.load("ccci-with-deps").DEPS == ["keycloak", "redis"] def test_dep_domain_distinct_per_dep(): """Two deps of different kinds (same parent/pr/ref) get distinct per-run domains.""" parent = "lasuite-docs" pr = "42" ref = "abc123" d1 = deps.dep_domain(parent, pr, ref, "keycloak") d2 = deps.dep_domain(parent, pr, ref, "redis") assert d1 != d2 # Both must look like the standard run-app pattern: -<6hex>.ci.commoninternet.net assert d1.endswith(".ci.commoninternet.net") assert d2.endswith(".ci.commoninternet.net") # The hash is determined by (parent, pr, ref, dep) — same inputs = same domain (idempotent) assert deps.dep_domain(parent, pr, ref, "keycloak") == d1 def test_dep_domain_distinct_per_parent(): """The same dep deployed by two different parent recipes (same dep, pr, ref) gets distinct domains — proves the dep is parent-scoped not just dep-name-scoped.""" d1 = deps.dep_domain("lasuite-docs", "42", "abc", "keycloak") d2 = deps.dep_domain("cryptpad", "42", "abc", "keycloak") assert d1 != d2 def test_write_and_load_run_state(tmp_path, monkeypatch): """write_run_state writes JSON to $CCCI_DEPS_FILE; load_run_state reads it back.""" state_path = tmp_path / "deps.json" monkeypatch.setenv("CCCI_DEPS_FILE", str(state_path)) state = [ {"recipe": "keycloak", "domain": "kc-deadbe.ci.commoninternet.net"}, {"recipe": "redis", "domain": "redi-abc123.ci.commoninternet.net"}, ] deps.write_run_state(state) loaded = deps.load_run_state() assert loaded == state def test_load_run_state_missing_env_returns_empty(monkeypatch): """No $CCCI_DEPS_FILE -> empty list.""" monkeypatch.delenv("CCCI_DEPS_FILE", raising=False) assert deps.load_run_state() == [] def test_write_run_state_no_env_is_noop(monkeypatch): """write_run_state silently no-ops without $CCCI_DEPS_FILE (so standalone helper use doesn't require setting up the env).""" monkeypatch.delenv("CCCI_DEPS_FILE", raising=False) deps.write_run_state([{"recipe": "x", "domain": "y"}]) # must not raise