gpt-5.2: fix tests and imports after git queries split
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-env-virtual (push) Has been cancelled
Mark stable commit / test-env-nix (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / lint-shell (push) Has been cancelled
Mark stable commit / lint-python (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled

https://chatgpt.com/share/694135eb-10a8-800f-8b12-968612f605c7

Gemini
https://ai.studio/apps/drive/1QO9MaEklm2zZMDZ6XPP0LStuAooXs1NO
This commit is contained in:
Kevin Veen-Birkenbach
2025-12-16 11:35:10 +01:00
parent e117115b7f
commit 0119af330f
12 changed files with 206 additions and 137 deletions

View File

@@ -0,0 +1,6 @@
from __future__ import annotations
# expose subpackages for patch() / resolve_name() friendliness
from . import release as release # noqa: F401
__all__ = ["release"]

View File

@@ -7,7 +7,7 @@ Version discovery and bumping helpers for the release workflow.
from __future__ import annotations from __future__ import annotations
from pkgmgr.core.git import get_tags from pkgmgr.core.git.queries import get_tags
from pkgmgr.core.version.semver import ( from pkgmgr.core.version.semver import (
SemVer, SemVer,
find_latest_version, find_latest_version,

View File

@@ -1,4 +1,3 @@
# src/pkgmgr/actions/release/workflow.py
from __future__ import annotations from __future__ import annotations
import os import os
@@ -6,7 +5,8 @@ import sys
from typing import Optional from typing import Optional
from pkgmgr.actions.branch import close_branch from pkgmgr.actions.branch import close_branch
from pkgmgr.core.git import get_current_branch, GitError from pkgmgr.core.git import GitError
from pkgmgr.core.git.queries import get_current_branch
from pkgmgr.core.repository.paths import resolve_repo_paths from pkgmgr.core.repository.paths import resolve_repo_paths
from .files import ( from .files import (

View File

@@ -7,7 +7,7 @@ from typing import Any, Dict, List, Optional, Tuple
from pkgmgr.cli.context import CLIContext from pkgmgr.cli.context import CLIContext
from pkgmgr.core.repository.dir import get_repo_dir from pkgmgr.core.repository.dir import get_repo_dir
from pkgmgr.core.repository.identifier import get_repo_identifier from pkgmgr.core.repository.identifier import get_repo_identifier
from pkgmgr.core.git import get_tags from pkgmgr.core.git.queries import get_tags
from pkgmgr.core.version.semver import extract_semver_from_tags from pkgmgr.core.version.semver import extract_semver_from_tags
from pkgmgr.actions.changelog import generate_changelog from pkgmgr.actions.changelog import generate_changelog

View File

@@ -7,7 +7,7 @@ from typing import Any, Dict, List, Optional, Tuple
from pkgmgr.cli.context import CLIContext from pkgmgr.cli.context import CLIContext
from pkgmgr.core.repository.dir import get_repo_dir from pkgmgr.core.repository.dir import get_repo_dir
from pkgmgr.core.repository.identifier import get_repo_identifier from pkgmgr.core.repository.identifier import get_repo_identifier
from pkgmgr.core.git import get_tags from pkgmgr.core.git.queries import get_tags
from pkgmgr.core.version.semver import SemVer, find_latest_version from pkgmgr.core.version.semver import SemVer, find_latest_version
from pkgmgr.core.version.installed import ( from pkgmgr.core.version.installed import (
get_installed_python_version, get_installed_python_version,

View File

@@ -7,7 +7,7 @@ from pkgmgr.core.git.commands import GitDeleteRemoteBranchError
class TestCloseBranch(unittest.TestCase): class TestCloseBranch(unittest.TestCase):
@patch("pkgmgr.actions.branch.close_branch.input", return_value="y") @patch("builtins.input", return_value="y")
@patch("pkgmgr.actions.branch.close_branch.get_current_branch", return_value="feature-x") @patch("pkgmgr.actions.branch.close_branch.get_current_branch", return_value="feature-x")
@patch("pkgmgr.actions.branch.close_branch.resolve_base_branch", return_value="main") @patch("pkgmgr.actions.branch.close_branch.resolve_base_branch", return_value="main")
@patch("pkgmgr.actions.branch.close_branch.fetch") @patch("pkgmgr.actions.branch.close_branch.fetch")
@@ -26,10 +26,10 @@ class TestCloseBranch(unittest.TestCase):
pull, pull,
checkout, checkout,
fetch, fetch,
resolve, _resolve,
current, _current,
input_mock, _input_mock,
): ) -> None:
close_branch(None, cwd=".") close_branch(None, cwd=".")
fetch.assert_called_once_with("origin", cwd=".") fetch.assert_called_once_with("origin", cwd=".")
checkout.assert_called_once_with("main", cwd=".") checkout.assert_called_once_with("main", cwd=".")
@@ -41,31 +41,61 @@ class TestCloseBranch(unittest.TestCase):
@patch("pkgmgr.actions.branch.close_branch.get_current_branch", return_value="main") @patch("pkgmgr.actions.branch.close_branch.get_current_branch", return_value="main")
@patch("pkgmgr.actions.branch.close_branch.resolve_base_branch", return_value="main") @patch("pkgmgr.actions.branch.close_branch.resolve_base_branch", return_value="main")
def test_refuses_to_close_base_branch(self, resolve, current): def test_refuses_to_close_base_branch(self, _resolve, _current) -> None:
with self.assertRaises(RuntimeError): with self.assertRaises(RuntimeError):
close_branch(None) close_branch(None)
@patch("pkgmgr.actions.branch.close_branch.input", return_value="n") @patch("builtins.input", return_value="n")
@patch("pkgmgr.actions.branch.close_branch.get_current_branch", return_value="feature-x") @patch("pkgmgr.actions.branch.close_branch.get_current_branch", return_value="feature-x")
@patch("pkgmgr.actions.branch.close_branch.resolve_base_branch", return_value="main") @patch("pkgmgr.actions.branch.close_branch.resolve_base_branch", return_value="main")
@patch("pkgmgr.actions.branch.close_branch.fetch") @patch("pkgmgr.actions.branch.close_branch.fetch")
def test_close_branch_aborts_on_no(self, fetch, resolve, current, input_mock): def test_close_branch_aborts_on_no(self, fetch, _resolve, _current, _input_mock) -> None:
close_branch(None, cwd=".") close_branch(None, cwd=".")
fetch.assert_not_called() fetch.assert_not_called()
@patch("builtins.input")
@patch("pkgmgr.actions.branch.close_branch.get_current_branch", return_value="feature-x") @patch("pkgmgr.actions.branch.close_branch.get_current_branch", return_value="feature-x")
@patch("pkgmgr.actions.branch.close_branch.resolve_base_branch", return_value="main") @patch("pkgmgr.actions.branch.close_branch.resolve_base_branch", return_value="main")
@patch("pkgmgr.actions.branch.close_branch.fetch") @patch("pkgmgr.actions.branch.close_branch.fetch")
def test_close_branch_force_skips_prompt(self, fetch, resolve, current): @patch("pkgmgr.actions.branch.close_branch.checkout")
@patch("pkgmgr.actions.branch.close_branch.pull")
@patch("pkgmgr.actions.branch.close_branch.merge_no_ff")
@patch("pkgmgr.actions.branch.close_branch.push")
@patch("pkgmgr.actions.branch.close_branch.delete_local_branch")
@patch("pkgmgr.actions.branch.close_branch.delete_remote_branch")
def test_close_branch_force_skips_prompt(
self,
delete_remote_branch,
delete_local_branch,
push,
merge_no_ff,
pull,
checkout,
fetch,
_resolve,
_current,
input_mock,
) -> None:
close_branch(None, cwd=".", force=True) close_branch(None, cwd=".", force=True)
fetch.assert_called_once()
# no interactive prompt when forced
input_mock.assert_not_called()
# workflow still runs (but is mocked)
fetch.assert_called_once_with("origin", cwd=".")
checkout.assert_called_once_with("main", cwd=".")
pull.assert_called_once_with("origin", "main", cwd=".")
merge_no_ff.assert_called_once_with("feature-x", cwd=".")
push.assert_called_once_with("origin", "main", cwd=".")
delete_local_branch.assert_called_once_with("feature-x", cwd=".", force=False)
delete_remote_branch.assert_called_once_with("origin", "feature-x", cwd=".")
@patch("pkgmgr.actions.branch.close_branch.get_current_branch", side_effect=GitError("fail")) @patch("pkgmgr.actions.branch.close_branch.get_current_branch", side_effect=GitError("fail"))
def test_close_branch_errors_if_cannot_detect_branch(self, current): def test_close_branch_errors_if_cannot_detect_branch(self, _current) -> None:
with self.assertRaises(RuntimeError): with self.assertRaises(RuntimeError):
close_branch(None) close_branch(None)
@patch("pkgmgr.actions.branch.close_branch.input", return_value="y") @patch("builtins.input", return_value="y")
@patch("pkgmgr.actions.branch.close_branch.get_current_branch", return_value="feature-x") @patch("pkgmgr.actions.branch.close_branch.get_current_branch", return_value="feature-x")
@patch("pkgmgr.actions.branch.close_branch.resolve_base_branch", return_value="main") @patch("pkgmgr.actions.branch.close_branch.resolve_base_branch", return_value="main")
@patch("pkgmgr.actions.branch.close_branch.fetch") @patch("pkgmgr.actions.branch.close_branch.fetch")
@@ -80,17 +110,17 @@ class TestCloseBranch(unittest.TestCase):
) )
def test_close_branch_remote_delete_failure_is_wrapped( def test_close_branch_remote_delete_failure_is_wrapped(
self, self,
delete_remote_branch, _delete_remote_branch,
delete_local_branch, _delete_local_branch,
push, _push,
merge_no_ff, _merge_no_ff,
pull, _pull,
checkout, _checkout,
fetch, _fetch,
resolve, _resolve,
current, _current,
input_mock, _input_mock,
): ) -> None:
with self.assertRaises(RuntimeError) as ctx: with self.assertRaises(RuntimeError) as ctx:
close_branch(None, cwd=".") close_branch(None, cwd=".")
self.assertIn("remote deletion failed", str(ctx.exception)) self.assertIn("remote deletion failed", str(ctx.exception))

View File

@@ -7,43 +7,55 @@ from pkgmgr.core.git.commands import GitDeleteRemoteBranchError
class TestDropBranch(unittest.TestCase): class TestDropBranch(unittest.TestCase):
@patch("pkgmgr.actions.branch.drop_branch.input", return_value="y") @patch("builtins.input", return_value="y")
@patch("pkgmgr.actions.branch.drop_branch.get_current_branch", return_value="feature-x") @patch("pkgmgr.actions.branch.drop_branch.get_current_branch", return_value="feature-x")
@patch("pkgmgr.actions.branch.drop_branch.resolve_base_branch", return_value="main") @patch("pkgmgr.actions.branch.drop_branch.resolve_base_branch", return_value="main")
@patch("pkgmgr.actions.branch.drop_branch.delete_local_branch") @patch("pkgmgr.actions.branch.drop_branch.delete_local_branch")
@patch("pkgmgr.actions.branch.drop_branch.delete_remote_branch") @patch("pkgmgr.actions.branch.drop_branch.delete_remote_branch")
def test_drop_branch_happy_path(self, delete_remote, delete_local, resolve, current, input_mock): def test_drop_branch_happy_path(self, delete_remote, delete_local, _resolve, _current, _input_mock) -> None:
drop_branch(None, cwd=".") drop_branch(None, cwd=".")
delete_local.assert_called_once_with("feature-x", cwd=".", force=False) delete_local.assert_called_once_with("feature-x", cwd=".", force=False)
delete_remote.assert_called_once_with("origin", "feature-x", cwd=".") delete_remote.assert_called_once_with("origin", "feature-x", cwd=".")
@patch("pkgmgr.actions.branch.drop_branch.get_current_branch", return_value="main") @patch("pkgmgr.actions.branch.drop_branch.get_current_branch", return_value="main")
@patch("pkgmgr.actions.branch.drop_branch.resolve_base_branch", return_value="main") @patch("pkgmgr.actions.branch.drop_branch.resolve_base_branch", return_value="main")
def test_refuses_to_drop_base_branch(self, resolve, current): def test_refuses_to_drop_base_branch(self, _resolve, _current) -> None:
with self.assertRaises(RuntimeError): with self.assertRaises(RuntimeError):
drop_branch(None) drop_branch(None)
@patch("pkgmgr.actions.branch.drop_branch.input", return_value="n") @patch("builtins.input", return_value="n")
@patch("pkgmgr.actions.branch.drop_branch.get_current_branch", return_value="feature-x") @patch("pkgmgr.actions.branch.drop_branch.get_current_branch", return_value="feature-x")
@patch("pkgmgr.actions.branch.drop_branch.resolve_base_branch", return_value="main") @patch("pkgmgr.actions.branch.drop_branch.resolve_base_branch", return_value="main")
@patch("pkgmgr.actions.branch.drop_branch.delete_local_branch") @patch("pkgmgr.actions.branch.drop_branch.delete_local_branch")
def test_drop_branch_aborts_on_no(self, delete_local, resolve, current, input_mock): def test_drop_branch_aborts_on_no(self, delete_local, _resolve, _current, _input_mock) -> None:
drop_branch(None, cwd=".") drop_branch(None, cwd=".")
delete_local.assert_not_called() delete_local.assert_not_called()
@patch("builtins.input")
@patch("pkgmgr.actions.branch.drop_branch.get_current_branch", return_value="feature-x") @patch("pkgmgr.actions.branch.drop_branch.get_current_branch", return_value="feature-x")
@patch("pkgmgr.actions.branch.drop_branch.resolve_base_branch", return_value="main") @patch("pkgmgr.actions.branch.drop_branch.resolve_base_branch", return_value="main")
@patch("pkgmgr.actions.branch.drop_branch.delete_local_branch") @patch("pkgmgr.actions.branch.drop_branch.delete_local_branch")
def test_drop_branch_force_skips_prompt(self, delete_local, resolve, current): @patch("pkgmgr.actions.branch.drop_branch.delete_remote_branch")
def test_drop_branch_force_skips_prompt(
self,
delete_remote,
delete_local,
_resolve,
_current,
input_mock,
) -> None:
drop_branch(None, cwd=".", force=True) drop_branch(None, cwd=".", force=True)
delete_local.assert_called_once()
input_mock.assert_not_called()
delete_local.assert_called_once_with("feature-x", cwd=".", force=False)
delete_remote.assert_called_once_with("origin", "feature-x", cwd=".")
@patch("pkgmgr.actions.branch.drop_branch.get_current_branch", side_effect=GitError("fail")) @patch("pkgmgr.actions.branch.drop_branch.get_current_branch", side_effect=GitError("fail"))
def test_drop_branch_errors_if_no_branch_detected(self, current): def test_drop_branch_errors_if_no_branch_detected(self, _current) -> None:
with self.assertRaises(RuntimeError): with self.assertRaises(RuntimeError):
drop_branch(None) drop_branch(None)
@patch("pkgmgr.actions.branch.drop_branch.input", return_value="y") @patch("builtins.input", return_value="y")
@patch("pkgmgr.actions.branch.drop_branch.get_current_branch", return_value="feature-x") @patch("pkgmgr.actions.branch.drop_branch.get_current_branch", return_value="feature-x")
@patch("pkgmgr.actions.branch.drop_branch.resolve_base_branch", return_value="main") @patch("pkgmgr.actions.branch.drop_branch.resolve_base_branch", return_value="main")
@patch("pkgmgr.actions.branch.drop_branch.delete_local_branch") @patch("pkgmgr.actions.branch.drop_branch.delete_local_branch")
@@ -51,7 +63,14 @@ class TestDropBranch(unittest.TestCase):
"pkgmgr.actions.branch.drop_branch.delete_remote_branch", "pkgmgr.actions.branch.drop_branch.delete_remote_branch",
side_effect=GitDeleteRemoteBranchError("boom", cwd="."), side_effect=GitDeleteRemoteBranchError("boom", cwd="."),
) )
def test_drop_branch_remote_delete_failure_is_wrapped(self, delete_remote, delete_local, resolve, current, input_mock): def test_drop_branch_remote_delete_failure_is_wrapped(
self,
_delete_remote,
_delete_local,
_resolve,
_current,
_input_mock,
) -> None:
with self.assertRaises(RuntimeError) as ctx: with self.assertRaises(RuntimeError) as ctx:
drop_branch(None, cwd=".") drop_branch(None, cwd=".")
self.assertIn("remote deletion failed", str(ctx.exception)) self.assertIn("remote deletion failed", str(ctx.exception))

View File

@@ -11,8 +11,17 @@ class TestOpenBranch(unittest.TestCase):
@patch("pkgmgr.actions.branch.open_branch.pull") @patch("pkgmgr.actions.branch.open_branch.pull")
@patch("pkgmgr.actions.branch.open_branch.create_branch") @patch("pkgmgr.actions.branch.open_branch.create_branch")
@patch("pkgmgr.actions.branch.open_branch.push_upstream") @patch("pkgmgr.actions.branch.open_branch.push_upstream")
def test_open_branch_executes_git_commands(self, push_upstream, create_branch, pull, checkout, fetch, resolve): def test_open_branch_executes_git_commands(
self,
push_upstream,
create_branch,
pull,
checkout,
fetch,
_resolve,
) -> None:
open_branch("feature-x", base_branch="main", cwd=".") open_branch("feature-x", base_branch="main", cwd=".")
fetch.assert_called_once_with("origin", cwd=".") fetch.assert_called_once_with("origin", cwd=".")
checkout.assert_called_once_with("main", cwd=".") checkout.assert_called_once_with("main", cwd=".")
pull.assert_called_once_with("origin", "main", cwd=".") pull.assert_called_once_with("origin", "main", cwd=".")
@@ -26,11 +35,25 @@ class TestOpenBranch(unittest.TestCase):
@patch("pkgmgr.actions.branch.open_branch.pull") @patch("pkgmgr.actions.branch.open_branch.pull")
@patch("pkgmgr.actions.branch.open_branch.create_branch") @patch("pkgmgr.actions.branch.open_branch.create_branch")
@patch("pkgmgr.actions.branch.open_branch.push_upstream") @patch("pkgmgr.actions.branch.open_branch.push_upstream")
def test_open_branch_prompts_for_name(self, fetch, resolve, input_mock): def test_open_branch_prompts_for_name(
self,
push_upstream,
create_branch,
pull,
checkout,
fetch,
_resolve,
_input_mock,
) -> None:
open_branch(None) open_branch(None)
fetch.assert_called_once()
def test_open_branch_rejects_empty_name(self): fetch.assert_called_once_with("origin", cwd=".")
checkout.assert_called_once_with("main", cwd=".")
pull.assert_called_once_with("origin", "main", cwd=".")
create_branch.assert_called_once_with("auto-branch", "main", cwd=".")
push_upstream.assert_called_once_with("origin", "auto-branch", cwd=".")
def test_open_branch_rejects_empty_name(self) -> None:
with patch("builtins.input", return_value=""): with patch("builtins.input", return_value=""):
with self.assertRaises(RuntimeError): with self.assertRaises(RuntimeError):
open_branch(None) open_branch(None)

View File

@@ -21,46 +21,34 @@ class TestMirrorGitRemote(unittest.TestCase):
) )
def test_build_default_ssh_url(self) -> None: def test_build_default_ssh_url(self) -> None:
repo = {
"provider": "github.com",
"account": "alice",
"repository": "repo",
}
self.assertEqual(
build_default_ssh_url(repo),
"git@github.com:alice/repo.git",
)
def test_determine_primary_prefers_origin(self) -> None:
repo = {"provider": "github.com", "account": "alice", "repository": "repo"} repo = {"provider": "github.com", "account": "alice", "repository": "repo"}
ctx = self._ctx(config={"origin": "git@github.com:alice/repo.git"}) self.assertEqual(build_default_ssh_url(repo), "git@github.com:alice/repo.git")
self.assertEqual(
determine_primary_remote_url(repo, ctx),
"git@github.com:alice/repo.git",
)
def test_determine_primary_uses_file_order(self) -> None: def test_determine_primary_prefers_origin_in_resolved(self) -> None:
# resolved_mirrors = config + file (file wins), so put origin in file.
repo = {"provider": "github.com", "account": "alice", "repository": "repo"} repo = {"provider": "github.com", "account": "alice", "repository": "repo"}
ctx = self._ctx( ctx = self._ctx(file={"origin": "git@github.com:alice/repo.git"})
file={ self.assertEqual(determine_primary_remote_url(repo, ctx), "git@github.com:alice/repo.git")
"first": "git@a/first.git",
"second": "git@a/second.git", def test_determine_primary_falls_back_to_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( self.assertEqual(determine_primary_remote_url(repo, ctx), "git@a/first.git")
determine_primary_remote_url(repo, ctx),
"git@a/first.git", def test_determine_primary_falls_back_to_config_order(self) -> None:
) repo = {"provider": "github.com", "account": "alice", "repository": "repo"}
ctx = self._ctx(config={"cfg1": "git@c/one.git", "cfg2": "git@c/two.git"})
self.assertEqual(determine_primary_remote_url(repo, ctx), "git@c/one.git")
def test_determine_primary_fallback_default(self) -> None: def test_determine_primary_fallback_default(self) -> None:
repo = {"provider": "github.com", "account": "alice", "repository": "repo"} repo = {"provider": "github.com", "account": "alice", "repository": "repo"}
ctx = self._ctx() ctx = self._ctx()
self.assertEqual( self.assertEqual(determine_primary_remote_url(repo, ctx), "git@github.com:alice/repo.git")
determine_primary_remote_url(repo, ctx),
"git@github.com:alice/repo.git",
)
@patch("pkgmgr.actions.mirror.git_remote._safe_git_output") @patch("pkgmgr.actions.mirror.git_remote.list_remotes", return_value=["origin", "backup"])
def test_has_origin_remote(self, m_out) -> None: def test_has_origin_remote_true(self, _m_list) -> None:
m_out.return_value = "origin\nupstream\n"
self.assertTrue(has_origin_remote("/tmp/repo")) self.assertTrue(has_origin_remote("/tmp/repo"))
@patch("pkgmgr.actions.mirror.git_remote.list_remotes", return_value=["backup"])
def test_has_origin_remote_false(self, _m_list) -> None:
self.assertFalse(has_origin_remote("/tmp/repo"))

View File

@@ -10,6 +10,8 @@ from pkgmgr.actions.mirror.types import RepoMirrorContext
class TestGitRemotePrimaryPush(unittest.TestCase): class TestGitRemotePrimaryPush(unittest.TestCase):
def test_origin_created_and_extra_push_added(self) -> None: def test_origin_created_and_extra_push_added(self) -> None:
repo = {"provider": "github.com", "account": "alice", "repository": "repo"} repo = {"provider": "github.com", "account": "alice", "repository": "repo"}
# Use file_mirrors so ctx.resolved_mirrors contains both, no setattr (frozen dataclass!)
ctx = RepoMirrorContext( ctx = RepoMirrorContext(
identifier="repo", identifier="repo",
repo_dir="/tmp/repo", repo_dir="/tmp/repo",
@@ -20,31 +22,44 @@ class TestGitRemotePrimaryPush(unittest.TestCase):
}, },
) )
executed: list[str] = [] with patch("os.path.isdir", return_value=True):
with patch("pkgmgr.actions.mirror.git_remote.has_origin_remote", return_value=False), patch(
def fake_run(cmd: str, cwd: str, preview: bool) -> None: "pkgmgr.actions.mirror.git_remote.add_remote"
executed.append(cmd) ) as m_add_remote, patch(
"pkgmgr.actions.mirror.git_remote.set_remote_url"
def fake_git(args, cwd): ) as m_set_remote_url, patch(
if args == ["remote"]: "pkgmgr.actions.mirror.git_remote.get_remote_push_urls", return_value=set()
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( ), patch(
"pkgmgr.actions.mirror.git_remote._safe_git_output", side_effect=fake_git "pkgmgr.actions.mirror.git_remote.add_remote_push_url"
): ) as m_add_push:
ensure_origin_remote(repo, ctx, preview=False) ensure_origin_remote(repo, ctx, preview=False)
self.assertEqual( # determine_primary_remote_url falls back to file order (primary first)
executed, m_add_remote.assert_called_once_with(
[ "origin",
"git remote add origin git@github.com:alice/repo.git", "git@github.com:alice/repo.git",
"git remote set-url origin git@github.com:alice/repo.git", cwd="/tmp/repo",
"git remote set-url --push origin git@github.com:alice/repo.git", preview=False,
"git remote set-url --add --push origin git@github.com:alice/repo-backup.git", )
],
m_set_remote_url.assert_any_call(
"origin",
"git@github.com:alice/repo.git",
cwd="/tmp/repo",
push=False,
preview=False,
)
m_set_remote_url.assert_any_call(
"origin",
"git@github.com:alice/repo.git",
cwd="/tmp/repo",
push=True,
preview=False,
)
m_add_push.assert_called_once_with(
"origin",
"git@github.com:alice/repo-backup.git",
cwd="/tmp/repo",
preview=False,
) )

