refactor(pull): switch repository pull to core.git commands
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

- Replace raw subprocess git pull with core.git.commands.pull_args
- Remove shell-based command execution
- Add GitPullArgsError wrapper for consistent error handling
- Align unit tests to mock pull_args instead of subprocess.run
- Preserve verification and prompt logic

https://chatgpt.com/share/69414dc9-5b30-800f-88b2-bd27a873580b
This commit is contained in:
Kevin Veen-Birkenbach
2025-12-16 13:17:04 +01:00
parent 019aa4b0d9
commit 2f89de1ff5
4 changed files with 121 additions and 80 deletions

View File

@@ -1,6 +1,6 @@
import io
import unittest
from unittest.mock import patch, MagicMock
from unittest.mock import patch
from pkgmgr.actions.repository.pull import pull_with_verification
@@ -12,14 +12,23 @@ class TestPullWithVerification(unittest.TestCase):
These tests verify:
- Preview mode behaviour
- Verification logic (prompting, bypassing, skipping)
- subprocess.run invocation
- pull_args invocation (instead of subprocess.run)
- Repository directory existence checks
- Handling of extra git pull arguments
"""
def _setup_mocks(self, mock_exists, mock_get_repo_id, mock_get_repo_dir,
mock_verify, exists=True, verified_ok=True,
errors=None, verified_info=True):
def _setup_mocks(
self,
mock_exists,
mock_get_repo_id,
mock_get_repo_dir,
mock_verify,
*,
exists: bool = True,
verified_ok: bool = True,
errors=None,
verified_info: bool = True,
):
"""Helper to configure repetitive mock behavior."""
repo = {
"name": "pkgmgr",
@@ -31,13 +40,13 @@ class TestPullWithVerification(unittest.TestCase):
mock_verify.return_value = (
verified_ok,
errors or [],
"deadbeef", # commit hash
"ABCDEF", # signing key
"deadbeef", # commit hash
"ABCDEF", # signing key
)
return repo
# ---------------------------------------------------------------------
@patch("pkgmgr.actions.repository.pull.subprocess.run")
@patch("pkgmgr.actions.repository.pull.pull_args")
@patch("pkgmgr.actions.repository.pull.verify_repository")
@patch("pkgmgr.actions.repository.pull.get_repo_dir")
@patch("pkgmgr.actions.repository.pull.get_repo_identifier")
@@ -50,11 +59,11 @@ class TestPullWithVerification(unittest.TestCase):
mock_get_repo_id,
mock_get_repo_dir,
mock_verify,
mock_subprocess,
mock_pull_args,
):
"""
Preview mode must NEVER request user input and must NEVER execute git.
It must only print the preview command.
Preview mode must NEVER request user input and must still call pull_args
in preview mode (which prints the preview command via core.git.run()).
"""
repo = self._setup_mocks(
mock_exists,
@@ -78,17 +87,15 @@ class TestPullWithVerification(unittest.TestCase):
preview=True,
)
output = buf.getvalue()
self.assertIn(
"[Preview] In '/fake/base/pkgmgr': git pull --ff-only",
output,
mock_input.assert_not_called()
mock_pull_args.assert_called_once_with(
["--ff-only"],
cwd="/fake/base/pkgmgr",
preview=True,
)
mock_input.assert_not_called()
mock_subprocess.assert_not_called()
# ---------------------------------------------------------------------
@patch("pkgmgr.actions.repository.pull.subprocess.run")
@patch("pkgmgr.actions.repository.pull.pull_args")
@patch("pkgmgr.actions.repository.pull.verify_repository")
@patch("pkgmgr.actions.repository.pull.get_repo_dir")
@patch("pkgmgr.actions.repository.pull.get_repo_identifier")
@@ -101,7 +108,7 @@ class TestPullWithVerification(unittest.TestCase):
mock_get_repo_id,
mock_get_repo_dir,
mock_verify,
mock_subprocess,
mock_pull_args,
):
"""
If verification fails and preview=False, the user is prompted.
@@ -118,22 +125,20 @@ class TestPullWithVerification(unittest.TestCase):
mock_input.return_value = "n"
buf = io.StringIO()
with patch("sys.stdout", new=buf):
pull_with_verification(
selected_repos=[repo],
repositories_base_dir="/fake/base",
all_repos=[repo],
extra_args=[],
no_verification=False,
preview=False,
)
pull_with_verification(
selected_repos=[repo],
repositories_base_dir="/fake/base",
all_repos=[repo],
extra_args=[],
no_verification=False,
preview=False,
)
mock_input.assert_called_once()
mock_subprocess.assert_not_called()
mock_pull_args.assert_not_called()
# ---------------------------------------------------------------------
@patch("pkgmgr.actions.repository.pull.subprocess.run")
@patch("pkgmgr.actions.repository.pull.pull_args")
@patch("pkgmgr.actions.repository.pull.verify_repository")
@patch("pkgmgr.actions.repository.pull.get_repo_dir")
@patch("pkgmgr.actions.repository.pull.get_repo_identifier")
@@ -146,11 +151,11 @@ class TestPullWithVerification(unittest.TestCase):
mock_get_repo_id,
mock_get_repo_dir,
mock_verify,
mock_subprocess,
mock_pull_args,
):
"""
If verification fails and the user accepts ('y'),
then the git pull should be executed.
then the git pull should be executed via pull_args.
"""
repo = self._setup_mocks(
mock_exists,
@@ -162,7 +167,6 @@ class TestPullWithVerification(unittest.TestCase):
)
mock_input.return_value = "y"
mock_subprocess.return_value = MagicMock(returncode=0)
pull_with_verification(
selected_repos=[repo],
@@ -173,11 +177,15 @@ class TestPullWithVerification(unittest.TestCase):
preview=False,
)
mock_subprocess.assert_called_once()
mock_input.assert_called_once()
mock_pull_args.assert_called_once_with(
[],
cwd="/fake/base/pkgmgr",
preview=False,
)
# ---------------------------------------------------------------------
@patch("pkgmgr.actions.repository.pull.subprocess.run")
@patch("pkgmgr.actions.repository.pull.pull_args")
@patch("pkgmgr.actions.repository.pull.verify_repository")
@patch("pkgmgr.actions.repository.pull.get_repo_dir")
@patch("pkgmgr.actions.repository.pull.get_repo_identifier")
@@ -190,7 +198,7 @@ class TestPullWithVerification(unittest.TestCase):
mock_get_repo_id,
mock_get_repo_dir,
mock_verify,
mock_subprocess,
mock_pull_args,
):
"""
If verification is successful, the user should NOT be prompted,
@@ -204,8 +212,6 @@ class TestPullWithVerification(unittest.TestCase):
verified_ok=True,
)
mock_subprocess.return_value = MagicMock(returncode=0)
pull_with_verification(
selected_repos=[repo],
repositories_base_dir="/fake/base",
@@ -216,12 +222,14 @@ class TestPullWithVerification(unittest.TestCase):
)
mock_input.assert_not_called()
mock_subprocess.assert_called_once()
cmd = mock_subprocess.call_args[0][0]
self.assertIn("git pull --rebase", cmd)
mock_pull_args.assert_called_once_with(
["--rebase"],
cwd="/fake/base/pkgmgr",
preview=False,
)
# ---------------------------------------------------------------------
@patch("pkgmgr.actions.repository.pull.subprocess.run")
@patch("pkgmgr.actions.repository.pull.pull_args")
@patch("pkgmgr.actions.repository.pull.verify_repository")
@patch("pkgmgr.actions.repository.pull.get_repo_dir")
@patch("pkgmgr.actions.repository.pull.get_repo_identifier")
@@ -234,11 +242,11 @@ class TestPullWithVerification(unittest.TestCase):
mock_get_repo_id,
mock_get_repo_dir,
mock_verify,
mock_subprocess,
mock_pull_args,
):
"""
If the repository directory does not exist, the repo must be skipped
silently and no git command executed.
and no git command executed.
"""
repo = self._setup_mocks(
mock_exists,
@@ -263,10 +271,10 @@ class TestPullWithVerification(unittest.TestCase):
self.assertIn("not found", output)
mock_input.assert_not_called()
mock_subprocess.assert_not_called()
mock_pull_args.assert_not_called()
# ---------------------------------------------------------------------
@patch("pkgmgr.actions.repository.pull.subprocess.run")
@patch("pkgmgr.actions.repository.pull.pull_args")
@patch("pkgmgr.actions.repository.pull.verify_repository")
@patch("pkgmgr.actions.repository.pull.get_repo_dir")
@patch("pkgmgr.actions.repository.pull.get_repo_identifier")
@@ -279,7 +287,7 @@ class TestPullWithVerification(unittest.TestCase):
mock_get_repo_id,
mock_get_repo_dir,
mock_verify,
mock_subprocess,
mock_pull_args,
):
"""
If no_verification=True, verification failures must NOT prompt.
@@ -294,8 +302,6 @@ class TestPullWithVerification(unittest.TestCase):
errors=["invalid"],
)
mock_subprocess.return_value = MagicMock(returncode=0)
pull_with_verification(
selected_repos=[repo],
repositories_base_dir="/fake/base",
@@ -306,4 +312,8 @@ class TestPullWithVerification(unittest.TestCase):
)
mock_input.assert_not_called()
mock_subprocess.assert_called_once()
mock_pull_args.assert_called_once_with(
[],
cwd="/fake/base/pkgmgr",
preview=False,
)