270 lines
12 KiB
Python
270 lines
12 KiB
Python
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
import click
|
|
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_default_dev(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")
|
|
|
|
def test_install_ci_extras(self) -> None:
|
|
with patch("scripts.setup._run") as mock_run:
|
|
setup._install_python_deps(".venv/bin", extras="ci")
|
|
mock_run.assert_called_once_with([".venv/bin/pip", "install", "-e", ".[ci]"], ".venv/bin")
|
|
|
|
def test_install_lint_extras(self) -> None:
|
|
with patch("scripts.setup._run") as mock_run:
|
|
setup._install_python_deps(".venv/bin", extras="ci,lint")
|
|
mock_run.assert_called_once_with([".venv/bin/pip", "install", "-e", ".[ci,lint]"], ".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_raises(self) -> None:
|
|
"""tea must be installed — setup fails if it's missing."""
|
|
with patch("shutil.which", return_value=None):
|
|
with patch.dict("os.environ", {"REPO_TOKEN": "tok"}, clear=True):
|
|
with pytest.raises(click.ClickException, match="not installed"):
|
|
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
|
|
|
|
def test_extras_ci(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""--extras ci should pass 'ci' to _install_python_deps."""
|
|
monkeypatch.chdir(tmp_path)
|
|
bin_dir = tmp_path / ".venv" / "bin"
|
|
bin_dir.mkdir(parents=True)
|
|
runner = CliRunner()
|
|
with patch("scripts.setup._install_python_deps") as mock_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), "--extras", "ci"])
|
|
assert result.exit_code == 0
|
|
mock_deps.assert_called_once_with(str(bin_dir), "ci")
|
|
|
|
def test_no_ansible_collections(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""--no-ansible-collections should skip ansible collection install."""
|
|
monkeypatch.chdir(tmp_path)
|
|
bin_dir = tmp_path / ".venv" / "bin"
|
|
bin_dir.mkdir(parents=True)
|
|
runner = CliRunner()
|
|
with patch("scripts.setup._install_python_deps"):
|
|
with patch("scripts.setup._install_ansible_collections") as mock_ansible:
|
|
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), "--no-ansible-collections"],
|
|
)
|
|
assert result.exit_code == 0
|
|
mock_ansible.assert_not_called()
|
|
|
|
def test_no_pre_commit(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""--no-pre-commit should skip pre-commit hook install."""
|
|
monkeypatch.chdir(tmp_path)
|
|
bin_dir = tmp_path / ".venv" / "bin"
|
|
bin_dir.mkdir(parents=True)
|
|
runner = CliRunner()
|
|
with patch("scripts.setup._install_python_deps"):
|
|
with patch("scripts.setup._install_ansible_collections"):
|
|
with patch("scripts.setup._install_pre_commit_hooks") as mock_hooks:
|
|
with patch("scripts.setup._configure_tea_login"):
|
|
with patch("scripts.setup._verify"):
|
|
result = runner.invoke(
|
|
setup.main,
|
|
["--bin", str(bin_dir), "--no-pre-commit"],
|
|
)
|
|
assert result.exit_code == 0
|
|
mock_hooks.assert_not_called()
|
|
|
|
def test_no_tea_login(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""--no-tea-login should skip tea login configuration."""
|
|
monkeypatch.chdir(tmp_path)
|
|
bin_dir = tmp_path / ".venv" / "bin"
|
|
bin_dir.mkdir(parents=True)
|
|
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") as mock_tea:
|
|
with patch("scripts.setup._verify"):
|
|
result = runner.invoke(
|
|
setup.main,
|
|
["--bin", str(bin_dir), "--no-tea-login"],
|
|
)
|
|
assert result.exit_code == 0
|
|
mock_tea.assert_not_called()
|
|
|
|
def test_lean_ci_setup(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""Simulate the setup-ci make target: all skip flags together."""
|
|
monkeypatch.chdir(tmp_path)
|
|
bin_dir = tmp_path / ".venv" / "bin"
|
|
bin_dir.mkdir(parents=True)
|
|
runner = CliRunner()
|
|
with patch("scripts.setup._install_python_deps") as mock_deps:
|
|
with patch("scripts.setup._install_ansible_collections") as mock_ansible:
|
|
with patch("scripts.setup._install_pre_commit_hooks") as mock_hooks:
|
|
with patch("scripts.setup._configure_tea_login") as mock_tea:
|
|
with patch("scripts.setup._verify"):
|
|
result = runner.invoke(
|
|
setup.main,
|
|
[
|
|
"--bin",
|
|
str(bin_dir),
|
|
"--extras",
|
|
"ci",
|
|
"--no-ansible-collections",
|
|
"--no-pre-commit",
|
|
"--no-tea-login",
|
|
],
|
|
)
|
|
assert result.exit_code == 0
|
|
mock_deps.assert_called_once_with(str(bin_dir), "ci")
|
|
mock_ansible.assert_not_called()
|
|
mock_hooks.assert_not_called()
|
|
mock_tea.assert_not_called()
|