"""ORCH-026 conditionality / self-hosting safety (TC-A06, TC-A07). TC-A06 kill-switch / out-of-scope: with the flag off (or for a repo outside the merge-gate scope) the merge path behaves 1:1 as before ORCH-026 — no-op. TC-A07 self-hosting safety: the new Level-A logic never pushes to main; the only force op stays --force-with-lease on the task branch; STAGE_TRANSITIONS and the QG_CHECKS registry are unchanged. """ import os import tempfile os.environ.setdefault("ORCH_DB_PATH", os.path.join(tempfile.gettempdir(), "test_orch026_cond.db")) os.environ.setdefault("ORCH_GITEA_TOKEN", "test-token") os.environ.setdefault("ORCH_PLANE_API_TOKEN", "test-token") from src import merge_gate # noqa: E402 from src.qg import checks # noqa: E402 # ----------------------------------------------------------------- TC-A06 def test_out_of_scope_repo_is_noop_even_with_flag_on(monkeypatch): """A repo outside merge_gate scope -> N/A pass, regardless of premerge flag.""" monkeypatch.setattr(checks.settings, "merge_gate_enabled", True, raising=False) monkeypatch.setattr(checks.settings, "merge_gate_repos", "orchestrator", raising=False) monkeypatch.setattr(checks.settings, "premerge_rebase_always", True, raising=False) # enduro-trails is NOT in the scope -> no lease, no rebase, just N/A. called = {"acquire": 0, "rebase": 0} monkeypatch.setattr(merge_gate, "acquire_merge_lease", lambda *a, **k: (called.__setitem__("acquire", called["acquire"] + 1), (True, "x"))[1], raising=False) monkeypatch.setattr(merge_gate, "auto_rebase_onto_main", lambda *a, **k: (called.__setitem__("rebase", called["rebase"] + 1), (True, "x"))[1], raising=False) ok, reason = checks.check_branch_mergeable("enduro-trails", "ET-1", "feature/e") assert ok is True assert "N/A" in reason assert called["acquire"] == 0 and called["rebase"] == 0 def test_task_deps_kill_switch_omits_gate(monkeypatch): """task_deps_enabled=False -> claim_next_job query is the ORCH-1 query (no gate).""" import src.db as db monkeypatch.setattr(db.settings, "task_deps_enabled", False, raising=False) # Inspect the SQL the claim builds by stubbing the connection. captured = {} class _FakeConn: def execute(self, sql, *a): captured.setdefault("sql", sql) class _R: def fetchone(self_inner): return None return _R() def commit(self): pass def close(self): pass monkeypatch.setattr(db, "get_db", lambda: _FakeConn()) db.claim_next_job() assert "NOT EXISTS" not in captured["sql"], "gate must be omitted when disabled" def test_task_deps_enabled_adds_gate(monkeypatch): import src.db as db monkeypatch.setattr(db.settings, "task_deps_enabled", True, raising=False) captured = {} class _FakeConn: def execute(self, sql, *a): captured.setdefault("sql", sql) class _R: def fetchone(self_inner): return None return _R() def commit(self): pass def close(self): pass monkeypatch.setattr(db, "get_db", lambda: _FakeConn()) db.claim_next_job() assert "NOT EXISTS" in captured["sql"], "gate must be present when enabled" assert "job_deps" in captured["sql"] # ----------------------------------------------------------------- TC-A07 def test_stage_transitions_unchanged(): """ORCH-026 must not touch the state machine (AC-A5).""" from src.stages import STAGE_TRANSITIONS # The canonical happy-path edges must still exist exactly. assert STAGE_TRANSITIONS["deploy-staging"]["next"] == "deploy" assert STAGE_TRANSITIONS["deploy"]["next"] == "done" assert STAGE_TRANSITIONS["development"]["next"] == "review" def test_qg_registry_has_no_new_dep_gate(): """The dependency gate is врезка in claim_next_job, NOT a registered QG.""" from src.qg.checks import QG_CHECKS joined = " ".join(QG_CHECKS.keys()) assert "task_dep" not in joined and "dependency" not in joined def test_premerge_only_force_with_lease_on_branch(): """auto_rebase_onto_main never pushes to main; force is --force-with-lease only.""" import inspect src = inspect.getsource(merge_gate.auto_rebase_onto_main) assert "--force-with-lease" in src # No raw 'push origin main' / force-push to main in the rebase path. assert "push origin main" not in src assert "--force " not in src # plain --force (not -with-lease) is forbidden