View File

@@ -8,16 +8,10 @@ from pkgmgr.actions.mirror.types import RepoMirrorContext
class TestMirrorSetupCmd(unittest.TestCase): class TestMirrorSetupCmd(unittest.TestCase):
def _ctx( def _ctx(self, *, repo_dir: str = "/tmp/repo", resolved: dict[str, str] | None = None) -> RepoMirrorContext:
self, # resolved_mirrors is a @property combining config+file. Put it into file_mirrors.
*,
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( return RepoMirrorContext(
identifier="repo-id", identifier="repo",
repo_dir=repo_dir, repo_dir=repo_dir,
config_mirrors={}, config_mirrors={},
file_mirrors=resolved or {}, file_mirrors=resolved or {},
@@ -26,7 +20,8 @@ class TestMirrorSetupCmd(unittest.TestCase):
@patch("pkgmgr.actions.mirror.setup_cmd.build_context") @patch("pkgmgr.actions.mirror.setup_cmd.build_context")
@patch("pkgmgr.actions.mirror.setup_cmd.ensure_origin_remote") @patch("pkgmgr.actions.mirror.setup_cmd.ensure_origin_remote")
def test_setup_mirrors_local_calls_ensure_origin_remote(self, m_ensure, m_ctx) -> None: 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"}) ctx = self._ctx(repo_dir="/tmp/repo", resolved={"primary": "git@x/y.git"})
m_ctx.return_value = ctx
repos = [{"provider": "github.com", "account": "alice", "repository": "repo"}] repos = [{"provider": "github.com", "account": "alice", "repository": "repo"}]
setup_mirrors( setup_mirrors(
@@ -39,24 +34,21 @@ class TestMirrorSetupCmd(unittest.TestCase):
ensure_remote=False, ensure_remote=False,
) )
self.assertEqual(m_ensure.call_count, 1) # ensure_origin_remote(repo, ctx, preview) is called positionally in your code
m_ensure.assert_called_once()
args, kwargs = m_ensure.call_args args, kwargs = m_ensure.call_args
# ensure_origin_remote(repo, ctx, preview) may be positional or kw. self.assertEqual(args[0], repos[0])
# Accept both to avoid coupling tests to call style. self.assertIs(args[1], ctx)
if "preview" in kwargs: self.assertEqual(kwargs.get("preview", args[2] if len(args) >= 3 else None), True)
self.assertTrue(kwargs["preview"])
else:
# args: (repo, ctx, preview)
self.assertTrue(args[2])
@patch("pkgmgr.actions.mirror.setup_cmd.build_context") @patch("pkgmgr.actions.mirror.setup_cmd.build_context")
@patch("pkgmgr.actions.mirror.setup_cmd.probe_mirror")
@patch("pkgmgr.actions.mirror.setup_cmd.determine_primary_remote_url") @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: @patch("pkgmgr.actions.mirror.setup_cmd.probe_remote_reachable")
def test_setup_mirrors_remote_no_mirrors_probes_primary(self, m_probe, m_primary, m_ctx) -> None:
m_ctx.return_value = self._ctx(repo_dir="/tmp/repo", resolved={}) m_ctx.return_value = self._ctx(repo_dir="/tmp/repo", resolved={})
m_primary.return_value = "git@github.com:alice/repo.git" m_primary.return_value = "git@github.com:alice/repo.git"
m_probe.return_value = (True, "") m_probe.return_value = True
repos = [{"provider": "github.com", "account": "alice", "repository": "repo"}] repos = [{"provider": "github.com", "account": "alice", "repository": "repo"}]
setup_mirrors( setup_mirrors(
@@ -70,10 +62,10 @@ class TestMirrorSetupCmd(unittest.TestCase):
) )
m_primary.assert_called() m_primary.assert_called()
m_probe.assert_called_with("git@github.com:alice/repo.git", "/tmp/repo") m_probe.assert_called_once_with("git@github.com:alice/repo.git", cwd="/tmp/repo")
@patch("pkgmgr.actions.mirror.setup_cmd.build_context") @patch("pkgmgr.actions.mirror.setup_cmd.build_context")
@patch("pkgmgr.actions.mirror.setup_cmd.probe_mirror") @patch("pkgmgr.actions.mirror.setup_cmd.probe_remote_reachable")
def test_setup_mirrors_remote_with_mirrors_probes_each(self, m_probe, m_ctx) -> None: def test_setup_mirrors_remote_with_mirrors_probes_each(self, m_probe, m_ctx) -> None:
m_ctx.return_value = self._ctx( m_ctx.return_value = self._ctx(
repo_dir="/tmp/repo", repo_dir="/tmp/repo",
@@ -82,7 +74,7 @@ class TestMirrorSetupCmd(unittest.TestCase):
"backup": "ssh://git@git.veen.world:2201/alice/repo.git", "backup": "ssh://git@git.veen.world:2201/alice/repo.git",
}, },
) )
m_probe.return_value = (True, "") m_probe.return_value = True
repos = [{"provider": "github.com", "account": "alice", "repository": "repo"}] repos = [{"provider": "github.com", "account": "alice", "repository": "repo"}]
setup_mirrors( setup_mirrors(
@@ -96,6 +88,8 @@ class TestMirrorSetupCmd(unittest.TestCase):
) )
self.assertEqual(m_probe.call_count, 2) self.assertEqual(m_probe.call_count, 2)
m_probe.assert_any_call("git@github.com:alice/repo.git", cwd="/tmp/repo")
m_probe.assert_any_call("ssh://git@git.veen.world:2201/alice/repo.git", cwd="/tmp/repo")
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -1,4 +1,3 @@
import unittest import unittest
from unittest.mock import patch from unittest.mock import patch
@@ -6,15 +5,10 @@ from pkgmgr.actions.publish.git_tags import head_semver_tags
class TestHeadSemverTags(unittest.TestCase): class TestHeadSemverTags(unittest.TestCase):
@patch("pkgmgr.actions.publish.git_tags.run_git") @patch("pkgmgr.actions.publish.git_tags.get_tags_at_ref", return_value=[])
def test_no_tags(self, mock_run_git): def test_no_tags(self, _mock_get_tags_at_ref) -> None:
mock_run_git.return_value = ""
self.assertEqual(head_semver_tags(), []) self.assertEqual(head_semver_tags(), [])
@patch("pkgmgr.actions.publish.git_tags.run_git") @patch("pkgmgr.actions.publish.git_tags.get_tags_at_ref", return_value=["v2.0.0", "nope", "v1.0.0", "v1.2.0"])
def test_filters_and_sorts_semver(self, mock_run_git): def test_filters_and_sorts_semver(self, _mock_get_tags_at_ref) -> None:
mock_run_git.return_value = "v1.0.0\nv2.0.0\nfoo\n" self.assertEqual(head_semver_tags(), ["v1.0.0", "v1.2.0", "v2.0.0"])
self.assertEqual(
head_semver_tags(),
["v1.0.0", "v2.0.0"],
)