"""Unit tests for F2-11 — SSO-dep "deps-not-ready" SKIP must NOT yield a GREEN run. Two halves of the fix are tested without any real deploy: 1. `run_recipe_ci.sso_dep_unverified` — the pure gate predicate the orchestrator uses to flip `overall` to fail when a deps-declaring recipe's SSO tests were skipped because deps weren't ready. 2. `conftest.pytest_collection_modifyitems` — when CCCI_DEPS_READY=0 it (a) skips every `requires_deps` test and (b) records the skipped count to `$CCCI_DEPS_SKIP_REPORT` so the orchestrator can surface it + gate on it. The end-to-end hazard (a real SSO-dep recipe going green on a skip) is exercised by e2e against cc-ci; here we lock the decision logic + the conftest→orchestrator signal that drives it. """ from __future__ import annotations import importlib.util import os import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "runner")) import run_recipe_ci # noqa: E402 # ---- 1. the pure gate predicate ---- def test_sso_dep_unverified_true_when_declared_notready_and_skipped(): """declares DEPS + deps not ready + ≥1 requires_deps test skipped → run must FAIL (F2-11).""" assert run_recipe_ci.sso_dep_unverified(["keycloak"], deps_ready=False, requires_deps_skipped=1) assert run_recipe_ci.sso_dep_unverified(["keycloak"], deps_ready=False, requires_deps_skipped=3) def test_sso_dep_unverified_false_when_deps_ready(): """deps ready (setup_custom_tests succeeded) → SSO tests actually ran → not a failure.""" assert not run_recipe_ci.sso_dep_unverified(["keycloak"], deps_ready=True, requires_deps_skipped=0) def test_sso_dep_unverified_false_when_no_deps_declared(): """A recipe with no DEPS can never trip the SSO-skip gate.""" assert not run_recipe_ci.sso_dep_unverified([], deps_ready=False, requires_deps_skipped=0) assert not run_recipe_ci.sso_dep_unverified(None, deps_ready=False, requires_deps_skipped=2) def test_sso_dep_unverified_false_when_nothing_skipped(): """Deps declared + not ready but ZERO requires_deps tests skipped → don't false-fail (the recipe has no SSO-marked tests to have been masked).""" assert not run_recipe_ci.sso_dep_unverified(["keycloak"], deps_ready=False, requires_deps_skipped=0) # ---- 2. conftest skip + record behavior ---- def _load_conftest(): """Load tests/conftest.py under a private module name (avoid clashing with pytest's own loaded `conftest`), so we can call pytest_collection_modifyitems directly with fakes.""" path = os.path.join(os.path.dirname(__file__), "..", "conftest.py") spec = importlib.util.spec_from_file_location("ccci_conftest_under_test", path) mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) return mod class _FakeItem: def __init__(self, keywords): # pytest `item.keywords` supports `in`; a dict suffices. self.keywords = {k: True for k in keywords} self.markers = [] def add_marker(self, mark): self.markers.append(mark) def test_conftest_skips_and_records_requires_deps_when_not_ready(tmp_path, monkeypatch): conftest = _load_conftest() report = tmp_path / "skip.txt" monkeypatch.setenv("CCCI_DEPS_READY", "0") monkeypatch.setenv("CCCI_DEPS_NOT_READY_REASON", "keycloak realm setup boom") monkeypatch.setenv("CCCI_DEPS_SKIP_REPORT", str(report)) sso1 = _FakeItem(["requires_deps"]) sso2 = _FakeItem(["requires_deps"]) plain = _FakeItem([]) # a non-deps custom test — must NOT be skipped conftest.pytest_collection_modifyitems(config=None, items=[sso1, sso2, plain]) # Both requires_deps items got a skip marker; the plain one did not. assert len(sso1.markers) == 1 and len(sso2.markers) == 1 assert plain.markers == [] # The skipped count was recorded for the orchestrator. assert report.read_text().split() == ["2"] def test_conftest_appends_across_invocations(tmp_path, monkeypatch): """The orchestrator runs one pytest per custom file; counts must accumulate (append).""" conftest = _load_conftest() report = tmp_path / "skip.txt" monkeypatch.setenv("CCCI_DEPS_READY", "0") monkeypatch.setenv("CCCI_DEPS_SKIP_REPORT", str(report)) conftest.pytest_collection_modifyitems(None, [_FakeItem(["requires_deps"])]) conftest.pytest_collection_modifyitems(None, [_FakeItem(["requires_deps"]), _FakeItem(["requires_deps"])]) total = sum(int(x) for x in report.read_text().split()) assert total == 3 def test_conftest_noop_and_no_record_when_deps_ready(tmp_path, monkeypatch): """deps ready → no skips, no report file written (early return).""" conftest = _load_conftest() report = tmp_path / "skip.txt" monkeypatch.setenv("CCCI_DEPS_READY", "1") monkeypatch.setenv("CCCI_DEPS_SKIP_REPORT", str(report)) item = _FakeItem(["requires_deps"]) conftest.pytest_collection_modifyitems(None, [item]) assert item.markers == [] # not skipped assert not report.exists() # nothing recorded