import subprocess import sys from pathlib import Path from unittest.mock import Mock from test_config import settings from lai_automation.git import GitWorkspace def test_prepare_uses_temporary_authentication_for_private_clone(tmp_path, monkeypatch) -> None: workspace = GitWorkspace(settings(workspace_root=tmp_path, gitea_token="token"), 10) calls = [] def run(command, **kwargs): calls.append((command, kwargs)) if command[1] == "clone": askpass = Path(kwargs["env"]["GIT_ASKPASS"]) assert askpass.is_file() assert Path(kwargs["env"]["GIT_BOT_USER_FILE"]).read_text() == "bot" assert Path(kwargs["env"]["GIT_BOT_TOKEN_FILE"]).read_text() == "token" workspace.path.mkdir() return subprocess.CompletedProcess(command, 0, "", "") monkeypatch.setattr("lai_automation.git.subprocess.run", run) workspace.prepare() clone_command, clone_options = calls[0] assert clone_command[:2] == ["git", "clone"] assert "token" not in " ".join(clone_command) assert clone_options["cwd"] == tmp_path assert not tuple(tmp_path.glob(".lai-git-auth-*")) def test_prepare_surfaces_clone_stderr_without_credentials(tmp_path, monkeypatch) -> None: workspace = GitWorkspace(settings(workspace_root=tmp_path, gitea_token="token"), 11) def run(command, **kwargs): raise subprocess.CalledProcessError( 128, command, output="", stderr="fatal: upstream returned 403", ) monkeypatch.setattr("lai_automation.git.subprocess.run", run) try: workspace.prepare() except RuntimeError as exception: message = str(exception) else: raise AssertionError("Expected clone failure") assert "Source repository clone failed" in message assert "upstream returned 403" in message assert "token" not in message assert not tuple(tmp_path.glob(".lai-git-auth-*")) def test_test_command_does_not_interpret_shell_metacharacters(tmp_path) -> None: workspace = GitWorkspace(settings(workspace_root=tmp_path), 1) workspace.path.mkdir() result = workspace.run("printf safe ; touch injected", check=False) assert result.stdout == "safe" assert not (workspace.path / "injected").exists() def test_extra_environment_is_passed_without_privilege_drop(tmp_path) -> None: workspace = GitWorkspace(settings(workspace_root=tmp_path), 6) workspace.path.mkdir() result = workspace.run_args( [sys.executable, "-c", "import os; print(os.environ['SAFE_TEST_VALUE'])"], extra_environment={"SAFE_TEST_VALUE": "available"}, ) assert result.stdout.strip() == "available" def test_coder_commands_use_native_process_identity(tmp_path, monkeypatch) -> None: workspace = GitWorkspace(settings(workspace_root=tmp_path), 9) workspace.path.mkdir() captured = {} def run(command, **kwargs): captured.update(kwargs) return subprocess.CompletedProcess(command, 0, "ok", "") monkeypatch.setattr("lai_automation.git.subprocess.run", run) monkeypatch.setattr( "lai_automation.git.pwd.getpwnam", lambda _: Mock(pw_dir="/home/coder", pw_uid=10001, pw_gid=10001), ) workspace.run_args(["php", "-v"], username="coder") assert captured["user"] == 10001 assert captured["group"] == 10001 assert captured["extra_groups"] == () assert "preexec_fn" not in captured def test_summary_includes_untracked_files(tmp_path) -> None: workspace = GitWorkspace(settings(workspace_root=tmp_path), 2) workspace.path.mkdir() subprocess.run(["git", "init"], cwd=workspace.path, check=True, capture_output=True) (workspace.path / "new.php").write_text(" None: workspace = GitWorkspace(settings(workspace_root=tmp_path), 7) workspace.path.mkdir() subprocess.run(["git", "init"], cwd=workspace.path, check=True, capture_output=True) tracked = workspace.path / "config.php" tracked.write_text(" None: workspace = GitWorkspace(settings(workspace_root=tmp_path), 3) workspace.path.mkdir() subprocess.run(["git", "init"], cwd=workspace.path, check=True, capture_output=True) subprocess.run( ["git", "config", "user.email", "test@example.invalid"], cwd=workspace.path, check=True, ) subprocess.run( ["git", "config", "user.name", "Test"], cwd=workspace.path, check=True, ) tracked = workspace.path / "tracked.php" tracked.write_text(" None: workspace = GitWorkspace(settings(workspace_root=tmp_path), 4) workspace.path.mkdir() subprocess.run(["git", "init"], cwd=workspace.path, check=True, capture_output=True) subprocess.run( ["git", "config", "user.email", "test@example.invalid"], cwd=workspace.path, check=True, ) subprocess.run( ["git", "config", "user.name", "Test"], cwd=workspace.path, check=True, ) tracked = workspace.path / "config.php" tracked.write_text(" None: workspace = GitWorkspace(settings(workspace_root=tmp_path), 5) workspace.path.mkdir() subprocess.run(["git", "init"], cwd=workspace.path, check=True, capture_output=True) tracked = workspace.path / "service.php" tracked.write_text(" None: workspace = GitWorkspace(settings(workspace_root=tmp_path), 8) workspace.path.mkdir() subprocess.run(["git", "init"], cwd=workspace.path, check=True, capture_output=True) tracked = workspace.path / "config.php" tracked.write_text("