/
miker
/
git-ai-py
Обзор
Документация
Войти
/
miker
/
git-ai-py
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
tests/unit/test_gitcmd.py
207 строк
8 KB
Mike S
Initial commit
08 авг 2026, 16:43
08 авг 2026, 16:43
491f864
Код
Авторство
О чём код?
"""Тесты обёртки над git.""" from __future__ import annotations import os from pathlib import Path import pytest from git_ai_metrics.adapters.git import gitcmd from git_ai_metrics.util.paths import normalize from tests.conftest import git class TestRun: def test_returns_stdout(self, repo: Path) -> None: result = gitcmd.run(["rev-parse", "--is-inside-work-tree"], cwd=repo) assert result.stdout.strip() == "true" def test_raises_on_failure(self, repo: Path) -> None: with pytest.raises(gitcmd.GitError) as excinfo: gitcmd.run(["cat-file", "-p", "0" * 40], cwd=repo) assert excinfo.value.returncode != 0 def test_check_false_returns_nonzero(self, repo: Path) -> None: result = gitcmd.run(["cat-file", "-p", "0" * 40], cwd=repo, check=False) assert result.returncode != 0 def test_sets_optional_locks_off(self, repo: Path) -> None: # A4.1: без этого демон конкурирует с пользователем за index.lock. result = gitcmd.run( ["config", "--get", "--default", "unset", "core.bare"], cwd=repo, check=False ) assert result.returncode in (0, 1) env = gitcmd._git_env(keep_git_env=False) assert env["GIT_OPTIONAL_LOCKS"] == "0" def test_forces_c_locale(self) -> None: # Вывод git локализован; фиксированная локаль делает диагностику # воспроизводимой независимо от машины. env = gitcmd._git_env(keep_git_env=False) assert env["LC_ALL"] == "C" assert env["LANG"] == "C" def test_strips_inherited_git_dir(self, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv("GIT_DIR", "/somewhere/else/.git") monkeypatch.setenv("GIT_WORK_TREE", "/somewhere/else") env = gitcmd._git_env(keep_git_env=False) assert "GIT_DIR" not in env assert "GIT_WORK_TREE" not in env def test_keeps_git_env_when_asked(self, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv("GIT_DIR", "/somewhere/else/.git") env = gitcmd._git_env(keep_git_env=True) assert env["GIT_DIR"] == "/somewhere/else/.git" def test_inherited_git_dir_does_not_hijack_call( self, repo: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: # Хуки запускаются с выставленным GIT_DIR. Если бы он наследовался, # вызов ушёл бы в чужой репозиторий — порча данных без единой ошибки. other = tmp_path / "other" other.mkdir() git(other, "init", "--quiet") monkeypatch.setenv("GIT_DIR", str(other / ".git")) result = gitcmd.run(["rev-parse", "--show-toplevel"], cwd=repo) assert normalize(result.stdout.strip()) == normalize(repo) def test_timeout_raises(self, repo: Path) -> None: with pytest.raises(gitcmd.GitTimeoutError): # `git help --all` заведомо не уложится в нулевой таймаут. gitcmd.run(["help", "--all"], cwd=repo, timeout=0.001) def test_missing_git_binary(self, repo: Path, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv("PATH", str(repo)) with pytest.raises(gitcmd.GitError, match="не найден"): gitcmd.run(["status"], cwd=repo) class TestContext: def test_basic_repo(self, repo: Path) -> None: ctx = gitcmd.context(repo) assert ctx.repo_root == normalize(repo) assert ctx.git_dir == normalize(repo / ".git") assert ctx.git_common_dir == normalize(repo / ".git") assert ctx.head_sha is not None assert ctx.head_ref == "refs/heads/main" def test_relative_git_dir_is_resolved(self, repo: Path) -> None: # Из корня репозитория git отдаёт ".git" относительным путём. ctx = gitcmd.context(repo) assert ctx.git_dir.is_absolute() assert ctx.git_common_dir.is_absolute() def test_from_subdirectory(self, repo: Path) -> None: nested = repo / "a" / "b" nested.mkdir(parents=True) ctx = gitcmd.context(nested) assert ctx.repo_root == normalize(repo) assert ctx.git_dir.is_absolute() def test_unborn_repo(self, empty_repo: Path) -> None: ctx = gitcmd.context(empty_repo) assert ctx.is_unborn assert ctx.head_sha is None # Ветка уже существует, хотя коммита ещё нет — это даёт ref # для самого первого коммита. assert ctx.head_ref == "refs/heads/main" assert not ctx.is_detached def test_detached_head(self, repo: Path) -> None: git(repo, "checkout", "--quiet", "--detach", "HEAD") ctx = gitcmd.context(repo) # §7.2: при detached HEAD ref обязан быть None, а SHA — присутствовать. assert ctx.is_detached assert ctx.head_ref is None assert ctx.head_sha is not None def test_outside_repository(self, tmp_path: Path) -> None: outside = tmp_path / "plain" outside.mkdir() with pytest.raises(gitcmd.NotARepositoryError): gitcmd.context(outside) def test_bare_repo_rejected(self, tmp_path: Path) -> None: bare = tmp_path / "bare.git" bare.mkdir() git(bare, "init", "--bare", "--quiet") # Рабочего дерева нет — атрибутировать нечего. with pytest.raises(gitcmd.NotARepositoryError): gitcmd.context(bare) def test_symlinked_path_normalized(self, repo: Path, tmp_path: Path) -> None: alias = tmp_path / "alias" alias.symlink_to(repo) assert gitcmd.context(alias).repo_root == gitcmd.context(repo).repo_root class TestWorktrees: def test_linked_worktree_distinguished(self, repo_with_worktree: tuple[Path, Path]) -> None: main, linked = repo_with_worktree main_ctx = gitcmd.context(main) linked_ctx = gitcmd.context(linked) # AC#2: разные worktree одного репозитория. assert not main_ctx.is_linked_worktree assert linked_ctx.is_linked_worktree def test_worktrees_share_common_dir(self, repo_with_worktree: tuple[Path, Path]) -> None: main, linked = repo_with_worktree # Общий git_common_dir — это то, что делает их одним репозиторием. assert gitcmd.context(main).git_common_dir == gitcmd.context(linked).git_common_dir def test_worktrees_have_distinct_git_dirs( self, repo_with_worktree: tuple[Path, Path] ) -> None: main, linked = repo_with_worktree assert gitcmd.context(main).git_dir != gitcmd.context(linked).git_dir def test_main_worktree_git_dir_equals_common_dir(self, repo: Path) -> None: ctx = gitcmd.context(repo) # Именно это вырождение делает hash(git_dir) непригодным в роли # worktree_id для главного worktree (A2.9). assert ctx.git_dir == ctx.git_common_dir def test_linked_worktree_has_own_branch(self, repo_with_worktree: tuple[Path, Path]) -> None: _, linked = repo_with_worktree assert gitcmd.context(linked).head_ref == "refs/heads/feature" class TestIsolation: def test_global_hooks_path_not_leaking(self, repo: Path) -> None: # Проверяет саму изоляцию тестов: если бы глобальный core.hooksPath # машины протекал, здесь оказался бы путь постороннего инструмента. result = gitcmd.run( ["config", "--get", "--default", "", "core.hooksPath"], cwd=repo, check=False ) value = result.stdout.strip() assert value == "" or value.startswith(os.fspath(repo))