From 1a13fcaa4e7d710a5d1dd355b511149cfbc8c96f Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Mon, 15 Dec 2025 00:16:04 +0100 Subject: [PATCH] refactor(mirror): enforce primary origin URL and align mirror resolution logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Resolve primary remote via RepoMirrorContext (origin → file order → config → default) - Always set origin fetch and push URL to primary - Add additional mirrors as extra push URLs without duplication - Update remote provisioning and setup commands to use context-based resolution - Adjust and extend unit tests to cover new origin/push behavior https://chatgpt.com/share/693f4538-42d4-800f-98c2-2ec264fd2e19 --- src/pkgmgr/actions/mirror/git_remote.py | 138 +++++---------- src/pkgmgr/actions/mirror/remote_provision.py | 29 +--- src/pkgmgr/actions/mirror/setup_cmd.py | 62 +++---- .../pkgmgr/actions/mirror/test_git_remote.py | 149 +++++----------- .../mirror/test_git_remote_primary_push.py | 50 ++++++ .../pkgmgr/actions/mirror/test_setup_cmd.py | 162 ++++++++---------- 6 files changed, 246 insertions(+), 344 deletions(-) create mode 100644 tests/unit/pkgmgr/actions/mirror/test_git_remote_primary_push.py diff --git a/src/pkgmgr/actions/mirror/git_remote.py b/src/pkgmgr/actions/mirror/git_remote.py index c02944f..15821f2 100644 --- a/src/pkgmgr/actions/mirror/git_remote.py +++ b/src/pkgmgr/actions/mirror/git_remote.py @@ -1,20 +1,15 @@ from __future__ import annotations import os +from typing import List, Optional, Set from pkgmgr.core.command.run import run_command from pkgmgr.core.git import GitError, run_git -from typing import List, Optional, Set from .types import MirrorMap, RepoMirrorContext, Repository def build_default_ssh_url(repo: Repository) -> Optional[str]: - """ - Build a simple SSH URL from repo config if no explicit mirror is defined. - - Example: git@github.com:account/repository.git - """ provider = repo.get("provider") account = repo.get("account") name = repo.get("repository") @@ -23,95 +18,82 @@ def build_default_ssh_url(repo: Repository) -> Optional[str]: if not provider or not account or not name: return None - provider = str(provider) - account = str(account) - name = str(name) - if port: return f"ssh://git@{provider}:{port}/{account}/{name}.git" - # GitHub-style shorthand return f"git@{provider}:{account}/{name}.git" def determine_primary_remote_url( repo: Repository, - resolved_mirrors: MirrorMap, + ctx: RepoMirrorContext, ) -> Optional[str]: """ - Determine the primary remote URL in a consistent way: - - 1. resolved_mirrors["origin"] - 2. any resolved mirror (first by name) - 3. default SSH URL from provider/account/repository + Priority order: + 1. origin from resolved mirrors + 2. MIRRORS file order + 3. config mirrors order + 4. default SSH URL """ - if "origin" in resolved_mirrors: - return resolved_mirrors["origin"] + resolved = ctx.resolved_mirrors - if resolved_mirrors: - first_name = sorted(resolved_mirrors.keys())[0] - return resolved_mirrors[first_name] + if resolved.get("origin"): + return resolved["origin"] + + for mirrors in (ctx.file_mirrors, ctx.config_mirrors): + for _, url in mirrors.items(): + if url: + return url return build_default_ssh_url(repo) def _safe_git_output(args: List[str], cwd: str) -> Optional[str]: - """ - Run a Git command via run_git and return its stdout, or None on failure. - """ try: return run_git(args, cwd=cwd) except GitError: return None -def current_origin_url(repo_dir: str) -> Optional[str]: - """ - Return the current URL for remote 'origin', or None if not present. - """ - output = _safe_git_output(["remote", "get-url", "origin"], cwd=repo_dir) - if not output: - return None - url = output.strip() - return url or None - - def has_origin_remote(repo_dir: str) -> bool: - """ - Check whether a remote called 'origin' exists in the repository. - """ - output = _safe_git_output(["remote"], cwd=repo_dir) - if not output: - return False - names = output.split() - return "origin" in names + out = _safe_git_output(["remote"], cwd=repo_dir) + return bool(out and "origin" in out.split()) -def _ensure_push_urls_for_origin( +def _set_origin_fetch_and_push(repo_dir: str, url: str, preview: bool) -> None: + fetch = f"git remote set-url origin {url}" + push = f"git remote set-url --push origin {url}" + + if preview: + print(f"[PREVIEW] Would run in {repo_dir!r}: {fetch}") + print(f"[PREVIEW] Would run in {repo_dir!r}: {push}") + return + + run_command(fetch, cwd=repo_dir, preview=False) + run_command(push, cwd=repo_dir, preview=False) + + +def _ensure_additional_push_urls( repo_dir: str, mirrors: MirrorMap, + primary: str, preview: bool, ) -> None: - """ - Ensure that all mirror URLs are present as push URLs on 'origin'. - """ - desired: Set[str] = {url for url in mirrors.values() if url} + desired: Set[str] = {u for u in mirrors.values() if u and u != primary} if not desired: return - existing_output = _safe_git_output( + out = _safe_git_output( ["remote", "get-url", "--push", "--all", "origin"], cwd=repo_dir, ) - existing = set(existing_output.splitlines()) if existing_output else set() + existing = set(out.splitlines()) if out else set() - missing = sorted(desired - existing) - for url in missing: + for url in sorted(desired - existing): cmd = f"git remote set-url --add --push origin {url}" if preview: print(f"[PREVIEW] Would run in {repo_dir!r}: {cmd}") else: - print(f"[INFO] Adding push URL to 'origin': {url}") run_command(cmd, cwd=repo_dir, preview=False) @@ -120,60 +102,32 @@ def ensure_origin_remote( ctx: RepoMirrorContext, preview: bool, ) -> None: - """ - Ensure that a usable 'origin' remote exists and has all push URLs. - """ repo_dir = ctx.repo_dir - resolved_mirrors = ctx.resolved_mirrors if not os.path.isdir(os.path.join(repo_dir, ".git")): - print(f"[WARN] {repo_dir} is not a Git repository (no .git directory).") + print(f"[WARN] {repo_dir} is not a Git repository.") return - url = determine_primary_remote_url(repo, resolved_mirrors) + primary = determine_primary_remote_url(repo, ctx) + if not primary: + print("[WARN] No primary mirror URL could be determined.") + return if not has_origin_remote(repo_dir): - if not url: - print( - "[WARN] Could not determine URL for 'origin' remote. " - "Please configure mirrors or provider/account/repository." - ) - return - - cmd = f"git remote add origin {url}" + cmd = f"git remote add origin {primary}" if preview: print(f"[PREVIEW] Would run in {repo_dir!r}: {cmd}") else: - print(f"[INFO] Adding 'origin' remote in {repo_dir}: {url}") run_command(cmd, cwd=repo_dir, preview=False) - else: - current = current_origin_url(repo_dir) - if current == url or not url: - print( - "[INFO] 'origin' already points to " - f"{current or ''} (no change needed)." - ) - else: - # We do not auto-change origin here, only log the mismatch. - print( - "[INFO] 'origin' exists with URL " - f"{current or ''}; not changing to {url}." - ) - # Ensure all mirrors are present as push URLs - _ensure_push_urls_for_origin(repo_dir, resolved_mirrors, preview) + _set_origin_fetch_and_push(repo_dir, primary, preview) + + _ensure_additional_push_urls(repo_dir, ctx.resolved_mirrors, primary, preview) def is_remote_reachable(url: str, cwd: Optional[str] = None) -> bool: - """ - Check whether a remote repository is reachable via `git ls-remote`. - - This does NOT modify anything; it only probes the remote. - """ - workdir = cwd or os.getcwd() try: - # --exit-code → non-zero exit code if the remote does not exist - run_git(["ls-remote", "--exit-code", url], cwd=workdir) + run_git(["ls-remote", "--exit-code", url], cwd=cwd or os.getcwd()) return True except GitError: return False diff --git a/src/pkgmgr/actions/mirror/remote_provision.py b/src/pkgmgr/actions/mirror/remote_provision.py index d24cca1..75fadf5 100644 --- a/src/pkgmgr/actions/mirror/remote_provision.py +++ b/src/pkgmgr/actions/mirror/remote_provision.py @@ -1,4 +1,3 @@ -# src/pkgmgr/actions/mirror/remote_provision.py from __future__ import annotations from typing import List @@ -19,36 +18,28 @@ def ensure_remote_repository( preview: bool, ) -> None: ctx = build_context(repo, repositories_base_dir, all_repos) - resolved_mirrors = ctx.resolved_mirrors - primary_url = determine_primary_remote_url(repo, resolved_mirrors) + primary_url = determine_primary_remote_url(repo, ctx) if not primary_url: - print("[INFO] No remote URL could be derived; skipping remote provisioning.") + print("[INFO] No primary URL found; skipping remote provisioning.") return - host_raw, owner_from_url, name_from_url = parse_repo_from_git_url(primary_url) + host_raw, owner, name = parse_repo_from_git_url(primary_url) host = normalize_provider_host(host_raw) - if not host or not owner_from_url or not name_from_url: - print("[WARN] Could not derive host/owner/repository from URL; cannot ensure remote repo.") - print(f" url={primary_url!r}") - print(f" host={host!r}, owner={owner_from_url!r}, repository={name_from_url!r}") + if not host or not owner or not name: + print("[WARN] Could not parse remote URL:", primary_url) return - print("------------------------------------------------------------") - print(f"[REMOTE ENSURE] {ctx.identifier}") - print(f"[REMOTE ENSURE] host: {host}") - print("------------------------------------------------------------") - spec = RepoSpec( - host=str(host), - owner=str(owner_from_url), - name=str(name_from_url), + host=host, + owner=owner, + name=name, private=bool(repo.get("private", True)), description=str(repo.get("description", "")), ) - provider_kind = str(repo.get("provider", "")).strip().lower() or None + provider_kind = str(repo.get("provider", "")).lower() or None try: result = ensure_remote_repo( @@ -66,5 +57,3 @@ def ensure_remote_repository( print(f"[REMOTE ENSURE] URL: {result.url}") except Exception as exc: # noqa: BLE001 print(f"[ERROR] Remote provisioning failed: {exc}") - - print() diff --git a/src/pkgmgr/actions/mirror/setup_cmd.py b/src/pkgmgr/actions/mirror/setup_cmd.py index 8ad0584..37a63c9 100644 --- a/src/pkgmgr/actions/mirror/setup_cmd.py +++ b/src/pkgmgr/actions/mirror/setup_cmd.py @@ -1,4 +1,3 @@ -# src/pkgmgr/actions/mirror/setup_cmd.py from __future__ import annotations from typing import List @@ -9,6 +8,7 @@ from .remote_check import probe_mirror from .remote_provision import ensure_remote_repository from .types import Repository + def _setup_local_mirrors_for_repo( repo: Repository, repositories_base_dir: str, @@ -22,7 +22,7 @@ def _setup_local_mirrors_for_repo( print(f"[MIRROR SETUP:LOCAL] dir: {ctx.repo_dir}") print("------------------------------------------------------------") - ensure_origin_remote(repo, ctx, preview=preview) + ensure_origin_remote(repo, ctx, preview) print() @@ -34,7 +34,6 @@ def _setup_remote_mirrors_for_repo( ensure_remote: bool, ) -> None: ctx = build_context(repo, repositories_base_dir, all_repos) - resolved_mirrors = ctx.resolved_mirrors print("------------------------------------------------------------") print(f"[MIRROR SETUP:REMOTE] {ctx.identifier}") @@ -44,37 +43,28 @@ def _setup_remote_mirrors_for_repo( if ensure_remote: ensure_remote_repository( repo, - repositories_base_dir=repositories_base_dir, - all_repos=all_repos, - preview=preview, + repositories_base_dir, + all_repos, + preview, ) - if not resolved_mirrors: - primary_url = determine_primary_remote_url(repo, resolved_mirrors) - if not primary_url: - print("[INFO] No mirrors configured and no primary URL available.") - print() + if not ctx.resolved_mirrors: + primary = determine_primary_remote_url(repo, ctx) + if not primary: return - ok, error_message = probe_mirror(primary_url, ctx.repo_dir) - if ok: - print(f"[OK] primary: {primary_url}") - else: - print(f"[WARN] primary: {primary_url}") - for line in error_message.splitlines(): - print(f" {line}") - + ok, msg = probe_mirror(primary, ctx.repo_dir) + print("[OK]" if ok else "[WARN]", primary) + if msg: + print(msg) print() return - for name, url in sorted(resolved_mirrors.items()): - ok, error_message = probe_mirror(url, ctx.repo_dir) - if ok: - print(f"[OK] {name}: {url}") - else: - print(f"[WARN] {name}: {url}") - for line in error_message.splitlines(): - print(f" {line}") + for name, url in ctx.resolved_mirrors.items(): + ok, msg = probe_mirror(url, ctx.repo_dir) + print(f"[OK] {name}: {url}" if ok else f"[WARN] {name}: {url}") + if msg: + print(msg) print() @@ -91,17 +81,17 @@ def setup_mirrors( for repo in selected_repos: if local: _setup_local_mirrors_for_repo( - repo=repo, - repositories_base_dir=repositories_base_dir, - all_repos=all_repos, - preview=preview, + repo, + repositories_base_dir, + all_repos, + preview, ) if remote: _setup_remote_mirrors_for_repo( - repo=repo, - repositories_base_dir=repositories_base_dir, - all_repos=all_repos, - preview=preview, - ensure_remote=ensure_remote, + repo, + repositories_base_dir, + all_repos, + preview, + ensure_remote, ) diff --git a/tests/unit/pkgmgr/actions/mirror/test_git_remote.py b/tests/unit/pkgmgr/actions/mirror/test_git_remote.py index 1f3cc5e..a2725d5 100644 --- a/tests/unit/pkgmgr/actions/mirror/test_git_remote.py +++ b/tests/unit/pkgmgr/actions/mirror/test_git_remote.py @@ -1,6 +1,3 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - from __future__ import annotations import unittest @@ -9,117 +6,61 @@ from unittest.mock import patch from pkgmgr.actions.mirror.git_remote import ( build_default_ssh_url, determine_primary_remote_url, - current_origin_url, has_origin_remote, ) -from pkgmgr.actions.mirror.types import MirrorMap, Repository +from pkgmgr.actions.mirror.types import RepoMirrorContext class TestMirrorGitRemote(unittest.TestCase): - """ - Unit tests for SSH URL and primary remote selection logic. - """ + def _ctx(self, *, file=None, config=None) -> RepoMirrorContext: + return RepoMirrorContext( + identifier="repo", + repo_dir="/tmp/repo", + config_mirrors=config or {}, + file_mirrors=file or {}, + ) - def test_build_default_ssh_url_without_port(self) -> None: - repo: Repository = { + def test_build_default_ssh_url(self) -> None: + repo = { "provider": "github.com", - "account": "kevinveenbirkenbach", - "repository": "package-manager", + "account": "alice", + "repository": "repo", } + self.assertEqual( + build_default_ssh_url(repo), + "git@github.com:alice/repo.git", + ) - url = build_default_ssh_url(repo) - self.assertEqual(url, "git@github.com:kevinveenbirkenbach/package-manager.git") + def test_determine_primary_prefers_origin(self) -> None: + repo = {"provider": "github.com", "account": "alice", "repository": "repo"} + ctx = self._ctx(config={"origin": "git@github.com:alice/repo.git"}) + self.assertEqual( + determine_primary_remote_url(repo, ctx), + "git@github.com:alice/repo.git", + ) - def test_build_default_ssh_url_with_port(self) -> None: - repo: Repository = { - "provider": "code.cymais.cloud", - "account": "kevinveenbirkenbach", - "repository": "pkgmgr", - "port": 2201, - } + def test_determine_primary_uses_file_order(self) -> None: + repo = {"provider": "github.com", "account": "alice", "repository": "repo"} + ctx = self._ctx( + file={ + "first": "git@a/first.git", + "second": "git@a/second.git", + } + ) + self.assertEqual( + determine_primary_remote_url(repo, ctx), + "git@a/first.git", + ) - url = build_default_ssh_url(repo) - self.assertEqual(url, "ssh://git@code.cymais.cloud:2201/kevinveenbirkenbach/pkgmgr.git") + def test_determine_primary_fallback_default(self) -> None: + repo = {"provider": "github.com", "account": "alice", "repository": "repo"} + ctx = self._ctx() + self.assertEqual( + determine_primary_remote_url(repo, ctx), + "git@github.com:alice/repo.git", + ) - def test_build_default_ssh_url_missing_fields_returns_none(self) -> None: - repo: Repository = { - "provider": "github.com", - "account": "kevinveenbirkenbach", - } - - url = build_default_ssh_url(repo) - self.assertIsNone(url) - - def test_determine_primary_remote_url_prefers_origin_in_resolved_mirrors(self) -> None: - repo: Repository = { - "provider": "github.com", - "account": "kevinveenbirkenbach", - "repository": "package-manager", - } - mirrors: MirrorMap = { - "origin": "git@github.com:kevinveenbirkenbach/package-manager.git", - "backup": "ssh://git@git.veen.world:2201/kevinveenbirkenbach/pkgmgr.git", - } - - url = determine_primary_remote_url(repo, mirrors) - self.assertEqual(url, "git@github.com:kevinveenbirkenbach/package-manager.git") - - def test_determine_primary_remote_url_uses_any_mirror_if_no_origin(self) -> None: - repo: Repository = { - "provider": "github.com", - "account": "kevinveenbirkenbach", - "repository": "package-manager", - } - mirrors: MirrorMap = { - "backup": "ssh://git@git.veen.world:2201/kevinveenbirkenbach/pkgmgr.git", - "mirror2": "ssh://git@code.cymais.cloud:2201/kevinveenbirkenbach/pkgmgr.git", - } - - url = determine_primary_remote_url(repo, mirrors) - self.assertEqual(url, "ssh://git@git.veen.world:2201/kevinveenbirkenbach/pkgmgr.git") - - def test_determine_primary_remote_url_falls_back_to_default_ssh(self) -> None: - repo: Repository = { - "provider": "github.com", - "account": "kevinveenbirkenbach", - "repository": "package-manager", - } - mirrors: MirrorMap = {} - - url = determine_primary_remote_url(repo, mirrors) - self.assertEqual(url, "git@github.com:kevinveenbirkenbach/package-manager.git") - - @patch("pkgmgr.actions.mirror.git_remote.run_git") - def test_current_origin_url_returns_value(self, mock_run_git) -> None: - mock_run_git.return_value = "git@github.com:alice/repo.git\n" - self.assertEqual(current_origin_url("/tmp/repo"), "git@github.com:alice/repo.git") - mock_run_git.assert_called_once_with(["remote", "get-url", "origin"], cwd="/tmp/repo") - - @patch("pkgmgr.actions.mirror.git_remote.run_git") - def test_current_origin_url_returns_none_on_git_error(self, mock_run_git) -> None: - from pkgmgr.core.git import GitError - - mock_run_git.side_effect = GitError("fail") - self.assertIsNone(current_origin_url("/tmp/repo")) - - @patch("pkgmgr.actions.mirror.git_remote.run_git") - def test_has_origin_remote_true(self, mock_run_git) -> None: - mock_run_git.return_value = "origin\nupstream\n" + @patch("pkgmgr.actions.mirror.git_remote._safe_git_output") + def test_has_origin_remote(self, m_out) -> None: + m_out.return_value = "origin\nupstream\n" self.assertTrue(has_origin_remote("/tmp/repo")) - mock_run_git.assert_called_once_with(["remote"], cwd="/tmp/repo") - - @patch("pkgmgr.actions.mirror.git_remote.run_git") - def test_has_origin_remote_false_on_missing_remote(self, mock_run_git) -> None: - mock_run_git.return_value = "upstream\n" - self.assertFalse(has_origin_remote("/tmp/repo")) - - @patch("pkgmgr.actions.mirror.git_remote.run_git") - def test_has_origin_remote_false_on_git_error(self, mock_run_git) -> None: - from pkgmgr.core.git import GitError - - mock_run_git.side_effect = GitError("fail") - self.assertFalse(has_origin_remote("/tmp/repo")) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/unit/pkgmgr/actions/mirror/test_git_remote_primary_push.py b/tests/unit/pkgmgr/actions/mirror/test_git_remote_primary_push.py new file mode 100644 index 0000000..8c21c3a --- /dev/null +++ b/tests/unit/pkgmgr/actions/mirror/test_git_remote_primary_push.py @@ -0,0 +1,50 @@ +from __future__ import annotations + +import unittest +from unittest.mock import patch + +from pkgmgr.actions.mirror.git_remote import ensure_origin_remote +from pkgmgr.actions.mirror.types import RepoMirrorContext + + +class TestGitRemotePrimaryPush(unittest.TestCase): + def test_origin_created_and_extra_push_added(self) -> None: + repo = {"provider": "github.com", "account": "alice", "repository": "repo"} + ctx = RepoMirrorContext( + identifier="repo", + repo_dir="/tmp/repo", + config_mirrors={}, + file_mirrors={ + "primary": "git@github.com:alice/repo.git", + "backup": "git@github.com:alice/repo-backup.git", + }, + ) + + executed: list[str] = [] + + def fake_run(cmd: str, cwd: str, preview: bool) -> None: + executed.append(cmd) + + def fake_git(args, cwd): + if args == ["remote"]: + return "" + if args == ["remote", "get-url", "--push", "--all", "origin"]: + return "git@github.com:alice/repo.git\n" + return "" + + with patch("os.path.isdir", return_value=True), patch( + "pkgmgr.actions.mirror.git_remote.run_command", side_effect=fake_run + ), patch( + "pkgmgr.actions.mirror.git_remote._safe_git_output", side_effect=fake_git + ): + ensure_origin_remote(repo, ctx, preview=False) + + self.assertEqual( + executed, + [ + "git remote add origin git@github.com:alice/repo.git", + "git remote set-url origin git@github.com:alice/repo.git", + "git remote set-url --push origin git@github.com:alice/repo.git", + "git remote set-url --add --push origin git@github.com:alice/repo-backup.git", + ], + ) diff --git a/tests/unit/pkgmgr/actions/mirror/test_setup_cmd.py b/tests/unit/pkgmgr/actions/mirror/test_setup_cmd.py index 884c280..ffd47b4 100644 --- a/tests/unit/pkgmgr/actions/mirror/test_setup_cmd.py +++ b/tests/unit/pkgmgr/actions/mirror/test_setup_cmd.py @@ -1,123 +1,101 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - from __future__ import annotations import unittest -from unittest.mock import MagicMock, PropertyMock, patch +from unittest.mock import patch from pkgmgr.actions.mirror.setup_cmd import setup_mirrors +from pkgmgr.actions.mirror.types import RepoMirrorContext class TestMirrorSetupCmd(unittest.TestCase): - """ - Unit tests for mirror setup orchestration (local + remote). - """ - - @patch("pkgmgr.actions.mirror.setup_cmd.ensure_origin_remote") - @patch("pkgmgr.actions.mirror.setup_cmd.build_context") - def test_setup_mirrors_local_calls_ensure_origin_remote( + def _ctx( self, - mock_build_context, - mock_ensure_origin, - ) -> None: - ctx = MagicMock() - ctx.identifier = "repo-id" - ctx.repo_dir = "/tmp/repo" - ctx.config_mirrors = {} - ctx.file_mirrors = {} - type(ctx).resolved_mirrors = PropertyMock(return_value={}) - mock_build_context.return_value = ctx + *, + repo_dir: str = "/tmp/repo", + resolved: dict[str, str] | None = None, + ) -> RepoMirrorContext: + # RepoMirrorContext derives resolved via property (config + file) + # We feed mirrors via file_mirrors to keep insertion order realistic. + return RepoMirrorContext( + identifier="repo-id", + repo_dir=repo_dir, + config_mirrors={}, + file_mirrors=resolved or {}, + ) - repo = {"provider": "github.com", "account": "alice", "repository": "repo"} + @patch("pkgmgr.actions.mirror.setup_cmd.build_context") + @patch("pkgmgr.actions.mirror.setup_cmd.ensure_origin_remote") + def test_setup_mirrors_local_calls_ensure_origin_remote(self, m_ensure, m_ctx) -> None: + m_ctx.return_value = self._ctx(repo_dir="/tmp/repo", resolved={"primary": "git@x/y.git"}) + repos = [{"provider": "github.com", "account": "alice", "repository": "repo"}] setup_mirrors( - selected_repos=[repo], - repositories_base_dir="/base", - all_repos=[repo], + selected_repos=repos, + repositories_base_dir="/tmp", + all_repos=repos, preview=True, local=True, remote=False, ensure_remote=False, ) - mock_ensure_origin.assert_called_once() - args, kwargs = mock_ensure_origin.call_args - self.assertEqual(args[0], repo) - self.assertEqual(kwargs.get("preview"), True) + self.assertEqual(m_ensure.call_count, 1) + args, kwargs = m_ensure.call_args + + # ensure_origin_remote(repo, ctx, preview) may be positional or kw. + # Accept both to avoid coupling tests to call style. + if "preview" in kwargs: + self.assertTrue(kwargs["preview"]) + else: + # args: (repo, ctx, preview) + self.assertTrue(args[2]) - @patch("pkgmgr.actions.mirror.setup_cmd.ensure_remote_repository") - @patch("pkgmgr.actions.mirror.setup_cmd.probe_mirror") @patch("pkgmgr.actions.mirror.setup_cmd.build_context") - def test_setup_mirrors_remote_provisions_when_enabled( - self, - mock_build_context, - mock_probe, - mock_ensure_remote_repository, - ) -> None: - ctx = MagicMock() - ctx.identifier = "repo-id" - ctx.repo_dir = "/tmp/repo" - ctx.config_mirrors = {"origin": "git@github.com:alice/repo.git"} - ctx.file_mirrors = {} - type(ctx).resolved_mirrors = PropertyMock(return_value={"origin": "git@github.com:alice/repo.git"}) - mock_build_context.return_value = ctx - - mock_probe.return_value = (True, "") - - repo = {"provider": "github.com", "account": "alice", "repository": "repo"} - - setup_mirrors( - selected_repos=[repo], - repositories_base_dir="/base", - all_repos=[repo], - preview=False, - local=False, - remote=True, - ensure_remote=True, - ) - - mock_ensure_remote_repository.assert_called_once() - mock_probe.assert_called_once() - - @patch("pkgmgr.actions.mirror.setup_cmd.ensure_remote_repository") @patch("pkgmgr.actions.mirror.setup_cmd.probe_mirror") - @patch("pkgmgr.actions.mirror.setup_cmd.build_context") - def test_setup_mirrors_remote_probes_all_resolved_mirrors( - self, - mock_build_context, - mock_probe, - mock_ensure_remote_repository, - ) -> None: - ctx = MagicMock() - ctx.identifier = "repo-id" - ctx.repo_dir = "/tmp/repo" - ctx.config_mirrors = {} - ctx.file_mirrors = {} - type(ctx).resolved_mirrors = PropertyMock( - return_value={ - "mirror": "git@github.com:alice/repo.git", - "backup": "ssh://git@git.veen.world:2201/alice/repo.git", - } - ) - mock_build_context.return_value = ctx - - mock_probe.return_value = (True, "") - - repo = {"provider": "github.com", "account": "alice", "repository": "repo"} + @patch("pkgmgr.actions.mirror.setup_cmd.determine_primary_remote_url") + def test_setup_mirrors_remote_no_mirrors_probes_primary(self, m_primary, m_probe, m_ctx) -> None: + m_ctx.return_value = self._ctx(repo_dir="/tmp/repo", resolved={}) + m_primary.return_value = "git@github.com:alice/repo.git" + m_probe.return_value = (True, "") + repos = [{"provider": "github.com", "account": "alice", "repository": "repo"}] setup_mirrors( - selected_repos=[repo], - repositories_base_dir="/base", - all_repos=[repo], - preview=False, + selected_repos=repos, + repositories_base_dir="/tmp", + all_repos=repos, + preview=True, local=False, remote=True, ensure_remote=False, ) - mock_ensure_remote_repository.assert_not_called() - self.assertEqual(mock_probe.call_count, 2) + m_primary.assert_called() + m_probe.assert_called_with("git@github.com:alice/repo.git", "/tmp/repo") + + @patch("pkgmgr.actions.mirror.setup_cmd.build_context") + @patch("pkgmgr.actions.mirror.setup_cmd.probe_mirror") + def test_setup_mirrors_remote_with_mirrors_probes_each(self, m_probe, m_ctx) -> None: + m_ctx.return_value = self._ctx( + repo_dir="/tmp/repo", + resolved={ + "origin": "git@github.com:alice/repo.git", + "backup": "ssh://git@git.veen.world:2201/alice/repo.git", + }, + ) + m_probe.return_value = (True, "") + + repos = [{"provider": "github.com", "account": "alice", "repository": "repo"}] + setup_mirrors( + selected_repos=repos, + repositories_base_dir="/tmp", + all_repos=repos, + preview=True, + local=False, + remote=True, + ensure_remote=False, + ) + + self.assertEqual(m_probe.call_count, 2) if __name__ == "__main__":