"""ORCH-022 / TC-13..TC-15: the security-gate QG wrapper + registry wiring. Covers the thin ``check_security_gate`` registry wrapper in src/qg/checks.py (its conditionality fast-paths) and that the new check is registered + dispatched by ``_run_qg``. The deterministic core (scan / verdict / frontmatter) is covered in tests/test_security_gate.py. """ import os os.environ.setdefault("ORCH_GITEA_TOKEN", "test-token") os.environ.setdefault("ORCH_PLANE_API_TOKEN", "test-token") from src import security_gate as sg # noqa: E402 from src.qg import checks as qg # noqa: E402 from src.qg.checks import QG_CHECKS, check_security_gate # noqa: E402 _WI = "ORCH-022" _BRANCH = "feature/ORCH-022-x" # --------------------------------------------------------------------------- # TC-13 — non-self repo with empty scope -> N/A fast pass (no scanner run). # --------------------------------------------------------------------------- def test_tc13_non_self_repo_empty_scope_is_na(monkeypatch): """TC-13: a non-self repo with an empty scope -> (True, 'security-gate N/A for ') immediately, WITHOUT invoking the scanners.""" monkeypatch.setattr(sg.settings, "security_gate_enabled", True) monkeypatch.setattr(sg.settings, "security_gate_repos", "") called = {"scan": False} def _should_not_run(*a, **k): called["scan"] = True raise AssertionError("scanner must not run for an N/A repo") monkeypatch.setattr(sg, "scan_secrets", _should_not_run) monkeypatch.setattr(sg, "audit_dependencies", _should_not_run) ok, reason = check_security_gate("enduro-trails", _WI, _BRANCH) assert ok is True assert "N/A" in reason assert "enduro-trails" in reason assert called["scan"] is False # --------------------------------------------------------------------------- # TC-14 — kill-switch disabled -> no-op pass. # --------------------------------------------------------------------------- def test_tc14_disabled_is_noop_pass(monkeypatch): """TC-14: ORCH_SECURITY_GATE_ENABLED=false -> no-op pass (True), scanners untouched.""" monkeypatch.setattr(sg.settings, "security_gate_enabled", False) def _should_not_run(*a, **k): raise AssertionError("scanner must not run when the gate is disabled") monkeypatch.setattr(sg, "scan_secrets", _should_not_run) monkeypatch.setattr(sg, "audit_dependencies", _should_not_run) ok, reason = check_security_gate("orchestrator", _WI, _BRANCH) assert ok is True assert "disabled" in reason.lower() # --------------------------------------------------------------------------- # TC-15 — registered in QG_CHECKS + dispatched by _run_qg. # --------------------------------------------------------------------------- def test_tc15_registered_in_qg_checks(): """TC-15a: the new check is registered and callable.""" assert "check_security_gate" in QG_CHECKS assert QG_CHECKS["check_security_gate"] is check_security_gate assert callable(QG_CHECKS["check_security_gate"]) def test_tc15_dispatched_by_run_qg(monkeypatch): """TC-15b: _run_qg routes 'check_security_gate' with the (repo, work_item_id, branch) signature to the registered wrapper.""" from src import stage_engine captured = {} def _fake(repo, work_item_id, branch): captured["args"] = (repo, work_item_id, branch) return True, "ok" monkeypatch.setitem(stage_engine.QG_CHECKS, "check_security_gate", _fake) passed, reason = stage_engine._run_qg("check_security_gate", "orchestrator", _WI, _BRANCH) assert passed is True assert captured["args"] == ("orchestrator", _WI, _BRANCH) def test_security_gate_applies_scope(monkeypatch): """Conditionality matrix mirrors merge_gate_applies / image_freshness_applies.""" monkeypatch.setattr(sg.settings, "security_gate_enabled", True) # Empty scope -> only the self-hosting repo. monkeypatch.setattr(sg.settings, "security_gate_repos", "") assert sg.security_gate_applies("orchestrator") is True assert sg.security_gate_applies("enduro-trails") is False # Explicit CSV scope -> only the listed repos (case-insensitive). monkeypatch.setattr(sg.settings, "security_gate_repos", "enduro-trails, foo") assert sg.security_gate_applies("enduro-trails") is True assert sg.security_gate_applies("orchestrator") is False # Kill-switch wins over everything. monkeypatch.setattr(sg.settings, "security_gate_enabled", False) assert sg.security_gate_applies("orchestrator") is False def test_qg_wrapper_delegates(monkeypatch): """The QG wrapper delegates to security_gate.check_security_gate verbatim.""" monkeypatch.setattr(sg, "check_security_gate", lambda r, w, b: (False, "delegated FAIL")) ok, reason = check_security_gate("orchestrator", _WI, _BRANCH) assert ok is False assert reason == "delegated FAIL"