All checks were successful
continuous-integration/drone/push Build is passing
a) compose.ccci.yml is FIRST-CLASS: the harness auto-copies tests/<recipe>/ compose.ccci.yml into the run's recipe checkout (ABRA_DIR-aware, lifecycle. provide_ccci_overlay) and auto-chaoses the pinned base deploy on its presence (kills the R7 implicit coupling). ghost/discourse install_steps.sh (copy-only boilerplate) deleted; CHAOS_BASE_DEPLOY removed from both metas + the registry. b) install-time deps wiring is the ONLY mode: deps with DEPS provision BEFORE the single deploy; legacy post-deploy provisioning + the setup_custom_tests.sh invocation machinery deleted. lasuite-docs migrated to install_steps.sh OIDC wiring (same env names/values as the old hook — only the timing moved); lasuite-drive's remaining post-deploy MinIO bucket one-shot moved to ops.py pre_install; both setup_custom_tests.sh files deleted; OIDC_AT_INSTALL removed from drive/meet metas + the registry. c) SKIP_GENERIC meta key deleted (zero users). Env form CCCI_SKIP_GENERIC* stays as the documented dev-only escape hatch; when active in a drone CI run the orchestrator prints a loud !! warning (manifest embedding lands in P5). d) conftest cleanup: dead pre-deploy-once fixtures deployed/deployed_app deleted (zero users), app_domain + _short + _wait_healthy dropped (only users were the deleted fixtures); deps_apps+deps_creds consolidated into ONE deps fixture (entries expose .domain etc. as attributes; dict access intact); the 6 lasuite test files renamed deps_creds->deps (fixture name only — assertions and flows byte-identical). requires_deps marker + F2-11 skip-report plumbing unchanged. Registry is now exactly the 14 final keys; docs §4 table regenerated. Stale setup_custom_tests/OIDC_AT_INSTALL prose in docstrings/comments/assert MESSAGES updated (no assert logic or expected value touched). Verified on cc-ci: cc-ci-run -m pytest tests/unit -q -> 175 passed; scripts/lint.sh -> PASS.
123 lines
5.0 KiB
Python
123 lines
5.0 KiB
Python
"""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 (dep provisioning 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
|