from __future__ import annotations from pathlib import Path from unittest.mock import patch import pytest from click.testing import CliRunner import scripts.setup as setup class TestRun: def test_run_success(self) -> None: with patch("subprocess.run") as mock_run: setup._run(["echo", "hello"], ".venv/bin") mock_run.assert_called_once_with(["echo", "hello"], check=True) def test_run_failure(self) -> None: import subprocess with patch("subprocess.run", side_effect=subprocess.CalledProcessError(1, ["echo"])): with pytest.raises(subprocess.CalledProcessError): setup._run(["echo", "hello"], ".venv/bin") class TestInstallPythonDeps: def test_install(self) -> None: with patch("scripts.setup._run") as mock_run: setup._install_python_deps(".venv/bin") mock_run.assert_called_once_with([".venv/bin/pip", "install", "-e", ".[dev]"], ".venv/bin") class TestInstallAnsibleCollections: def test_install(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.chdir(tmp_path) (tmp_path / "ansible").mkdir() (tmp_path / "ansible/requirements.yml").write_text("collections: []") with patch("scripts.setup._run") as mock_run: setup._install_ansible_collections(".venv/bin") mock_run.assert_called_once() def test_no_requirements(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.chdir(tmp_path) with patch("scripts.setup._run") as mock_run: setup._install_ansible_collections(".venv/bin") mock_run.assert_not_called() class TestInstallPreCommitHooks: def test_install(self) -> None: with patch("scripts.setup._run") as mock_run: setup._install_pre_commit_hooks(".venv/bin") assert mock_run.call_count == 3 calls = [c.args[0] for c in mock_run.call_args_list] # Each call should have the pre-commit binary and --hook-type flag for call in calls: assert ".venv/bin/pre-commit" in call[0] assert "--hook-type" in call class TestVerify: def test_verify_success(self) -> None: import subprocess mock_result = subprocess.CompletedProcess( args=["grm", "--version"], returncode=0, stdout="grm 1.0.0", stderr="" ) with patch("subprocess.run", return_value=mock_result): setup._verify(".venv/bin") def test_verify_not_found(self) -> None: with patch("subprocess.run", side_effect=FileNotFoundError()): setup._verify(".venv/bin") def test_verify_timeout(self) -> None: import subprocess with patch("subprocess.run", side_effect=subprocess.TimeoutExpired(cmd=["grm", "--version"], timeout=10)): setup._verify(".venv/bin") class TestConfigureTeaLogin: def test_tea_not_installed(self) -> None: with patch("shutil.which", return_value=None): with patch.dict("os.environ", {"REPO_TOKEN": "tok"}, clear=True): setup._configure_tea_login() def test_no_repo_token(self) -> None: with patch("shutil.which", return_value="/usr/bin/tea"): with patch.dict("os.environ", {}, clear=True): setup._configure_tea_login() def test_login_already_exists(self) -> None: import subprocess mock_result = subprocess.CompletedProcess( args=["tea", "login", "list"], returncode=0, stdout="grm https://git.example.com", stderr="" ) with patch("shutil.which", return_value="/usr/bin/tea"): with patch.dict("os.environ", {"REPO_TOKEN": "tok"}, clear=True): with patch("subprocess.run", return_value=mock_result): setup._configure_tea_login() def test_login_added_successfully(self) -> None: import subprocess list_result = subprocess.CompletedProcess(args=["tea", "login", "list"], returncode=0, stdout="", stderr="") add_result = subprocess.CompletedProcess( args=["tea", "login", "add"], returncode=0, stdout="Login added", stderr="" ) default_result = subprocess.CompletedProcess( args=["tea", "login", "default"], returncode=0, stdout="", stderr="" ) with patch("shutil.which", return_value="/usr/bin/tea"): with patch.dict("os.environ", {"REPO_TOKEN": "tok"}, clear=True): with patch("subprocess.run", side_effect=[list_result, add_result, default_result]): setup._configure_tea_login() def test_login_add_failure(self) -> None: import subprocess list_result = subprocess.CompletedProcess(args=["tea", "login", "list"], returncode=0, stdout="", stderr="") add_result = subprocess.CompletedProcess( args=["tea", "login", "add"], returncode=1, stdout="", stderr="auth failed" ) with patch("shutil.which", return_value="/usr/bin/tea"): with patch.dict("os.environ", {"REPO_TOKEN": "tok"}, clear=True): with patch("subprocess.run", side_effect=[list_result, add_result]): setup._configure_tea_login() class TestMain: def test_bin_not_found(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.chdir(tmp_path) runner = CliRunner() result = runner.invoke(setup.main, ["--bin", "nonexistent/bin"]) assert result.exit_code != 0 assert "Bin directory not found" in result.output def test_success(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.chdir(tmp_path) bin_dir = tmp_path / ".venv" / "bin" bin_dir.mkdir(parents=True) (bin_dir / "grm").touch() (bin_dir / "pre-commit").touch() (tmp_path / "ansible").mkdir() (tmp_path / "ansible/requirements.yml").write_text("collections: []") runner = CliRunner() with patch("scripts.setup._install_python_deps"): with patch("scripts.setup._install_ansible_collections"): with patch("scripts.setup._install_pre_commit_hooks"): with patch("scripts.setup._configure_tea_login"): with patch("scripts.setup._verify"): result = runner.invoke(setup.main, ["--bin", str(bin_dir)]) assert result.exit_code == 0 assert "Setup complete" in result.output