Skip to content

Commit 367f559

Browse files
committed
tests(test[pytest_plugin]): Add comprehensive tests for Git init compatibility
why: Ensure Git init compatibility fix works across all scenarios what: - Test basic and bare repository creation - Test Git version compatibility with mocked old/new Git - Test configuration hierarchy (param > env > default) - Test error handling for permission errors - Test post-init callback execution - Add integration test with real Git command Tests use monkeypatch to simulate different Git versions and ensure the fallback mechanism works correctly for older Git versions that don't support --initial-branch.
1 parent 2339972 commit 367f559

File tree

1 file changed

+224
-0
lines changed

1 file changed

+224
-0
lines changed

tests/test_pytest_plugin.py

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@
99
import pytest
1010

1111
from libvcs._internal.run import run
12+
from libvcs.cmd.git import Git
13+
from libvcs.exc import CommandError
14+
from libvcs.pytest_plugin import (
15+
DEFAULT_GIT_INITIAL_BRANCH,
16+
_create_git_remote_repo,
17+
)
1218

1319
if t.TYPE_CHECKING:
1420
import pathlib
@@ -176,3 +182,221 @@ def test_git_bare_repo_sync_and_commit(
176182
# Test
177183
result = pytester.runpytest(str(first_test_filename))
178184
result.assert_outcomes(passed=2)
185+
186+
187+
def test_create_git_remote_repo_basic(tmp_path: pathlib.Path) -> None:
188+
"""Test basic git repository creation."""
189+
repo_path = tmp_path / "test-repo"
190+
191+
result = _create_git_remote_repo(repo_path, init_cmd_args=[])
192+
193+
assert result == repo_path
194+
assert repo_path.exists()
195+
assert (repo_path / ".git").exists()
196+
197+
198+
def test_create_git_remote_repo_bare(tmp_path: pathlib.Path) -> None:
199+
"""Test bare git repository creation."""
200+
repo_path = tmp_path / "test-repo.git"
201+
202+
result = _create_git_remote_repo(repo_path, init_cmd_args=["--bare"])
203+
204+
assert result == repo_path
205+
assert repo_path.exists()
206+
assert (repo_path / "HEAD").exists()
207+
assert not (repo_path / ".git").exists()
208+
209+
210+
def test_create_git_remote_repo_with_initial_branch(
211+
tmp_path: pathlib.Path,
212+
monkeypatch: pytest.MonkeyPatch,
213+
) -> None:
214+
"""Test repository creation with custom initial branch.
215+
216+
This test checks both modern Git (2.30.0+) and fallback behavior.
217+
"""
218+
repo_path = tmp_path / "test-repo"
219+
220+
# Track Git.init calls
221+
init_calls: list[dict[str, t.Any]] = []
222+
223+
def mock_init(self: Git, *args: t.Any, **kwargs: t.Any) -> str:
224+
init_calls.append({"args": args, "kwargs": kwargs})
225+
226+
# Simulate old Git that doesn't support --initial-branch
227+
if kwargs.get("initial_branch"):
228+
msg = "error: unknown option `initial-branch'"
229+
raise CommandError(
230+
msg,
231+
returncode=1,
232+
cmd=["git", "init", "--initial-branch=main"],
233+
)
234+
235+
# Create the repo directory to simulate successful init
236+
self.path.mkdir(exist_ok=True)
237+
(self.path / ".git").mkdir(exist_ok=True)
238+
return "Initialized empty Git repository"
239+
240+
monkeypatch.setattr(Git, "init", mock_init)
241+
242+
result = _create_git_remote_repo(repo_path, initial_branch="develop")
243+
244+
# Should have tried twice: once with initial_branch, once without
245+
assert len(init_calls) == 2
246+
assert init_calls[0]["kwargs"].get("initial_branch") == "develop"
247+
assert "initial_branch" not in init_calls[1]["kwargs"]
248+
assert result == repo_path
249+
250+
251+
def test_create_git_remote_repo_modern_git(
252+
tmp_path: pathlib.Path,
253+
monkeypatch: pytest.MonkeyPatch,
254+
) -> None:
255+
"""Test repository creation with Git 2.30.0+ that supports --initial-branch."""
256+
repo_path = tmp_path / "test-repo"
257+
258+
init_calls: list[dict[str, t.Any]] = []
259+
260+
def mock_init(self: Git, *args: t.Any, **kwargs: t.Any) -> str:
261+
init_calls.append({"args": args, "kwargs": kwargs})
262+
# Simulate successful init with --initial-branch support
263+
self.path.mkdir(exist_ok=True)
264+
(self.path / ".git").mkdir(exist_ok=True)
265+
branch = kwargs.get("initial_branch", "master")
266+
return f"Initialized empty Git repository with initial branch '{branch}'"
267+
268+
monkeypatch.setattr(Git, "init", mock_init)
269+
270+
result = _create_git_remote_repo(repo_path, initial_branch="main")
271+
272+
# Should only call init once since it succeeded
273+
assert len(init_calls) == 1
274+
assert init_calls[0]["kwargs"].get("initial_branch") == "main"
275+
assert result == repo_path
276+
277+
278+
@pytest.mark.parametrize(
279+
("env_var", "param", "expected_branch"),
280+
[
281+
("custom-env", None, "custom-env"), # Use env var
282+
("custom-env", "param-override", "param-override"), # Param overrides env
283+
(None, "explicit-param", "explicit-param"), # Use param
284+
(None, None, DEFAULT_GIT_INITIAL_BRANCH), # Use default
285+
],
286+
)
287+
def test_create_git_remote_repo_branch_configuration(
288+
tmp_path: pathlib.Path,
289+
monkeypatch: pytest.MonkeyPatch,
290+
env_var: str | None,
291+
param: str | None,
292+
expected_branch: str,
293+
) -> None:
294+
"""Test initial branch configuration hierarchy."""
295+
# Always reload the module to ensure fresh state
296+
import sys
297+
298+
if "libvcs.pytest_plugin" in sys.modules:
299+
del sys.modules["libvcs.pytest_plugin"]
300+
301+
if env_var:
302+
monkeypatch.setenv("LIBVCS_GIT_DEFAULT_INITIAL_BRANCH", env_var)
303+
304+
# Import after setting env var
305+
from libvcs.pytest_plugin import _create_git_remote_repo
306+
307+
repo_path = tmp_path / "test-repo"
308+
309+
# Track what branch was used
310+
used_branch = None
311+
312+
def mock_init(self: Git, *args: t.Any, **kwargs: t.Any) -> str:
313+
nonlocal used_branch
314+
used_branch = kwargs.get("initial_branch")
315+
self.path.mkdir(exist_ok=True)
316+
(self.path / ".git").mkdir(exist_ok=True)
317+
return "Initialized"
318+
319+
monkeypatch.setattr(Git, "init", mock_init)
320+
321+
_create_git_remote_repo(repo_path, initial_branch=param)
322+
323+
assert used_branch == expected_branch
324+
325+
326+
def test_create_git_remote_repo_post_init_callback(tmp_path: pathlib.Path) -> None:
327+
"""Test that post-init callback is executed."""
328+
repo_path = tmp_path / "test-repo"
329+
callback_executed = False
330+
callback_path = None
331+
332+
def post_init_callback(
333+
remote_repo_path: pathlib.Path,
334+
env: t.Any = None,
335+
) -> None:
336+
nonlocal callback_executed, callback_path
337+
callback_executed = True
338+
callback_path = remote_repo_path
339+
(remote_repo_path / "callback-marker.txt").write_text("executed")
340+
341+
_create_git_remote_repo(
342+
repo_path,
343+
remote_repo_post_init=post_init_callback,
344+
init_cmd_args=[], # Create non-bare repo for easier testing
345+
)
346+
347+
assert callback_executed
348+
assert callback_path == repo_path
349+
assert (repo_path / "callback-marker.txt").exists()
350+
assert (repo_path / "callback-marker.txt").read_text() == "executed"
351+
352+
353+
def test_create_git_remote_repo_permission_error(
354+
tmp_path: pathlib.Path,
355+
monkeypatch: pytest.MonkeyPatch,
356+
) -> None:
357+
"""Test handling of permission errors."""
358+
repo_path = tmp_path / "test-repo"
359+
360+
def mock_init(self: Git, *args: t.Any, **kwargs: t.Any) -> str:
361+
msg = "fatal: cannot mkdir .git: Permission denied"
362+
raise CommandError(
363+
msg,
364+
returncode=128,
365+
cmd=["git", "init"],
366+
)
367+
368+
monkeypatch.setattr(Git, "init", mock_init)
369+
370+
with pytest.raises(CommandError) as exc_info:
371+
_create_git_remote_repo(repo_path)
372+
373+
assert "Permission denied" in str(exc_info.value)
374+
375+
376+
@pytest.mark.skipif(
377+
not shutil.which("git"),
378+
reason="git is not available",
379+
)
380+
def test_create_git_remote_repo_integration(tmp_path: pathlib.Path) -> None:
381+
"""Integration test with real git command."""
382+
repo_path = tmp_path / "integration-repo"
383+
384+
result = _create_git_remote_repo(repo_path, initial_branch="development")
385+
386+
assert result == repo_path
387+
assert repo_path.exists()
388+
389+
# Check actual git status
390+
git = Git(path=repo_path)
391+
392+
# Get git version to determine what to check
393+
try:
394+
version = git.version()
395+
if version.major > 2 or (version.major == 2 and version.minor >= 30):
396+
# Can check branch name on modern Git
397+
branch_output = git.run(["symbolic-ref", "HEAD"])
398+
assert "refs/heads/development" in branch_output
399+
except Exception:
400+
# Just verify it's a valid repo
401+
status = git.run(["status", "--porcelain"])
402+
assert isinstance(status, str)

0 commit comments

Comments
 (0)