"""Complete PROJECT_ROOT elimination in cross-repository MCP operations (#741). #706 introduced the immutable ``canonical_repository_root``; #739/#740 routed three consumption paths through it. This module covers the paths #740 left behind, whose shape is uniform: the *filesystem* guards were migrated to the canonical root, but *repository identity* still bottomed out in ``_local_git_remote_url``'s hardcoded ``cwd=PROJECT_ROOT`` — always the Gitea-Tools installation checkout. The consequences asserted here: * **Identity inversion.** ``_resolve``, the #530 remote/repo guard and the anti-stomp org/repo fill all derived the *target* repository from the *install* checkout, so a cross-repository namespace resolved Gitea-Tools coordinates while its branch/parity facts came from the target repo. * **Guard disagreement.** ``_verify_role_mutation_workspace`` omitted ``configured_canonical_root``, so the #274 branches-only / worktree-membership guards validated ``Gitea-Tools/branches/`` rather than the bound target. * **Explicit-coordinate override.** Both-explicit ``org``/``repo`` short-circuit ``assess_remote_repo_match``, so caller coordinates bypassed validation entirely rather than merely *confirming* the binding. * **Configuration fail-open.** ``_flatten_identity`` silently dropped ``canonical_repository_root``, so a v2-``environments`` namespace fell back to the install root; the v1 path never validated the field at all. A role-by-operation matrix drives author, reviewer, merger and reconciler against: correct cross-repository target, wrong repository, wrong worktree, explicit matching coordinates, explicit mismatched coordinates, missing canonical root, invalid canonical root, immutable binding after first bind, and request-override attempts. Real git repositories are used throughout. No network calls are made, no branch is deleted, and no merge is performed. """ from __future__ import annotations import json import os import subprocess import sys import tempfile import unittest from pathlib import Path from unittest.mock import patch sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) import canonical_repository_root as crr # noqa: E402 import gitea_config # noqa: E402 import gitea_mcp_server as srv # noqa: E402 import mcp_server # noqa: E402 import session_context_binding as session_ctx # noqa: E402 INSTALL_ORG = "Scaled-Tech-Consulting" INSTALL_REPO = "Gitea-Tools" INSTALL_SLUG = f"{INSTALL_ORG}/{INSTALL_REPO}" INSTALL_URL = f"https://gitea.prgs.cc/{INSTALL_SLUG}.git" TARGET_ORG = "Scaled-Tech-Consulting" TARGET_REPO = "mcp-control-plane" TARGET_SLUG = f"{TARGET_ORG}/{TARGET_REPO}" TARGET_URL = f"https://gitea.prgs.cc/{TARGET_SLUG}.git" THIRD_REPO = "Timesheet" THIRD_SLUG = f"{INSTALL_ORG}/{THIRD_REPO}" # tests/conftest.py installs an autouse fixture # (mutation_profile_fixture.install_deterministic_remote_urls) that *permanently # reassigns* srv._local_git_remote_url to a stub mapping remote names to fixed # URLs. That stub deliberately ignores the working directory, which is exactly # the behaviour this module must verify — so these tests would silently assert # against the stub rather than production code in a full-suite run. Capture the # genuine implementation at import time (before any fixture executes) and # reinstall it per test. _REAL_LOCAL_GIT_REMOTE_URL = srv._local_git_remote_url def _git(cwd: str, *args: str) -> str: res = subprocess.run( ["git", "-C", cwd, *args], capture_output=True, text=True, check=True ) return res.stdout.strip() def _init_repo(path: Path, remote_url: str, *, remote_name: str = "origin") -> str: path.mkdir(parents=True, exist_ok=True) _git(str(path), "init", "-q") _git(str(path), "config", "user.email", "t@example.com") _git(str(path), "config", "user.name", "Test") _git(str(path), "remote", "add", remote_name, remote_url) # Distinct content per repo: identical seed content, author and timestamp # otherwise produce byte-identical commits and therefore an identical SHA, # which would make the parity-dimension assertions vacuously true. (path / "README.md").write_text(f"seed {remote_url}\n") _git(str(path), "add", "README.md") _git(str(path), "commit", "-q", "-m", f"seed {remote_name}") return os.path.realpath(str(path)) _ROLE_OPS = { "author": ( [ "gitea.read", "gitea.issue.create", "gitea.issue.comment", "gitea.pr.create", "gitea.pr.comment", "gitea.branch.create", "gitea.branch.push", "gitea.repo.commit", ], ["gitea.pr.approve", "gitea.pr.merge", "gitea.pr.request_changes"], ), "reviewer": ( [ "gitea.read", "gitea.pr.approve", "gitea.pr.request_changes", "gitea.pr.comment", "gitea.issue.comment", ], ["gitea.pr.create", "gitea.branch.push", "gitea.pr.merge"], ), "merger": ( ["gitea.read", "gitea.pr.merge", "gitea.pr.comment"], [ "gitea.pr.create", "gitea.branch.push", "gitea.pr.approve", "gitea.pr.request_changes", ], ), "reconciler": ( ["gitea.read", "gitea.pr.close", "gitea.pr.comment", "gitea.branch.delete"], [ "gitea.pr.create", "gitea.branch.push", "gitea.pr.approve", "gitea.pr.merge", "gitea.pr.request_changes", ], ), } ROLES = tuple(_ROLE_OPS) def _profile(role: str, *, canonical_root: str | None = None) -> dict: allowed, forbidden = _ROLE_OPS[role] profile = { "enabled": True, "context": "prgs", "role": role, "username": "jcwalker3", "base_url": "https://gitea.prgs.cc", "auth": {"type": "env", "name": f"GITEA_TOKEN_PRGS_{role.upper()}"}, "allowed_operations": list(allowed), "forbidden_operations": list(forbidden), "execution_profile": f"prgs-{role}", "allowed_repositories": [TARGET_SLUG], } if canonical_root is not None: profile["canonical_repository_root"] = canonical_root return profile def _config(profiles: dict) -> dict: return { "version": 2, "rules": {"allow_runtime_switching": True}, "contexts": { "prgs": { "enabled": True, "gitea": {"enabled": True, "base_url": "https://gitea.prgs.cc"}, } }, "profiles": profiles, } class _CrossRepoHarness(unittest.TestCase): """Real install + target git repos, temp profiles.json, no network.""" def setUp(self): self._dir = tempfile.TemporaryDirectory() self.tmp = self._dir.name self.config_path = os.path.join(self.tmp, "profiles.json") # The real install checkout carries a remote literally named `prgs`; # a freshly cloned target repository normally names its remote `origin`. # Reproducing that asymmetry is the point: identity derivation must not # depend on the remote happening to share the `remote=` argument's name. self.install_root = _init_repo( Path(self.tmp) / "install", INSTALL_URL, remote_name="prgs" ) self.target_root = _init_repo( Path(self.tmp) / "target", TARGET_URL, remote_name="origin" ) self.third_root = _init_repo( Path(self.tmp) / "third", f"https://gitea.prgs.cc/{THIRD_SLUG}.git" ) self.not_a_repo = os.path.join(self.tmp, "plain-dir") os.makedirs(self.not_a_repo, exist_ok=True) session_ctx._reset_session_context_for_testing() gitea_config._active_profile_override = None mcp_server._IDENTITY_CACHE.clear() srv._MUTATION_AUTHORITY = None # PROJECT_ROOT is wherever the server file physically lives; pin it to a # real Gitea-Tools-identified checkout so "install-derived" is # deterministic regardless of the developer's layout. self._project_root = patch.object(srv, "PROJECT_ROOT", self.install_root) self._project_root.start() # Undo the autouse deterministic-remote stub for this module only. self._real_remote_url = patch.object( srv, "_local_git_remote_url", _REAL_LOCAL_GIT_REMOTE_URL ) self._real_remote_url.start() def tearDown(self): self._real_remote_url.stop() self._project_root.stop() session_ctx._reset_session_context_for_testing() gitea_config._active_profile_override = None mcp_server._IDENTITY_CACHE.clear() srv._MUTATION_AUTHORITY = None self._dir.cleanup() def _write_config(self, profiles: dict) -> None: with open(self.config_path, "w", encoding="utf-8") as fh: fh.write(json.dumps(_config(profiles))) def _env(self, role: str, **extra) -> dict: env = { "GITEA_MCP_CONFIG": self.config_path, "GITEA_MCP_PROFILE": f"prgs-{role}", "GITEA_TOKEN_PRGS_AUTHOR": "t", "GITEA_TOKEN_PRGS_REVIEWER": "t", "GITEA_TOKEN_PRGS_MERGER": "t", "GITEA_TOKEN_PRGS_RECONCILER": "t", } env.update(extra) return env def _bind(self, role: str, canonical_root: str | None): """Activate *role* with *canonical_root* and return an env patch ctx.""" self._write_config( {f"prgs-{role}": _profile(role, canonical_root=canonical_root)} ) return patch.dict(os.environ, self._env(role), clear=True) # =========================================================================== # 1. The central helper (single source of root resolution) # =========================================================================== class TestCanonicalLocalGitRoot(_CrossRepoHarness): """_canonical_local_git_root is the one place a target root is derived.""" def test_unconfigured_namespace_uses_installation_root(self): with self._bind("author", None): self.assertEqual(srv._canonical_local_git_root(), self.install_root) def test_configured_namespace_uses_target_root(self): with self._bind("author", self.target_root): self.assertEqual(srv._canonical_local_git_root(), self.target_root) def test_configured_root_resolves_to_git_toplevel(self): nested = os.path.join(self.target_root, "branches", "wt") os.makedirs(nested, exist_ok=True) with self._bind("author", nested): # A subdirectory of the target repo still resolves to its toplevel. self.assertEqual(srv._canonical_local_git_root(), self.target_root) def test_invalid_root_never_silently_becomes_installation_root(self): """A configured-but-broken root must not fall back to Gitea-Tools.""" with self._bind("author", self.not_a_repo): resolved = srv._canonical_local_git_root() self.assertNotEqual(resolved, self.install_root) self.assertEqual(resolved, os.path.realpath(self.not_a_repo)) def test_env_override_beats_profile_binding(self): self._write_config( {"prgs-author": _profile("author", canonical_root=self.third_root)} ) env = self._env("author", GITEA_CANONICAL_REPOSITORY_ROOT=self.target_root) with patch.dict(os.environ, env, clear=True): self.assertEqual(srv._canonical_local_git_root(), self.target_root) # =========================================================================== # 2. Repository identity — the upstream inversion # =========================================================================== class TestRepositoryIdentityDerivation(_CrossRepoHarness): """_local_git_remote_url and its consumers must read the target repo.""" def test_remote_url_reads_target_not_installation(self): with self._bind("author", self.target_root): self.assertEqual(srv._local_git_remote_url("origin"), TARGET_URL) def test_remote_url_unconfigured_still_reads_installation(self): with self._bind("author", None): # The install checkout's remote is named `prgs`, matching production. self.assertEqual(srv._local_git_remote_url("prgs"), INSTALL_URL) def test_workspace_slug_follows_canonical_root(self): with self._bind("author", self.target_root): self.assertEqual(srv._workspace_repository_slug("origin"), TARGET_SLUG) def test_workspace_slug_unconfigured_is_installation(self): with self._bind("author", None): self.assertEqual(srv._workspace_repository_slug("prgs"), INSTALL_SLUG) def test_every_role_derives_the_same_target_identity(self): for role in ROLES: with self.subTest(role=role): with self._bind(role, self.target_root): self.assertEqual( srv._workspace_repository_slug("origin"), TARGET_SLUG ) # =========================================================================== # 3. _resolve — omitted and explicit coordinates # =========================================================================== class TestResolveTargetCoordinates(_CrossRepoHarness): """Omitted coordinates follow the binding; explicit ones may only confirm.""" def test_omitted_coordinates_resolve_to_target_repository(self): for role in ROLES: with self.subTest(role=role): with self._bind(role, self.target_root): _host, org, repo = srv._resolve("prgs", None, None, None) self.assertEqual((org, repo), (TARGET_ORG, TARGET_REPO)) def test_omitted_coordinates_unconfigured_resolve_to_installation(self): with self._bind("author", None): _host, org, repo = srv._resolve("prgs", None, None, None) self.assertEqual((org, repo), (INSTALL_ORG, INSTALL_REPO)) def test_explicit_matching_coordinates_confirm_the_binding(self): for role in ROLES: with self.subTest(role=role): with self._bind(role, self.target_root): _host, org, repo = srv._resolve( "prgs", None, TARGET_ORG, TARGET_REPO ) self.assertEqual((org, repo), (TARGET_ORG, TARGET_REPO)) def test_explicit_mismatched_repository_cannot_override_binding(self): for role in ROLES: with self.subTest(role=role): with self._bind(role, self.target_root): with self.assertRaises(RuntimeError) as ctx: srv._resolve("prgs", None, TARGET_ORG, INSTALL_REPO) msg = str(ctx.exception) self.assertIn("#741", msg) self.assertIn("fail closed", msg) def test_explicit_mismatched_organization_cannot_override_binding(self): with self._bind("author", self.target_root): with self.assertRaises(RuntimeError): srv._resolve("prgs", None, "SomeoneElse", TARGET_REPO) def test_gitea_tools_rooted_namespace_cannot_target_control_plane(self): """Direction 1: an install-rooted namespace must not reach the target.""" with self._bind("author", self.install_root): with self.assertRaises(RuntimeError): srv._resolve("prgs", None, TARGET_ORG, TARGET_REPO) def test_control_plane_rooted_namespace_cannot_target_gitea_tools(self): """Direction 2: a target-rooted namespace must not reach Gitea-Tools.""" with self._bind("author", self.target_root): with self.assertRaises(RuntimeError): srv._resolve("prgs", None, INSTALL_ORG, INSTALL_REPO) def test_unconfigured_namespace_keeps_existing_explicit_behaviour(self): """Same-repository behaviour is unchanged (no new fail-closed path).""" with self._bind("author", None): _host, org, repo = srv._resolve("prgs", None, INSTALL_ORG, INSTALL_REPO) self.assertEqual((org, repo), (INSTALL_ORG, INSTALL_REPO)) # =========================================================================== # 4. #274 workspace guard — the two guard paths must agree # =========================================================================== class TestMutationWorkspaceGuardBinding(_CrossRepoHarness): """Both guard paths must agree on which repository they protect.""" def _contexts(self, worktree: str | None = None): return srv._resolve_namespace_mutation_context(worktree) def test_mutation_context_uses_target_root(self): with self._bind("author", self.target_root): self.assertEqual(self._contexts()["canonical_repo_root"], self.target_root) def test_mutation_context_unconfigured_uses_installation_root(self): with self._bind("author", None): self.assertEqual(self._contexts()["canonical_repo_root"], self.install_root) def test_role_mutation_workspace_guard_agrees_with_mutation_context(self): """The omitted configured_canonical_root made these two disagree.""" import namespace_workspace_binding as nwb for role in ROLES: with self.subTest(role=role): with self._bind(role, self.target_root): configured, _src = srv._configured_canonical_root() assessment = nwb.assess_namespace_mutation_workspace( role_kind=role, worktree_path=None, worktree=None, process_project_root=srv.PROJECT_ROOT, profile_name=f"prgs-{role}", configured_canonical_root=configured, ) self.assertEqual( assessment["canonical_repo_root"], self._contexts()["canonical_repo_root"], ) self.assertEqual( assessment["canonical_repo_root"], self.target_root ) def test_wrong_worktree_is_rejected_against_target_root(self): """A worktree belonging to the install repo is not in the target repo.""" import author_mutation_worktree as amw with self._bind("author", self.target_root): foreign = os.path.join(self.install_root, "branches", "wt") os.makedirs(foreign, exist_ok=True) membership = amw.assess_workspace_repo_membership( workspace_path=foreign, canonical_repo_root=self.target_root, ) self.assertTrue(membership["block"]) # =========================================================================== # 5. Canonical-root validation — missing / invalid / mismatched # =========================================================================== class TestCanonicalRootFailClosed(_CrossRepoHarness): """Missing, invalid, ambiguous and mismatched roots fail closed.""" def _assess(self, value, *, expected=TARGET_SLUG, require=True): return crr.assess_canonical_repository_root( configured_value=value, source="test", expected_slug=expected, process_project_root=self.install_root, remote="origin", require_binding=require, ) def test_missing_root_fails_closed_when_binding_required(self): result = self._assess(None) self.assertTrue(result["block"]) self.assertFalse(result["configured"]) def test_missing_root_is_the_single_repo_default_when_not_required(self): result = self._assess(None, expected=None, require=False) self.assertFalse(result["block"]) self.assertEqual(result["canonical_repo_root"], self.install_root) def test_nonexistent_root_fails_closed(self): result = self._assess(os.path.join(self.tmp, "nope")) self.assertTrue(result["block"]) self.assertIn("does not exist", " ".join(result["reasons"])) def test_non_git_directory_fails_closed(self): result = self._assess(self.not_a_repo) self.assertTrue(result["block"]) self.assertIn("not a git repository", " ".join(result["reasons"])) def test_mismatched_repository_identity_fails_closed(self): result = self._assess(self.third_root) self.assertTrue(result["block"]) self.assertIn("mismatch", " ".join(result["reasons"])) def test_matching_repository_identity_is_proven(self): result = self._assess(self.target_root) self.assertFalse(result["block"]) self.assertEqual(result["resolved_slug"], TARGET_SLUG) self.assertEqual(result["canonical_repo_root"], self.target_root) def test_unresolvable_root_yields_fail_closed_reasons_not_fallback(self): with self._bind("author", self.not_a_repo): slug, reasons = srv._canonical_repository_slug(srv.get_profile(), "origin") self.assertIsNone(slug) self.assertTrue(reasons) # =========================================================================== # 6. Immutability — first bind wins, requests cannot replace the root # =========================================================================== class TestCanonicalRootImmutability(_CrossRepoHarness): """No request-supplied value can establish or swap the pinned root.""" def test_first_bind_pins_target_root(self): with self._bind("author", self.target_root): with patch( "gitea_mcp_server.api_request", side_effect=lambda *a, **k: { "login": "jcwalker3", "full_name": "T", "id": 1, "email": "t@example.com", }, ): srv.gitea_whoami(remote="prgs") bound = session_ctx.get_session_context() self.assertEqual(bound["canonical_repository_root"], self.target_root) def test_binding_is_first_write_wins(self): with self._bind("author", self.target_root): session_ctx.seed_session_context_if_unbound( profile_name="prgs-author", remote="prgs", host="gitea.prgs.cc", identity="jcwalker3", org=TARGET_ORG, repository=TARGET_REPO, role_kind="author", source="test", canonical_repository_root=self.target_root, ) # A second, contradictory seed must not replace the pin. session_ctx.seed_session_context_if_unbound( profile_name="prgs-author", remote="prgs", host="gitea.prgs.cc", identity="jcwalker3", org=INSTALL_ORG, repository=INSTALL_REPO, role_kind="author", source="test-2", canonical_repository_root=self.install_root, ) bound = session_ctx.get_session_context() self.assertEqual(bound["canonical_repository_root"], self.target_root) self.assertEqual(bound["repository"], TARGET_REPO) def test_request_supplied_worktree_cannot_replace_the_root(self): with self._bind("author", self.target_root): ctx = srv._resolve_namespace_mutation_context(self.install_root) # The workspace argument may be demoted/inspected, but the canonical # repository root stays the configured target. self.assertEqual(ctx["canonical_repo_root"], self.target_root) def test_request_supplied_coordinates_cannot_replace_the_root(self): with self._bind("author", self.target_root): with self.assertRaises(RuntimeError): srv._resolve("prgs", None, INSTALL_ORG, INSTALL_REPO) self.assertEqual(srv._canonical_local_git_root(), self.target_root) # =========================================================================== # 7. Parity dimensions stay separately labelled (#739 F3 preserved) # =========================================================================== class TestParityDimensionSeparation(_CrossRepoHarness): """Server-implementation parity stays anchored to the install checkout.""" def test_server_dimension_is_installation_not_target(self): import master_parity_gate with self._bind("author", self.target_root): head = master_parity_gate.read_git_head(srv.PROJECT_ROOT) self.assertEqual(head, _git(self.install_root, "rev-parse", "HEAD")) self.assertNotEqual(head, _git(self.target_root, "rev-parse", "HEAD")) def test_target_dimension_reads_the_target_checkout(self): with self._bind("author", self.target_root): self.assertEqual( _git(srv._canonical_local_git_root(), "rev-parse", "HEAD"), _git(self.target_root, "rev-parse", "HEAD"), ) # =========================================================================== # 8. Configuration loaders validate the binding consistently (AC13) # =========================================================================== class TestConfigurationLoaderValidation(unittest.TestCase): """Every supported loader treats canonical_repository_root identically.""" def setUp(self): self._dir = tempfile.TemporaryDirectory() self.tmp = self._dir.name def tearDown(self): self._dir.cleanup() def _write(self, data: dict) -> str: path = os.path.join(self.tmp, "profiles.json") with open(path, "w", encoding="utf-8") as fh: fh.write(json.dumps(data)) return path # --- v1 --------------------------------------------------------------- def _v1(self, root): return { "version": 1, "profiles": { "prgs-author": { "base_url": "https://gitea.prgs.cc", "auth": {"type": "env", "name": "GITEA_TOKEN"}, "canonical_repository_root": root, } }, } def test_v1_rejects_relative_canonical_root(self): path = self._write(self._v1("relative/path")) with self.assertRaises(gitea_config.ConfigError) as ctx: gitea_config.load_config(path) self.assertIn("absolute", str(ctx.exception)) def test_v1_rejects_blank_canonical_root(self): path = self._write(self._v1(" ")) with self.assertRaises(gitea_config.ConfigError): gitea_config.load_config(path) def test_v1_accepts_absolute_canonical_root(self): path = self._write(self._v1("/abs/target")) loaded = gitea_config.load_config(path) self.assertEqual( loaded["profiles"]["prgs-author"]["canonical_repository_root"], "/abs/target", ) def test_v1_without_the_field_is_unchanged(self): data = self._v1("/abs/target") del data["profiles"]["prgs-author"]["canonical_repository_root"] loaded = gitea_config.load_config(self._write(data)) self.assertNotIn("canonical_repository_root", loaded["profiles"]["prgs-author"]) # --- v2 environments -------------------------------------------------- def _v2_env(self, root): ident = { "auth": {"type": "env", "name": "GITEA_TOKEN"}, "base_url": "https://gitea.prgs.cc", "username": "jcwalker3", "allowed_operations": ["gitea.read"], } if root is not None: ident["canonical_repository_root"] = root return { "version": 2, "environments": { "prgs": {"services": {"gitea": {"identities": {"author": ident}}}} }, } def test_v2_environments_propagates_canonical_root(self): """Regression: the field was silently dropped during flattening.""" loaded = gitea_config.load_config(self._write(self._v2_env("/abs/target"))) profile = loaded["profiles"]["prgs.gitea.author"] self.assertEqual(profile["canonical_repository_root"], "/abs/target") def test_v2_environments_rejects_relative_canonical_root(self): with self.assertRaises(gitea_config.ConfigError) as ctx: gitea_config.load_config(self._write(self._v2_env("relative/path"))) self.assertIn("absolute", str(ctx.exception)) def test_v2_environments_without_the_field_is_unchanged(self): loaded = gitea_config.load_config(self._write(self._v2_env(None))) self.assertNotIn( "canonical_repository_root", loaded["profiles"]["prgs.gitea.author"] ) # --- v2 contexts (already validated; guard against regression) --------- def test_v2_contexts_still_rejects_relative_canonical_root(self): data = { "version": 2, "contexts": { "prgs": { "enabled": True, "gitea": {"enabled": True, "base_url": "https://gitea.prgs.cc"}, } }, "profiles": { "prgs-author": { "enabled": True, "context": "prgs", "username": "jcwalker3", "auth": {"type": "env", "name": "GITEA_TOKEN"}, "allowed_operations": ["gitea.read"], "canonical_repository_root": "relative/path", } }, } with self.assertRaises(gitea_config.ConfigError): gitea_config.load_config(self._write(data)) # =========================================================================== # 9. Role-by-operation matrix # =========================================================================== class TestRoleOperationMatrix(_CrossRepoHarness): """Each role, each cross-repository condition, one assertion per cell.""" def test_correct_target_resolves_for_every_role(self): for role in ROLES: with self.subTest(role=role, case="correct-target"): with self._bind(role, self.target_root): _h, org, repo = srv._resolve("prgs", None, None, None) self.assertEqual((org, repo), (TARGET_ORG, TARGET_REPO)) def test_wrong_repository_is_rejected_for_every_role(self): for role in ROLES: with self.subTest(role=role, case="wrong-repo"): with self._bind(role, self.target_root): with self.assertRaises(RuntimeError): srv._resolve("prgs", None, INSTALL_ORG, INSTALL_REPO) def test_explicit_matching_coordinates_pass_for_every_role(self): for role in ROLES: with self.subTest(role=role, case="explicit-match"): with self._bind(role, self.target_root): _h, org, repo = srv._resolve("prgs", None, TARGET_ORG, TARGET_REPO) self.assertEqual((org, repo), (TARGET_ORG, TARGET_REPO)) def test_explicit_mismatch_fails_closed_for_every_role(self): for role in ROLES: with self.subTest(role=role, case="explicit-mismatch"): with self._bind(role, self.target_root): with self.assertRaises(RuntimeError): srv._resolve("prgs", None, INSTALL_ORG, THIRD_REPO) def test_missing_canonical_root_preserves_single_repo_default(self): for role in ROLES: with self.subTest(role=role, case="missing-root"): with self._bind(role, None): self.assertEqual(srv._canonical_local_git_root(), self.install_root) def test_invalid_canonical_root_never_becomes_install_root(self): for role in ROLES: with self.subTest(role=role, case="invalid-root"): with self._bind(role, self.not_a_repo): self.assertNotEqual( srv._canonical_local_git_root(), self.install_root ) def test_wrong_worktree_rejected_for_every_role(self): import author_mutation_worktree as amw foreign = os.path.join(self.install_root, "branches", "foreign") os.makedirs(foreign, exist_ok=True) for role in ROLES: with self.subTest(role=role, case="wrong-worktree"): with self._bind(role, self.target_root): membership = amw.assess_workspace_repo_membership( workspace_path=foreign, canonical_repo_root=srv._canonical_local_git_root(), ) self.assertTrue(membership["block"]) def test_request_override_attempt_rejected_for_every_role(self): for role in ROLES: with self.subTest(role=role, case="request-override"): with self._bind(role, self.target_root): with self.assertRaises(RuntimeError): srv._resolve("prgs", None, "Attacker", "evil-repo") self.assertEqual(srv._canonical_local_git_root(), self.target_root) if __name__ == "__main__": unittest.main()