"""Regression tests for Issue #983: derived target base ref for cross-repository checkouts. The mutation guard previously assumed ``prgs/master`` and the parity report assumed ``origin/master``. Any repository using neither — for example remote ``MDCPS`` on integration branch ``dev`` — could not prove base equivalence, so every gated mutation failed closed with no reachable remedy. These tests build hermetic git repositories on disk (no network, no fetch) and assert the derived target end to end: identity remote, integration branch, tracking ref, fail-closed refusals, and agreement between the mutation guard and the parity report. """ from __future__ import annotations import inspect import os import subprocess import tempfile import unittest import anti_stomp_preflight import canonical_repository_root as crr import master_parity_gate import root_checkout_guard def _git(root: str, *args: str) -> str: res = subprocess.run( ["git", "-C", root, *args], capture_output=True, text=True, check=True, ) return (res.stdout or "").strip() def _make_repo(root: str, *, remote: str | None, url: str | None) -> str: """Initialise a repository with one commit and an optional named remote.""" os.makedirs(root, exist_ok=True) _git(root, "init", "--quiet") _git(root, "config", "user.email", "test@example.invalid") _git(root, "config", "user.name", "Issue983 Test") _git(root, "config", "commit.gpgsign", "false") with open(os.path.join(root, "seed.txt"), "w", encoding="utf-8") as fh: fh.write("seed\n") _git(root, "add", "seed.txt") _git(root, "commit", "--quiet", "-m", "seed") if remote and url: _git(root, "remote", "add", remote, url) return _git(root, "rev-parse", "HEAD") def _set_remote_branch(root: str, remote: str, branch: str, sha: str) -> None: """Create refs/remotes// without contacting a network.""" _git(root, "update-ref", f"refs/remotes/{remote}/{branch}", sha) def _set_remote_head(root: str, remote: str, branch: str) -> None: _git( root, "symbolic-ref", f"refs/remotes/{remote}/HEAD", f"refs/remotes/{remote}/{branch}", ) def _advance(root: str, message: str) -> str: with open(os.path.join(root, "seed.txt"), "a", encoding="utf-8") as fh: fh.write(message + "\n") _git(root, "add", "seed.txt") _git(root, "commit", "--quiet", "-m", message) return _git(root, "rev-parse", "HEAD") class _RepoCase(unittest.TestCase): def setUp(self) -> None: self._tmp = tempfile.TemporaryDirectory() self.addCleanup(self._tmp.cleanup) self.root = os.path.join(self._tmp.name, "repo") class TestPrgsBehaviourPreserved(_RepoCase): """AC1: existing PRGS behaviour using prgs/master is unchanged.""" def test_prgs_master_resolves_unchanged(self): head = _make_repo( self.root, remote="prgs", url="https://gitea.prgs.cc/Scaled-Tech-Consulting/Gitea-Tools.git", ) _set_remote_branch(self.root, "prgs", "master", head) _set_remote_head(self.root, "prgs", "master") got = crr.resolve_target_base_ref(self.root) self.assertTrue(got["proven"], got["reasons"]) self.assertEqual(got["remote"], "prgs") self.assertEqual(got["branch"], "master") self.assertEqual(got["tracking_ref"], "refs/remotes/prgs/master") self.assertEqual(got["repository_slug"], "Scaled-Tech-Consulting/Gitea-Tools") self.assertEqual(root_checkout_guard.resolve_remote_master_sha(self.root), head) def test_legacy_explicit_remote_refs_path_is_untouched(self): """An explicit remote_refs override still short-circuits derivation.""" head = _make_repo( self.root, remote="prgs", url="https://gitea.prgs.cc/Scaled-Tech-Consulting/Gitea-Tools.git", ) _set_remote_branch(self.root, "prgs", "master", head) state = root_checkout_guard.resolve_remote_master_ref_state( self.root, remote_refs=root_checkout_guard.REMOTE_MASTER_REFS ) self.assertEqual(state["sha"], head) self.assertEqual(state["source"], "explicit_remote_refs") class TestCrossRepositoryTarget(_RepoCase): """AC2/AC3/AC4: MDCPS/dev, no origin remote, exact remote-name case.""" def _mdcps(self) -> str: head = _make_repo( self.root, remote="MDCPS", url="https://gitea.example.net/MDCPS/WeeklyBriefings-Meta.git", ) _set_remote_branch(self.root, "MDCPS", "dev", head) _set_remote_head(self.root, "MDCPS", "dev") return head def test_mdcps_dev_resolves(self): head = self._mdcps() got = crr.resolve_target_base_ref(self.root) self.assertTrue(got["proven"], got["reasons"]) self.assertEqual(got["remote"], "MDCPS") self.assertEqual(got["branch"], "dev") self.assertEqual(got["tracking_ref"], "refs/remotes/MDCPS/dev") self.assertEqual(got["repository_slug"], "MDCPS/WeeklyBriefings-Meta") self.assertEqual(root_checkout_guard.resolve_remote_master_sha(self.root), head) def test_no_remote_named_origin(self): self._mdcps() self.assertEqual(_git(self.root, "remote"), "MDCPS") got = crr.resolve_target_base_ref(self.root) self.assertTrue(got["proven"], got["reasons"]) self.assertNotIn("origin", got["tracking_ref"]) def test_remote_name_case_is_preserved_exactly(self): self._mdcps() got = crr.resolve_target_base_ref(self.root) self.assertEqual(got["remote"], "MDCPS") self.assertNotEqual(got["remote"], "mdcps") # The tracking ref must address the real ref, which is case-sensitive. self.assertEqual(got["tracking_ref"], "refs/remotes/MDCPS/dev") self.assertTrue( _git(self.root, "rev-parse", "--verify", got["tracking_ref"]), "case-preserved tracking ref must resolve", ) def test_lowercase_candidate_never_supplies_the_remote_name(self): """Guards against silently case-folding MDCPS to the candidate 'mdcps'. ``_IDENTITY_REMOTE_CANDIDATES`` contains a lowercase ``mdcps`` entry and is probed *before* the repository's own remote listing. Git remote names live in case-sensitive config subsections on every platform, so the lowercase probe cannot resolve and the exact-case name must arrive from ``git remote``. Asserted through config rather than ref lookup because a case-insensitive filesystem (macOS) resolves loose refs either way, which would make a ref-based assertion test the filesystem instead of the code. """ self._mdcps() res = subprocess.run( ["git", "-C", self.root, "remote", "get-url", "mdcps"], capture_output=True, text=True, check=False, ) self.assertNotEqual(res.returncode, 0, "git remote names are case-sensitive") # Even when the caller *hints* the wrong case, the resolved name is exact. got = crr.resolve_target_base_ref(self.root, remote="mdcps") self.assertTrue(got["proven"], got["reasons"]) self.assertEqual(got["remote"], "MDCPS") self.assertEqual(got["tracking_ref"], "refs/remotes/MDCPS/dev") name, slug = crr.resolve_identity_remote(self.root) self.assertEqual(name, "MDCPS") self.assertEqual(slug, "MDCPS/WeeklyBriefings-Meta") class TestTargetStaleness(_RepoCase): """AC5/AC6: local equal to, behind, or divergent from the resolved tip.""" def _repo_with_tip(self) -> tuple[str, str]: head = _make_repo( self.root, remote="MDCPS", url="https://gitea.example.net/MDCPS/WeeklyBriefings-Meta.git", ) _set_remote_branch(self.root, "MDCPS", "dev", head) _set_remote_head(self.root, "MDCPS", "dev") return head, self.root def test_local_equal_to_resolved_tip_is_not_stale(self): self._repo_with_tip() got = master_parity_gate.assess_target_repository_parity( canonical_root=self.root, source="test" ) self.assertTrue(got["determinable"]) self.assertFalse(got["stale"]) self.assertEqual(got["tracking_ref"], "refs/remotes/MDCPS/dev") self.assertEqual(got["base_remote"], "MDCPS") self.assertEqual(got["base_branch"], "dev") def test_local_behind_resolved_tip_is_stale(self): head, _ = self._repo_with_tip() advanced = _advance(self.root, "remote moved on") _set_remote_branch(self.root, "MDCPS", "dev", advanced) _git(self.root, "reset", "--hard", "--quiet", head) got = master_parity_gate.assess_target_repository_parity( canonical_root=self.root, source="test" ) self.assertTrue(got["determinable"]) self.assertTrue(got["stale"]) self.assertEqual(got["checkout_head"], head) self.assertEqual(got["remote_tracking_head"], advanced) def test_local_divergent_from_resolved_tip_is_stale(self): head, _ = self._repo_with_tip() remote_side = _advance(self.root, "remote side") _set_remote_branch(self.root, "MDCPS", "dev", remote_side) _git(self.root, "reset", "--hard", "--quiet", head) local_side = _advance(self.root, "local side") got = master_parity_gate.assess_target_repository_parity( canonical_root=self.root, source="test" ) self.assertTrue(got["stale"]) self.assertEqual(got["checkout_head"], local_side) self.assertNotEqual(local_side, remote_side) class TestFailClosed(_RepoCase): """AC7/AC8: missing remote/ref and ambiguous resolution never guess.""" def test_missing_remote_fails_closed(self): _make_repo(self.root, remote=None, url=None) got = crr.resolve_target_base_ref(self.root) self.assertFalse(got["proven"]) self.assertEqual(got["reason_code"], crr.DENY_NO_IDENTITY_REMOTE) self.assertEqual(got["tracking_refs"], ()) self.assertIsNone(root_checkout_guard.resolve_remote_master_sha(self.root)) def test_missing_tracking_ref_fails_closed(self): _make_repo( self.root, remote="MDCPS", url="https://gitea.example.net/MDCPS/WeeklyBriefings-Meta.git", ) # Remote configured, but nothing has ever been fetched. got = crr.resolve_target_base_ref(self.root) self.assertFalse(got["proven"]) self.assertEqual(got["reason_code"], crr.DENY_NO_BASE_BRANCH) self.assertIsNone(root_checkout_guard.resolve_remote_master_sha(self.root)) def test_ambiguous_integration_branch_fails_closed(self): head = _make_repo( self.root, remote="MDCPS", url="https://gitea.example.net/MDCPS/WeeklyBriefings-Meta.git", ) # Two candidate integration branches and no recorded remote default. _set_remote_branch(self.root, "MDCPS", "dev", head) _set_remote_branch(self.root, "MDCPS", "main", head) got = crr.resolve_target_base_ref(self.root) self.assertFalse(got["proven"]) self.assertEqual(got["reason_code"], crr.DENY_AMBIGUOUS_BASE_BRANCH) self.assertIsNone(root_checkout_guard.resolve_remote_master_sha(self.root)) def test_recorded_remote_head_resolves_otherwise_ambiguous_branches(self): """Ambiguity is refused only when git records no default.""" head = _make_repo( self.root, remote="MDCPS", url="https://gitea.example.net/MDCPS/WeeklyBriefings-Meta.git", ) _set_remote_branch(self.root, "MDCPS", "dev", head) _set_remote_branch(self.root, "MDCPS", "main", head) _set_remote_head(self.root, "MDCPS", "dev") got = crr.resolve_target_base_ref(self.root) self.assertTrue(got["proven"], got["reasons"]) self.assertEqual(got["branch"], "dev") self.assertEqual(got["source"], crr.BASE_REF_SOURCE_REMOTE_HEAD) def test_ambiguous_identity_remote_fails_closed(self): head = _make_repo( self.root, remote="MDCPS", url="https://gitea.example.net/MDCPS/WeeklyBriefings-Meta.git", ) _git( self.root, "remote", "add", "prgs", "https://gitea.prgs.cc/Scaled-Tech-Consulting/Gitea-Tools.git", ) _set_remote_branch(self.root, "MDCPS", "dev", head) _set_remote_branch(self.root, "prgs", "master", head) got = crr.resolve_target_base_ref(self.root) self.assertFalse(got["proven"]) self.assertEqual(got["reason_code"], crr.DENY_AMBIGUOUS_REMOTE) self.assertEqual(got["tracking_refs"], ()) def test_named_remote_disambiguates(self): """An explicitly named remote is authoritative and not ambiguous.""" head = _make_repo( self.root, remote="MDCPS", url="https://gitea.example.net/MDCPS/WeeklyBriefings-Meta.git", ) _git( self.root, "remote", "add", "prgs", "https://gitea.prgs.cc/Scaled-Tech-Consulting/Gitea-Tools.git", ) _set_remote_branch(self.root, "MDCPS", "dev", head) _set_remote_branch(self.root, "prgs", "master", head) got = crr.resolve_target_base_ref(self.root, remote="MDCPS") self.assertTrue(got["proven"], got["reasons"]) self.assertEqual(got["remote"], "MDCPS") self.assertEqual(got["branch"], "dev") def test_orphan_tracking_ref_from_removed_remote_is_ignored(self): """The live Gitea-Tools symptom: refs/remotes/origin/* outlives its remote.""" head = _make_repo( self.root, remote="prgs", url="https://gitea.prgs.cc/Scaled-Tech-Consulting/Gitea-Tools.git", ) _set_remote_branch(self.root, "prgs", "master", head) _set_remote_head(self.root, "prgs", "master") # An abandoned ref left behind by a remote that no longer exists. _set_remote_branch(self.root, "origin", "master", head) _advance(self.root, "orphan must not be consulted") got = master_parity_gate.assess_target_repository_parity( canonical_root=self.root, source="test" ) self.assertEqual(got["tracking_ref"], "refs/remotes/prgs/master") self.assertEqual(got["repository_slug"], "Scaled-Tech-Consulting/Gitea-Tools") self.assertNotIn( "target repository identity could not be derived from its git remote", got["reasons"], ) class TestGatingAndReportingAgree(_RepoCase): """AC9: mutation gating and parity reporting resolve the same target.""" def test_same_resolved_target_for_gate_and_report(self): head = _make_repo( self.root, remote="MDCPS", url="https://gitea.example.net/MDCPS/WeeklyBriefings-Meta.git", ) _set_remote_branch(self.root, "MDCPS", "dev", head) _set_remote_head(self.root, "MDCPS", "dev") gate = root_checkout_guard.resolve_remote_master_ref_state(self.root) report = master_parity_gate.assess_target_repository_parity( canonical_root=self.root, source="test" ) self.assertEqual(gate["remote"], report["base_remote"]) self.assertEqual(gate["branch"], report["base_branch"]) self.assertEqual(gate["sha"], report["remote_tracking_head"]) self.assertIn( gate["ref"], (report["tracking_ref"], f"{gate['remote']}/{gate['branch']}"), ) def test_both_sides_refuse_the_same_unresolvable_target(self): _make_repo(self.root, remote=None, url=None) self.assertIsNone(root_checkout_guard.resolve_remote_master_sha(self.root)) report = master_parity_gate.assess_target_repository_parity( canonical_root=self.root, source="test" ) self.assertIsNone(report["remote_tracking_head"]) self.assertFalse(report["stale"]) self.assertTrue(report["reasons"]) class TestProductionCallers(unittest.TestCase): """AC10: every affected production caller supplies/consumes the resolved target.""" def test_guard_reports_the_ref_it_actually_compared(self): assessment = root_checkout_guard.assess_root_checkout_guard( workspace_path="/tmp/nonexistent-workspace-983", canonical_repo_root="/tmp/nonexistent-root-983", current_branch="dev", head_sha="a" * 40, porcelain_status="", remote_master_sha="b" * 40, remote_master_ref="refs/remotes/MDCPS/dev", ) self.assertTrue(assessment["block"]) joined = " ".join(assessment["reasons"]) self.assertIn("refs/remotes/MDCPS/dev", joined) self.assertNotIn("prgs/master", joined) def test_guard_message_without_a_ref_stays_generic(self): assessment = root_checkout_guard.assess_root_checkout_guard( workspace_path="/tmp/nonexistent-workspace-983", canonical_repo_root="/tmp/nonexistent-root-983", current_branch="master", head_sha="a" * 40, porcelain_status="", remote_master_sha="b" * 40, ) joined = " ".join(assessment["reasons"]) self.assertIn("the tracking integration ref", joined) self.assertNotIn("prgs/master", joined) def test_anti_stomp_preflight_forwards_the_resolved_ref(self): sig = inspect.signature(anti_stomp_preflight.assess_anti_stomp_preflight) self.assertIn("remote_master_ref", sig.parameters) src = inspect.getsource(anti_stomp_preflight.assess_anti_stomp_preflight) self.assertIn("remote_master_ref=remote_master_ref", src) def test_no_production_caller_inherits_the_prgs_default(self): """Every resolve site must derive, or pass remote_refs explicitly.""" import gitea_mcp_server src = inspect.getsource(gitea_mcp_server) # The four historical call sites now consume the resolved-target state. self.assertGreaterEqual(src.count("resolve_remote_master_ref_state("), 4) self.assertNotIn("resolve_remote_master_sha(canonical_root)", src) def test_resolver_signature_supports_explicit_remote(self): sig = inspect.signature(root_checkout_guard.resolve_remote_master_sha) self.assertIn("remote", sig.parameters) self.assertIn("remote_refs", sig.parameters) class TestRepositoryStructureUntouched(_RepoCase): """Derivation is strictly read-only: it never writes refs or branches.""" def test_resolution_creates_no_refs_or_branches(self): head = _make_repo( self.root, remote="MDCPS", url="https://gitea.example.net/MDCPS/WeeklyBriefings-Meta.git", ) _set_remote_branch(self.root, "MDCPS", "dev", head) _set_remote_head(self.root, "MDCPS", "dev") fmt = "--format=%(refname) %(objectname)" before = _git(self.root, "for-each-ref", fmt) before_remotes = _git(self.root, "remote") crr.resolve_target_base_ref(self.root) root_checkout_guard.resolve_remote_master_sha(self.root) master_parity_gate.assess_target_repository_parity( canonical_root=self.root, source="test" ) self.assertEqual(_git(self.root, "for-each-ref", fmt), before) self.assertEqual(_git(self.root, "remote"), before_remotes) if __name__ == "__main__": unittest.main()