"""ORCH-026 Level A (TC-A01): proactive pre-merge rebase. check_branch_mergeable must ALWAYS rebase the task branch onto the current origin/main under the held merge-lease when ``premerge_rebase_always`` is on — even when ``branch_is_behind_main`` would short-circuit (no conflict, formally not behind). With the flag OFF the ORCH-043 short-circuit is restored 1:1. These are pure unit tests: every merge_gate primitive is monkeypatched, so no git/network is touched — we assert the CONTROL FLOW (was auto_rebase_onto_main called?) and the verdict. """ import os import tempfile import pytest os.environ.setdefault("ORCH_DB_PATH", os.path.join(tempfile.gettempdir(), "test_orch026_premerge.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 @pytest.fixture def patched_gate(monkeypatch): """Patch merge_gate primitives; record whether auto_rebase ran.""" calls = {"rebase": 0, "retest": 0, "released": 0, "behind_checked": 0} monkeypatch.setattr(checks.settings, "merge_gate_enabled", True, raising=False) monkeypatch.setattr(checks.settings, "merge_gate_repos", "", raising=False) monkeypatch.setattr(merge_gate, "acquire_merge_lease", lambda *a, **k: (True, "lease acquired"), raising=False) def _behind(repo, branch): calls["behind_checked"] += 1 return False # NOT behind -> ORCH-043 would short-circuit def _rebase(repo, branch): calls["rebase"] += 1 return True, "rebased (noop)" def _retest(repo, branch): calls["retest"] += 1 return True, "green" def _release(repo, branch=None): calls["released"] += 1 monkeypatch.setattr(merge_gate, "branch_is_behind_main", _behind, raising=False) monkeypatch.setattr(merge_gate, "auto_rebase_onto_main", _rebase, raising=False) monkeypatch.setattr(merge_gate, "retest_branch", _retest, raising=False) monkeypatch.setattr(merge_gate, "release_merge_lease", _release, raising=False) return calls def test_always_rebases_even_when_not_behind(patched_gate, monkeypatch): """premerge_rebase_always=True -> auto_rebase_onto_main ALWAYS called (AC-A2).""" monkeypatch.setattr(checks.settings, "premerge_rebase_always", True, raising=False) ok, reason = checks.check_branch_mergeable("orchestrator", "ORCH-026", "feature/x") assert ok is True assert patched_gate["rebase"] == 1, "rebase must run even when not behind" assert patched_gate["retest"] == 1, "re-test must run after the proactive rebase" def test_flag_off_short_circuits_like_orch043(patched_gate, monkeypatch): """premerge_rebase_always=False -> not-behind short-circuit, no rebase (AC-A7).""" monkeypatch.setattr(checks.settings, "premerge_rebase_always", False, raising=False) ok, reason = checks.check_branch_mergeable("orchestrator", "ORCH-026", "feature/x") assert ok is True assert reason == "branch up-to-date with main" assert patched_gate["rebase"] == 0, "must NOT rebase when not behind and flag off" def test_disabled_gate_is_noop(monkeypatch): """merge_gate_enabled=False -> pass-through, no lease/rebase at all (AC-G2).""" monkeypatch.setattr(checks.settings, "merge_gate_enabled", False, raising=False) monkeypatch.setattr(checks.settings, "premerge_rebase_always", True, raising=False) ok, reason = checks.check_branch_mergeable("orchestrator", "ORCH-026", "feature/x") assert ok is True assert "disabled" in reason