Skip to content

Commit 4ea1494

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 5467031 commit 4ea1494

1 file changed

Lines changed: 215 additions & 0 deletions

File tree

tests/test_pytest_plugin.py

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111
import pytest
1212

1313
from libvcs._internal.run import run
14+
from libvcs.cmd.git import Git
15+
from libvcs.exc import CommandError
16+
from libvcs.pytest_plugin import (
17+
_create_git_remote_repo,
18+
)
1419

1520
if t.TYPE_CHECKING:
1621
import pathlib
@@ -311,3 +316,213 @@ def test_git_repo_fixture_submodule_file_protocol(
311316
f"git submodule add failed: {result}\n"
312317
"git_repo fixture needs set_home dependency for child processes"
313318
)
319+
320+
321+
def test_create_git_remote_repo_basic(tmp_path: pathlib.Path) -> None:
322+
"""Test basic git repository creation."""
323+
repo_path = tmp_path / "test-repo"
324+
325+
result = _create_git_remote_repo(repo_path, init_cmd_args=[])
326+
327+
assert result == repo_path
328+
assert repo_path.exists()
329+
assert (repo_path / ".git").exists()
330+
331+
332+
def test_create_git_remote_repo_bare(tmp_path: pathlib.Path) -> None:
333+
"""Test bare git repository creation."""
334+
repo_path = tmp_path / "test-repo.git"
335+
336+
result = _create_git_remote_repo(repo_path, init_cmd_args=["--bare"])
337+
338+
assert result == repo_path
339+
assert repo_path.exists()
340+
assert (repo_path / "HEAD").exists()
341+
assert not (repo_path / ".git").exists()
342+
343+
344+
def test_create_git_remote_repo_with_initial_branch(
345+
tmp_path: pathlib.Path,
346+
monkeypatch: pytest.MonkeyPatch,
347+
) -> None:
348+
"""Test repository creation with custom initial branch.
349+
350+
This test checks both modern Git (2.30.0+) and fallback behavior.
351+
"""
352+
repo_path = tmp_path / "test-repo"
353+
354+
# Track Git.init calls
355+
init_calls: list[dict[str, t.Any]] = []
356+
357+
def mock_init(self: Git, *args: t.Any, **kwargs: t.Any) -> str:
358+
init_calls.append({"args": args, "kwargs": kwargs})
359+
360+
# Simulate old Git that doesn't support --initial-branch
361+
if kwargs.get("initial_branch"):
362+
msg = "error: unknown option `initial-branch'"
363+
raise CommandError(
364+
msg,
365+
returncode=1,
366+
cmd=["git", "init", "--initial-branch=main"],
367+
)
368+
369+
# Create the repo directory to simulate successful init
370+
self.path.mkdir(exist_ok=True)
371+
(self.path / ".git").mkdir(exist_ok=True)
372+
return "Initialized empty Git repository"
373+
374+
monkeypatch.setattr(Git, "init", mock_init)
375+
376+
result = _create_git_remote_repo(repo_path, initial_branch="develop")
377+
378+
# Should have tried twice: once with initial_branch, once without
379+
assert len(init_calls) == 2
380+
assert init_calls[0]["kwargs"].get("initial_branch") == "develop"
381+
assert "initial_branch" not in init_calls[1]["kwargs"]
382+
assert result == repo_path
383+
384+
385+
def test_create_git_remote_repo_modern_git(
386+
tmp_path: pathlib.Path,
387+
monkeypatch: pytest.MonkeyPatch,
388+
) -> None:
389+
"""Test repository creation with Git 2.30.0+ that supports --initial-branch."""
390+
repo_path = tmp_path / "test-repo"
391+
392+
init_calls: list[dict[str, t.Any]] = []
393+
394+
def mock_init(self: Git, *args: t.Any, **kwargs: t.Any) -> str:
395+
init_calls.append({"args": args, "kwargs": kwargs})
396+
# Simulate successful init with --initial-branch support
397+
self.path.mkdir(exist_ok=True)
398+
(self.path / ".git").mkdir(exist_ok=True)
399+
branch = kwargs.get("initial_branch", "master")
400+
return f"Initialized empty Git repository with initial branch '{branch}'"
401+
402+
monkeypatch.setattr(Git, "init", mock_init)
403+
404+
result = _create_git_remote_repo(repo_path, initial_branch="main")
405+
406+
# Should only call init once since it succeeded
407+
assert len(init_calls) == 1
408+
assert init_calls[0]["kwargs"].get("initial_branch") == "main"
409+
assert result == repo_path
410+
411+
412+
@pytest.mark.parametrize(
413+
("module_default", "param", "expected_branch"),
414+
[
415+
("custom-env", None, "custom-env"), # Module default propagates when no param
416+
("custom-env", "param-override", "param-override"), # Param overrides default
417+
("master", "explicit-param", "explicit-param"), # Param wins
418+
("master", None, "master"), # Module default
419+
],
420+
)
421+
def test_create_git_remote_repo_branch_configuration(
422+
tmp_path: pathlib.Path,
423+
monkeypatch: pytest.MonkeyPatch,
424+
module_default: str,
425+
param: str | None,
426+
expected_branch: str,
427+
) -> None:
428+
"""Test initial branch configuration hierarchy."""
429+
from libvcs import pytest_plugin
430+
431+
monkeypatch.setattr(pytest_plugin, "DEFAULT_GIT_INITIAL_BRANCH", module_default)
432+
433+
repo_path = tmp_path / "test-repo"
434+
435+
# Track what branch was used
436+
used_branch = None
437+
438+
def mock_init(self: Git, *args: t.Any, **kwargs: t.Any) -> str:
439+
nonlocal used_branch
440+
used_branch = kwargs.get("initial_branch")
441+
self.path.mkdir(exist_ok=True)
442+
(self.path / ".git").mkdir(exist_ok=True)
443+
return "Initialized"
444+
445+
monkeypatch.setattr(Git, "init", mock_init)
446+
447+
pytest_plugin._create_git_remote_repo(repo_path, initial_branch=param)
448+
449+
assert used_branch == expected_branch
450+
451+
452+
def test_create_git_remote_repo_post_init_callback(tmp_path: pathlib.Path) -> None:
453+
"""Test that post-init callback is executed."""
454+
repo_path = tmp_path / "test-repo"
455+
callback_executed = False
456+
callback_path = None
457+
458+
def post_init_callback(
459+
remote_repo_path: pathlib.Path,
460+
env: t.Any = None,
461+
) -> None:
462+
nonlocal callback_executed, callback_path
463+
callback_executed = True
464+
callback_path = remote_repo_path
465+
(remote_repo_path / "callback-marker.txt").write_text("executed")
466+
467+
_create_git_remote_repo(
468+
repo_path,
469+
remote_repo_post_init=post_init_callback,
470+
init_cmd_args=[], # Create non-bare repo for easier testing
471+
)
472+
473+
assert callback_executed
474+
assert callback_path == repo_path
475+
assert (repo_path / "callback-marker.txt").exists()
476+
assert (repo_path / "callback-marker.txt").read_text() == "executed"
477+
478+
479+
def test_create_git_remote_repo_permission_error(
480+
tmp_path: pathlib.Path,
481+
monkeypatch: pytest.MonkeyPatch,
482+
) -> None:
483+
"""Test handling of permission errors."""
484+
repo_path = tmp_path / "test-repo"
485+
486+
def mock_init(self: Git, *args: t.Any, **kwargs: t.Any) -> str:
487+
msg = "fatal: cannot mkdir .git: Permission denied"
488+
raise CommandError(
489+
msg,
490+
returncode=128,
491+
cmd=["git", "init"],
492+
)
493+
494+
monkeypatch.setattr(Git, "init", mock_init)
495+
496+
with pytest.raises(CommandError) as exc_info:
497+
_create_git_remote_repo(repo_path)
498+
499+
assert "Permission denied" in str(exc_info.value)
500+
501+
502+
@pytest.mark.skipif(
503+
not shutil.which("git"),
504+
reason="git is not available",
505+
)
506+
def test_create_git_remote_repo_integration(tmp_path: pathlib.Path) -> None:
507+
"""Integration test with real git command."""
508+
repo_path = tmp_path / "integration-repo"
509+
510+
result = _create_git_remote_repo(repo_path, initial_branch="development")
511+
512+
assert result == repo_path
513+
assert repo_path.exists()
514+
515+
# Check actual git status
516+
git = Git(path=repo_path)
517+
518+
# Get git version to determine what to check
519+
try:
520+
version = git.version()
521+
if version.major > 2 or (version.major == 2 and version.minor >= 30):
522+
# Can check branch name on modern Git
523+
branch_output = git.run(["symbolic-ref", "HEAD"])
524+
assert "refs/heads/development" in branch_output
525+
except Exception:
526+
# Just verify it's a valid repo
527+
status = git.run(["status", "--porcelain"])
528+
assert isinstance(status, str)

0 commit comments

Comments
 (0)