import asyncio import subprocess from pathlib import Path from codexpi.commands import CommandResult from codexpi.git_service import commit, diff, ensure_identity, repository_tree, snapshot, suggest_commit_message def git(root: Path, *args: str): subprocess.run(["git", *args], cwd=root, check=True, capture_output=True) def test_git_snapshot_and_diff(tmp_path: Path): repo = tmp_path / "repo" repo.mkdir() git(repo, "init") git(repo, "config", "user.email", "test@example.invalid") git(repo, "config", "user.name", "CodexPi Test") target = repo / "hello.txt" target.write_text("hello\n") git(repo, "add", "hello.txt") git(repo, "commit", "-m", "initial") target.write_text("hello\nworld\n") state = asyncio.run(snapshot(repo)) assert state["is_repository"] is True assert state["files"][0]["path"] == "hello.txt" assert state["history"][0]["subject"] == "initial" assert state["history"][0]["files"] == [{"status": "A", "path": "hello.txt", "old_path": None}] assert "+world" in asyncio.run(diff(repo)) def test_git_snapshot_supports_repository_without_commits(tmp_path: Path): repo = tmp_path / "empty-repo" repo.mkdir() git(repo, "init") state = asyncio.run(snapshot(repo)) assert state["is_repository"] is True assert state["history"] == [] assert state["branch"] in {"main", "master"} def test_git_snapshot_reports_plain_directory_without_error(tmp_path: Path): state = asyncio.run(snapshot(tmp_path)) assert state == {"is_repository": False, "branch": None, "files": [], "history": []} def test_repository_tree_includes_tracked_and_visible_untracked_files(tmp_path: Path): repo = tmp_path / "tree" repo.mkdir() git(repo, "init") (repo / "src").mkdir() (repo / "src" / "tracked.py").write_text("print('tracked')\n", encoding="utf-8") (repo / "README.md").write_text("read me\n", encoding="utf-8") (repo / "visible.txt").write_text("visible\n", encoding="utf-8") (repo / "secret.env").write_text("SECRET=value\n", encoding="utf-8") (repo / ".gitignore").write_text("secret.env\n", encoding="utf-8") git(repo, "add", "src/tracked.py", "README.md", ".gitignore") tree = asyncio.run(repository_tree(repo)) assert tree == { "files": [".gitignore", "README.md", "src/tracked.py", "visible.txt"], "total": 4, "truncated": False, } def test_git_commit_configures_missing_identity_locally(tmp_path: Path): repo = tmp_path / "identity" repo.mkdir() git(repo, "init") (repo / "hello.txt").write_text("hello\n", encoding="utf-8") git(repo, "add", "hello.txt") asyncio.run(commit(repo, "Premier commit", "Example User")) name = subprocess.run(["git", "config", "--local", "user.name"], cwd=repo, check=True, capture_output=True, text=True).stdout.strip() email = subprocess.run(["git", "config", "--local", "user.email"], cwd=repo, check=True, capture_output=True, text=True).stdout.strip() assert name == "Example User" assert email == "example-user@codexpi.local" def test_git_identity_preserves_existing_repository_values(tmp_path: Path): repo = tmp_path / "existing-identity" repo.mkdir() git(repo, "init") git(repo, "config", "user.name", "Existing Author") git(repo, "config", "user.email", "existing@example.test") assert asyncio.run(ensure_identity(repo, "Other User")) == {"name": "Existing Author", "email": "existing@example.test"} def test_codex_suggests_a_single_line_commit_message(tmp_path: Path, monkeypatch): calls = 0 async def fake_command(argv, **_kwargs): nonlocal calls calls += 1 if calls == 1: return CommandResult(argv, 0, " M app.py\n", "") return CommandResult(argv, 0, '{"type":"item.completed","item":{"type":"agent_message","text":"Améliore le flux de commit automatique"}}\n', "") monkeypatch.setattr("codexpi.git_service.run_command", fake_command) assert asyncio.run(suggest_commit_message(tmp_path, "codex")) == "Améliore le flux de commit automatique"