"""ORCH-110 TC-11: the local re-test necessity contract (D4 / FR-4 / AC-6). The chosen contract: run the local re-test IFF the pre-merge rebase actually moved HEAD (``main`` had moved -> a real semantic-conflict risk). When the rebase is a PROVEN no-op (HEAD unchanged -> branch already at origin/main, the CI/tester/staging- validated commit) the re-test is SKIPPED — it would be a redundant single point of false failure. On ANY uncertainty (an empty SHA) the re-test runs (fail-safe -> the red-rollback contract BR-6/AC-3 is never weakened). """ import os import tempfile os.environ.setdefault("ORCH_DB_PATH", os.path.join(tempfile.gettempdir(), "test_orch110_contract.db")) os.environ.setdefault("ORCH_REPOS_DIR", tempfile.gettempdir()) os.environ.setdefault("ORCH_GITEA_TOKEN", "test-token") os.environ.setdefault("ORCH_PLANE_API_TOKEN", "test-token") import pytest # noqa: E402 from src import merge_gate # noqa: E402 from src.qg import checks as qg # noqa: E402 _REPO = "orchestrator" _BRANCH = "feature/ORCH-110-x" _WI = "ORCH-110" @pytest.fixture def gate(monkeypatch): """Real check_branch_mergeable, mocked primitives; record control flow.""" state = {"retest": 0, "released": 0, "rebased": 0} monkeypatch.setattr(qg.settings, "merge_gate_enabled", True, raising=False) monkeypatch.setattr(qg.settings, "merge_gate_repos", "", raising=False) monkeypatch.setattr(qg.settings, "premerge_rebase_always", True, raising=False) monkeypatch.setattr(qg.settings, "merge_retest_skip_when_current_enabled", True, raising=False) monkeypatch.setattr(merge_gate, "acquire_merge_lease", lambda *a, **k: (True, "lease acquired")) monkeypatch.setattr(merge_gate, "branch_is_behind_main", lambda r, b: True) def _rebase(r, b): state["rebased"] += 1 return True, "rebased onto origin/main" def _release(r, b=None): state["released"] += 1 def _retest(r, b): state["retest"] += 1 return True, "re-test green" monkeypatch.setattr(merge_gate, "auto_rebase_onto_main", _rebase) monkeypatch.setattr(merge_gate, "release_merge_lease", _release) monkeypatch.setattr(merge_gate, "retest_branch", _retest) return state def _set_head_shas(monkeypatch, shas): seq = list(shas) monkeypatch.setattr(merge_gate, "head_sha", lambda r, b: seq.pop(0) if seq else "") def test_tc11_noop_rebase_skips_retest_lease_held(gate, monkeypatch): """Proven no-op rebase (HEAD unchanged) -> skip re-test, PASS, lease HELD.""" _set_head_shas(monkeypatch, ["sha_same", "sha_same"]) ok, reason = qg.check_branch_mergeable(_REPO, _WI, _BRANCH) assert ok is True assert "re-test skipped" in reason assert gate["retest"] == 0 # re-test NOT run assert gate["released"] == 0 # lease HELD until the merge def test_tc11_head_moved_runs_retest(gate, monkeypatch): """A real catch-up (HEAD moved) -> re-test RUNS (the ORCH-043 risk is real).""" _set_head_shas(monkeypatch, ["sha_old", "sha_new"]) ok, reason = qg.check_branch_mergeable(_REPO, _WI, _BRANCH) assert ok is True assert reason == "rebased onto main, re-test green" assert gate["retest"] == 1 @pytest.mark.parametrize("shas", [["", "sha_new"], ["sha_old", ""], ["", ""]]) def test_tc11_uncertain_sha_runs_retest_failsafe(gate, monkeypatch, shas): """Cannot prove a no-op (empty SHA) -> re-test RUNS (fail-safe, never skips on uncertainty).""" _set_head_shas(monkeypatch, shas) ok, reason = qg.check_branch_mergeable(_REPO, _WI, _BRANCH) assert ok is True assert gate["retest"] == 1 # re-test still runs on uncertainty def test_tc11_skip_bumps_observability_counter(gate, monkeypatch): """The skip increments the read-only observability counter (D6).""" merge_gate._MERGE_GATE_COUNTERS["retest_skipped_current_total"] = 0 _set_head_shas(monkeypatch, ["sha_same", "sha_same"]) qg.check_branch_mergeable(_REPO, _WI, _BRANCH) assert merge_gate._MERGE_GATE_COUNTERS["retest_skipped_current_total"] == 1