Public Access
611 lines
26 KiB
Python
611 lines
26 KiB
Python
"""Unit tests for devx.tools.setup."""
|
|
|
|
import os
|
|
import subprocess
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
from click.testing import CliRunner
|
|
|
|
from devx.tools.setup import (
|
|
_configure_tea_login,
|
|
_install_ansible_collections,
|
|
_install_pre_commit_hooks,
|
|
_install_python_deps,
|
|
_run,
|
|
_try_gitea_mirror_install,
|
|
_verify,
|
|
main,
|
|
)
|
|
|
|
|
|
class TestRun:
|
|
@patch("devx.tools.setup.subprocess.run")
|
|
def test_run_success(self, mock_run: MagicMock) -> None:
|
|
_run(["echo", "hello"])
|
|
mock_run.assert_called_once_with(["echo", "hello"], check=True)
|
|
|
|
@patch("devx.tools.setup.subprocess.run")
|
|
def test_run_failure_raises(self, mock_run: MagicMock) -> None:
|
|
mock_run.side_effect = subprocess.CalledProcessError(1, ["echo", "hello"])
|
|
with pytest.raises(subprocess.CalledProcessError):
|
|
_run(["echo", "hello"])
|
|
|
|
|
|
class TestInstallPythonDeps:
|
|
@patch("devx.tools.setup.subprocess.run")
|
|
@patch.dict(os.environ, {}, clear=False)
|
|
def test_install_dev(self, mock_run: MagicMock) -> None:
|
|
os.environ.pop("PIP_BREAK_SYSTEM_PACKAGES", None)
|
|
mock_run.return_value = MagicMock(returncode=0)
|
|
_install_python_deps(".venv/bin", "dev")
|
|
mock_run.assert_called_once_with([".venv/bin/pip", "install", "-e", ".[dev]"], check=False)
|
|
|
|
@patch("devx.tools.setup.subprocess.run")
|
|
@patch.dict(os.environ, {}, clear=False)
|
|
def test_install_ci(self, mock_run: MagicMock) -> None:
|
|
os.environ.pop("PIP_BREAK_SYSTEM_PACKAGES", None)
|
|
mock_run.return_value = MagicMock(returncode=0)
|
|
_install_python_deps(".venv/bin", "ci")
|
|
mock_run.assert_called_once_with([".venv/bin/pip", "install", "-e", ".[ci]"], check=False)
|
|
|
|
@patch("devx.tools.setup.subprocess.run")
|
|
@patch.dict(os.environ, {}, clear=False)
|
|
def test_install_custom_extras(self, mock_run: MagicMock) -> None:
|
|
os.environ.pop("PIP_BREAK_SYSTEM_PACKAGES", None)
|
|
mock_run.return_value = MagicMock(returncode=0)
|
|
_install_python_deps(".venv/bin", "ci,lint")
|
|
mock_run.assert_called_once_with([".venv/bin/pip", "install", "-e", ".[ci,lint]"], check=False)
|
|
|
|
@patch("devx.tools.setup.subprocess.run")
|
|
def test_install_with_break_system_packages(self, mock_run: MagicMock) -> None:
|
|
mock_run.return_value = MagicMock(returncode=0)
|
|
with patch.dict(os.environ, {"PIP_BREAK_SYSTEM_PACKAGES": "1"}):
|
|
_install_python_deps(".venv/bin", "ci")
|
|
mock_run.assert_called_once_with(
|
|
[".venv/bin/pip", "install", "-e", ".[ci]", "--break-system-packages"], check=False
|
|
)
|
|
|
|
@patch("devx.tools.setup._run")
|
|
@patch("devx.tools.setup.subprocess.run")
|
|
def test_install_retry_with_ignore_installed(self, mock_subprocess: MagicMock, mock_run: MagicMock) -> None:
|
|
mock_subprocess.return_value = MagicMock(returncode=1)
|
|
with patch.dict(os.environ, {"PIP_BREAK_SYSTEM_PACKAGES": "1"}):
|
|
_install_python_deps(".venv/bin", "ci")
|
|
mock_run.assert_called_once_with(
|
|
[".venv/bin/pip", "install", "-e", ".[ci]", "--break-system-packages", "--ignore-installed"]
|
|
)
|
|
|
|
@patch("devx.tools.setup.subprocess.run")
|
|
@patch.dict(os.environ, {}, clear=False)
|
|
def test_install_failure_without_break_system(self, mock_run: MagicMock) -> None:
|
|
os.environ.pop("PIP_BREAK_SYSTEM_PACKAGES", None)
|
|
mock_run.return_value = MagicMock(returncode=1)
|
|
with pytest.raises(subprocess.CalledProcessError):
|
|
_install_python_deps(".venv/bin", "ci")
|
|
|
|
|
|
class TestInstallPreCommitHooks:
|
|
@patch("devx.tools.setup._run")
|
|
def test_installs_all_hook_types(self, mock_run: MagicMock) -> None:
|
|
_install_pre_commit_hooks(".venv/bin")
|
|
assert mock_run.call_count == 3
|
|
hook_types = [call.args[0][3] for call in mock_run.call_args_list]
|
|
assert "pre-commit" in hook_types
|
|
assert "commit-msg" in hook_types
|
|
assert "pre-push" in hook_types
|
|
|
|
|
|
class TestInstallAnsibleCollections:
|
|
@patch("devx.tools.setup.shutil.which", return_value="/usr/local/bin/ansible-galaxy")
|
|
@patch("devx.tools.setup._run")
|
|
def test_installs_from_requirements(self, mock_run: MagicMock, mock_which: MagicMock, tmp_path: Path) -> None:
|
|
req = tmp_path / "ansible" / "requirements.yml"
|
|
req.parent.mkdir(parents=True)
|
|
req.write_text("collections: []")
|
|
with patch("devx.tools.setup.Path") as mock_path:
|
|
mock_path.return_value.exists.return_value = True
|
|
mock_path.return_value.__str__ = lambda _: str(req)
|
|
mock_path.return_value.read_text = lambda: req.read_text()
|
|
_install_ansible_collections(".venv/bin")
|
|
mock_run.assert_called_once()
|
|
|
|
@patch("devx.tools.setup._run")
|
|
def test_skips_when_no_requirements(self, mock_run: MagicMock) -> None:
|
|
_install_ansible_collections(".venv/bin")
|
|
mock_run.assert_not_called()
|
|
|
|
@patch("tenacity.nap.time.sleep")
|
|
@patch("devx.tools.setup.shutil.which", return_value="/usr/local/bin/ansible-galaxy")
|
|
@patch("devx.tools.setup._run")
|
|
def test_retries_on_transient_failure(
|
|
self, mock_run: MagicMock, mock_which: MagicMock, mock_sleep: MagicMock, tmp_path: Path
|
|
) -> None:
|
|
"""ansible-galaxy install should retry on transient network errors."""
|
|
import subprocess as _subprocess
|
|
|
|
req = tmp_path / "ansible" / "requirements.yml"
|
|
req.parent.mkdir(parents=True)
|
|
req.write_text("collections: []")
|
|
# First call fails (timeout), second succeeds
|
|
mock_run.side_effect = [
|
|
_subprocess.CalledProcessError(1, ["ansible-galaxy", "collection", "install"]),
|
|
None,
|
|
]
|
|
with patch("devx.tools.setup.Path") as mock_path:
|
|
mock_path.return_value.exists.return_value = True
|
|
mock_path.return_value.__str__ = lambda _: str(req)
|
|
mock_path.return_value.read_text = lambda: req.read_text()
|
|
_install_ansible_collections(".venv/bin")
|
|
assert mock_run.call_count == 2
|
|
|
|
@patch("tenacity.nap.time.sleep")
|
|
@patch("devx.tools.setup.shutil.which", return_value="/usr/local/bin/ansible-galaxy")
|
|
@patch("devx.tools.setup._run")
|
|
def test_exhausts_retries_then_raises(
|
|
self, mock_run: MagicMock, mock_which: MagicMock, mock_sleep: MagicMock, tmp_path: Path
|
|
) -> None:
|
|
"""After 3 attempts, the error should propagate."""
|
|
import subprocess as _subprocess
|
|
|
|
req = tmp_path / "ansible" / "requirements.yml"
|
|
req.parent.mkdir(parents=True)
|
|
req.write_text("collections: []")
|
|
mock_run.side_effect = _subprocess.CalledProcessError(1, ["ansible-galaxy"])
|
|
with patch("devx.tools.setup.Path") as mock_path:
|
|
mock_path.return_value.exists.return_value = True
|
|
mock_path.return_value.__str__ = lambda _: str(req)
|
|
mock_path.return_value.read_text = lambda: req.read_text()
|
|
with pytest.raises(_subprocess.CalledProcessError):
|
|
_install_ansible_collections(".venv/bin")
|
|
assert mock_run.call_count == 3
|
|
|
|
@patch("devx.tools.setup._try_gitea_mirror_install", return_value=True)
|
|
@patch("devx.tools.setup.shutil.which", return_value="/usr/local/bin/ansible-galaxy")
|
|
@patch("devx.tools.setup._run")
|
|
def test_mirror_install_skips_galaxy_fallback(
|
|
self, mock_run: MagicMock, mock_which: MagicMock, mock_mirror: MagicMock, tmp_path: Path
|
|
) -> None:
|
|
"""When mirror install succeeds, galaxy fallback is not called."""
|
|
req = tmp_path / "ansible" / "requirements.yml"
|
|
req.parent.mkdir(parents=True)
|
|
req.write_text("collections: []")
|
|
with patch("devx.tools.setup.Path") as mock_path:
|
|
mock_path.return_value.exists.return_value = True
|
|
mock_path.return_value.__str__ = lambda _: str(req)
|
|
mock_path.return_value.read_text = lambda: req.read_text()
|
|
_install_ansible_collections(".venv/bin")
|
|
# _run should not be called because mirror install returns True
|
|
mock_run.assert_not_called()
|
|
|
|
|
|
class TestTryGiteaMirrorInstall:
|
|
"""Tests for _try_gitea_mirror_install — Gitea mirror with auth + fallback."""
|
|
|
|
_GITEA_URL = "https://git.example.com/api/packages/org/generic/ansible-collections/1.0.0/ansible-posix-1.0.0.tar.gz"
|
|
|
|
@patch.dict(os.environ, {}, clear=True)
|
|
def test_no_url_entries_returns_false(self, tmp_path: Path) -> None:
|
|
"""Requirements without type: url entries should return False."""
|
|
req = tmp_path / "requirements.yml"
|
|
req.write_text("collections:\n - name: ansible.posix\n version: '1.0.0'\n")
|
|
result = _try_gitea_mirror_install("ansible-galaxy", req)
|
|
assert result is False
|
|
|
|
@patch.dict(os.environ, {}, clear=True)
|
|
def test_no_token_returns_false(self, tmp_path: Path) -> None:
|
|
"""No Gitea token set → return False to fall back to galaxy."""
|
|
req = tmp_path / "requirements.yml"
|
|
req.write_text(
|
|
"collections:\n"
|
|
" - name: ansible.posix\n"
|
|
" version: '1.0.0'\n"
|
|
" type: url\n"
|
|
f" source: '{self._GITEA_URL}'\n"
|
|
)
|
|
result = _try_gitea_mirror_install("ansible-galaxy", req)
|
|
assert result is False
|
|
|
|
@patch.dict(os.environ, {"CI_GITEA_TOKEN": "tok123"}, clear=True)
|
|
def test_yaml_parse_error_returns_false(self, tmp_path: Path) -> None:
|
|
"""Malformed YAML → return False."""
|
|
req = tmp_path / "requirements.yml"
|
|
req.write_text("not: valid: yaml: [[")
|
|
result = _try_gitea_mirror_install("ansible-galaxy", req)
|
|
assert result is False
|
|
|
|
@patch("devx.tools.setup._run")
|
|
@patch("urllib.request.urlopen")
|
|
@patch.dict(os.environ, {"CI_GITEA_TOKEN": "tok123"}, clear=True)
|
|
def test_successful_mirror_install(self, mock_urlopen: MagicMock, mock_run: MagicMock, tmp_path: Path) -> None:
|
|
"""Valid URL entries + token → downloads with auth and installs offline."""
|
|
req = tmp_path / "requirements.yml"
|
|
req.write_text(
|
|
f"collections:\n"
|
|
f" - name: ansible.posix\n"
|
|
f" version: '1.0.0'\n"
|
|
f" type: url\n"
|
|
f" source: '{self._GITEA_URL}'\n"
|
|
)
|
|
mock_resp = MagicMock()
|
|
mock_resp.read.return_value = b"fake-tarball"
|
|
mock_resp.__enter__ = lambda _: mock_resp
|
|
mock_resp.__exit__ = lambda *a: None
|
|
mock_urlopen.return_value = mock_resp
|
|
|
|
result = _try_gitea_mirror_install("ansible-galaxy", req)
|
|
assert result is True
|
|
|
|
# Verify auth header was added
|
|
call_args = mock_urlopen.call_args[0][0]
|
|
assert call_args.get_header("Authorization") == "token tok123"
|
|
|
|
# Verify offline install was called
|
|
install_cmd = mock_run.call_args[0][0]
|
|
assert "collection" in install_cmd
|
|
assert "install" in install_cmd
|
|
assert "--offline" in install_cmd
|
|
|
|
@patch("urllib.request.urlopen")
|
|
@patch.dict(os.environ, {"CI_GITEA_TOKEN": "tok123"}, clear=True)
|
|
def test_download_failure_returns_false(self, mock_urlopen: MagicMock, tmp_path: Path) -> None:
|
|
"""Download failure → return False to fall back to galaxy."""
|
|
req = tmp_path / "requirements.yml"
|
|
req.write_text(
|
|
f"collections:\n"
|
|
f" - name: ansible.posix\n"
|
|
f" version: '1.0.0'\n"
|
|
f" type: url\n"
|
|
f" source: '{self._GITEA_URL}'\n"
|
|
)
|
|
mock_urlopen.side_effect = Exception("401 Unauthorized")
|
|
|
|
result = _try_gitea_mirror_install("ansible-galaxy", req)
|
|
assert result is False
|
|
|
|
@patch("devx.tools.setup._run")
|
|
@patch("urllib.request.urlopen")
|
|
@patch.dict(os.environ, {"CI_GITEA_API_TOKEN": "tok456"}, clear=True)
|
|
def test_prefers_api_token_over_legacy(self, mock_urlopen: MagicMock, mock_run: MagicMock, tmp_path: Path) -> None:
|
|
"""CI_GITEA_API_TOKEN takes priority over CI_GITEA_TOKEN."""
|
|
req = tmp_path / "requirements.yml"
|
|
req.write_text(
|
|
f"collections:\n"
|
|
f" - name: ansible.posix\n"
|
|
f" version: '1.0.0'\n"
|
|
f" type: url\n"
|
|
f" source: '{self._GITEA_URL}'\n"
|
|
)
|
|
mock_resp = MagicMock()
|
|
mock_resp.read.return_value = b"fake-tarball"
|
|
mock_resp.__enter__ = lambda _: mock_resp
|
|
mock_resp.__exit__ = lambda *a: None
|
|
mock_urlopen.return_value = mock_resp
|
|
|
|
result = _try_gitea_mirror_install("ansible-galaxy", req)
|
|
assert result is True
|
|
|
|
call_args = mock_urlopen.call_args[0][0]
|
|
assert call_args.get_header("Authorization") == "token tok456"
|
|
|
|
@patch("devx.tools.setup._run")
|
|
@patch("urllib.request.urlopen")
|
|
@patch.dict(os.environ, {"DEVELOPER_GITEA_API_TOKEN": "tok789"}, clear=True)
|
|
def test_developer_token_fallback(self, mock_urlopen: MagicMock, mock_run: MagicMock, tmp_path: Path) -> None:
|
|
"""DEVELOPER_GITEA_API_TOKEN is used when CI tokens are absent."""
|
|
req = tmp_path / "requirements.yml"
|
|
req.write_text(
|
|
f"collections:\n"
|
|
f" - name: ansible.posix\n"
|
|
f" version: '1.0.0'\n"
|
|
f" type: url\n"
|
|
f" source: '{self._GITEA_URL}'\n"
|
|
)
|
|
mock_resp = MagicMock()
|
|
mock_resp.read.return_value = b"fake-tarball"
|
|
mock_resp.__enter__ = lambda _: mock_resp
|
|
mock_resp.__exit__ = lambda *a: None
|
|
mock_urlopen.return_value = mock_resp
|
|
|
|
result = _try_gitea_mirror_install("ansible-galaxy", req)
|
|
assert result is True
|
|
|
|
call_args = mock_urlopen.call_args[0][0]
|
|
assert call_args.get_header("Authorization") == "token tok789"
|
|
|
|
@patch("devx.tools.setup._run")
|
|
@patch("urllib.request.urlopen")
|
|
@patch.dict(os.environ, {"CI_GITEA_TOKEN": "tok123"}, clear=True)
|
|
def test_non_gitea_url_passed_through(self, mock_urlopen: MagicMock, mock_run: MagicMock, tmp_path: Path) -> None:
|
|
"""URL entries not pointing to /api/packages/ are kept as-is (no download)."""
|
|
external_url = "https://galaxy.ansible.com/download/ansible-posix-1.0.0.tar.gz"
|
|
req = tmp_path / "requirements.yml"
|
|
req.write_text(
|
|
f"collections:\n"
|
|
f" - name: ansible.posix\n"
|
|
f" version: '1.0.0'\n"
|
|
f" type: url\n"
|
|
f" source: '{external_url}'\n"
|
|
)
|
|
# Should not call urlopen since the URL is not a Gitea package URL
|
|
result = _try_gitea_mirror_install("ansible-galaxy", req)
|
|
assert result is True
|
|
mock_urlopen.assert_not_called()
|
|
|
|
@patch("devx.tools.setup._run")
|
|
@patch("urllib.request.urlopen")
|
|
@patch.dict(os.environ, {"CI_GITEA_TOKEN": "tok123"}, clear=True)
|
|
def test_mixed_entries_gitea_and_non_gitea(
|
|
self, mock_urlopen: MagicMock, mock_run: MagicMock, tmp_path: Path
|
|
) -> None:
|
|
"""Mix of Gitea URL entries and regular galaxy entries."""
|
|
req = tmp_path / "requirements.yml"
|
|
req.write_text(
|
|
f"collections:\n"
|
|
f" - name: ansible.posix\n"
|
|
f" version: '1.0.0'\n"
|
|
f" type: url\n"
|
|
f" source: '{self._GITEA_URL}'\n"
|
|
f" - name: community.general\n"
|
|
f" version: '13.0.0'\n"
|
|
)
|
|
mock_resp = MagicMock()
|
|
mock_resp.read.return_value = b"fake-tarball"
|
|
mock_resp.__enter__ = lambda _: mock_resp
|
|
mock_resp.__exit__ = lambda *a: None
|
|
mock_urlopen.return_value = mock_resp
|
|
|
|
result = _try_gitea_mirror_install("ansible-galaxy", req)
|
|
assert result is True
|
|
# Only the Gitea URL entry should trigger a download
|
|
mock_urlopen.assert_called_once()
|
|
|
|
|
|
class TestConfigureTeaLogin:
|
|
@patch("devx.tools.setup.shutil.which", return_value=None)
|
|
def test_tea_not_installed(self, mock_which: MagicMock) -> None:
|
|
_configure_tea_login()
|
|
mock_which.assert_called_once_with("tea")
|
|
|
|
@patch("devx.tools.setup.shutil.which", return_value="/usr/local/bin/tea")
|
|
@patch.dict("os.environ", {}, clear=True)
|
|
def test_no_token(self, mock_which: MagicMock) -> None:
|
|
_configure_tea_login()
|
|
|
|
@patch("devx.tools.setup.subprocess.run")
|
|
@patch("devx.tools.setup.shutil.which", return_value="/usr/local/bin/tea")
|
|
@patch.dict("os.environ", {"CI_GITEA_TOKEN": "tok123"}, clear=True)
|
|
def test_login_already_exists(self, mock_which: MagicMock, mock_run: MagicMock) -> None:
|
|
mock_run.return_value = MagicMock(returncode=0, stdout="devx\ngrm\n", stderr="")
|
|
_configure_tea_login()
|
|
# Should only call login list, not login add
|
|
assert mock_run.call_count == 1
|
|
assert "list" in mock_run.call_args[0][0]
|
|
|
|
@patch("devx.tools.setup.subprocess.run")
|
|
@patch("devx.tools.setup.shutil.which", return_value="/usr/local/bin/tea")
|
|
@patch.dict("os.environ", {"CI_GITEA_TOKEN": "tok123"}, clear=True)
|
|
def test_login_add_success(self, mock_which: MagicMock, mock_run: MagicMock) -> None:
|
|
list_result = MagicMock(returncode=0, stdout="", stderr="")
|
|
add_result = MagicMock(returncode=0, stdout="", stderr="")
|
|
default_result = MagicMock(returncode=0, stdout="", stderr="")
|
|
mock_run.side_effect = [list_result, add_result, default_result]
|
|
_configure_tea_login()
|
|
assert mock_run.call_count == 3
|
|
assert "add" in mock_run.call_args_list[1][0][0]
|
|
assert "default" in mock_run.call_args_list[2][0][0]
|
|
|
|
@patch("devx.tools.setup.subprocess.run")
|
|
@patch("devx.tools.setup.shutil.which", return_value="/usr/local/bin/tea")
|
|
@patch.dict("os.environ", {"CI_GITEA_TOKEN": "tok123"}, clear=True)
|
|
def test_login_add_failure(self, mock_which: MagicMock, mock_run: MagicMock) -> None:
|
|
list_result = MagicMock(returncode=0, stdout="", stderr="")
|
|
add_result = MagicMock(returncode=1, stdout="", stderr="auth failed")
|
|
mock_run.side_effect = [list_result, add_result]
|
|
_configure_tea_login()
|
|
assert mock_run.call_count == 2
|
|
|
|
@patch("devx.tools.setup.subprocess.run")
|
|
@patch("devx.tools.setup.shutil.which", return_value="/usr/local/bin/tea")
|
|
@patch.dict(
|
|
"os.environ",
|
|
{"CI_GITEA_TOKEN": "tok123", "DEVX_GITEA_API_URL": "https://custom.example.com/api/v1"},
|
|
clear=True,
|
|
)
|
|
def test_custom_gitea_url(self, mock_which: MagicMock, mock_run: MagicMock) -> None:
|
|
list_result = MagicMock(returncode=0, stdout="", stderr="")
|
|
add_result = MagicMock(returncode=0, stdout="", stderr="")
|
|
default_result = MagicMock(returncode=0, stdout="", stderr="")
|
|
mock_run.side_effect = [list_result, add_result, default_result]
|
|
_configure_tea_login()
|
|
add_call = mock_run.call_args_list[1][0][0]
|
|
assert "https://custom.example.com" in add_call
|
|
|
|
|
|
class TestVerify:
|
|
@patch("devx.tools.setup.subprocess.run")
|
|
def test_verify_runs_tools(self, mock_run: MagicMock) -> None:
|
|
mock_run.return_value = MagicMock(returncode=0, stdout="devx 0.1.0", stderr="")
|
|
_verify(".venv/bin")
|
|
# Should try to run --version for devx and pre-commit
|
|
assert mock_run.call_count >= 1
|
|
|
|
@patch("devx.tools.setup.subprocess.run")
|
|
def test_verify_handles_missing_tools(self, mock_run: MagicMock) -> None:
|
|
mock_run.side_effect = FileNotFoundError("not found")
|
|
_verify(".venv/bin") # Should not raise
|
|
|
|
@patch("devx.tools.setup.subprocess.run")
|
|
def test_verify_handles_timeout(self, mock_run: MagicMock) -> None:
|
|
mock_run.side_effect = subprocess.TimeoutExpired(cmd="devx", timeout=10)
|
|
_verify(".venv/bin") # Should not raise
|
|
|
|
@patch("devx.tools.setup.subprocess.run")
|
|
def test_verify_handles_nonzero_returncode(self, mock_run: MagicMock) -> None:
|
|
"""When a tool returns non-zero, it is skipped without raising."""
|
|
mock_run.return_value = MagicMock(returncode=1, stdout="", stderr="error")
|
|
_verify(".venv/bin") # Should not raise
|
|
|
|
|
|
class TestMain:
|
|
@patch("devx.tools.setup._configure_tea_login")
|
|
@patch("devx.tools.setup._verify")
|
|
@patch("devx.tools.setup._install_pre_commit_hooks")
|
|
@patch("devx.tools.setup._install_ansible_collections")
|
|
@patch("devx.tools.setup._install_python_deps")
|
|
def test_main_success(
|
|
self,
|
|
mock_install_deps: MagicMock,
|
|
mock_install_ansible: MagicMock,
|
|
mock_install_hooks: MagicMock,
|
|
mock_verify: MagicMock,
|
|
mock_tea: MagicMock,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
bin_dir = tmp_path / "bin"
|
|
bin_dir.mkdir()
|
|
runner = CliRunner()
|
|
result = runner.invoke(main, ["--bin", str(bin_dir)])
|
|
assert result.exit_code == 0
|
|
mock_install_deps.assert_called_once()
|
|
mock_install_ansible.assert_called_once()
|
|
mock_install_hooks.assert_called_once()
|
|
mock_verify.assert_called_once()
|
|
mock_tea.assert_called_once()
|
|
|
|
@patch("devx.tools.setup._configure_tea_login")
|
|
@patch("devx.tools.setup._verify")
|
|
@patch("devx.tools.setup._install_pre_commit_hooks")
|
|
@patch("devx.tools.setup._install_ansible_collections")
|
|
@patch("devx.tools.setup._install_python_deps")
|
|
def test_main_no_pre_commit(
|
|
self,
|
|
mock_install_deps: MagicMock,
|
|
mock_install_ansible: MagicMock,
|
|
mock_install_hooks: MagicMock,
|
|
mock_verify: MagicMock,
|
|
mock_tea: MagicMock,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
bin_dir = tmp_path / "bin"
|
|
bin_dir.mkdir()
|
|
runner = CliRunner()
|
|
result = runner.invoke(main, ["--bin", str(bin_dir), "--no-pre-commit"])
|
|
assert result.exit_code == 0
|
|
mock_install_deps.assert_called_once()
|
|
mock_install_hooks.assert_not_called()
|
|
|
|
@patch("devx.tools.setup._configure_tea_login")
|
|
@patch("devx.tools.setup._verify")
|
|
@patch("devx.tools.setup._install_pre_commit_hooks")
|
|
@patch("devx.tools.setup._install_ansible_collections")
|
|
@patch("devx.tools.setup._install_python_deps")
|
|
def test_main_no_ansible_collections(
|
|
self,
|
|
mock_install_deps: MagicMock,
|
|
mock_install_ansible: MagicMock,
|
|
mock_install_hooks: MagicMock,
|
|
mock_verify: MagicMock,
|
|
mock_tea: MagicMock,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
bin_dir = tmp_path / "bin"
|
|
bin_dir.mkdir()
|
|
runner = CliRunner()
|
|
result = runner.invoke(main, ["--bin", str(bin_dir), "--no-ansible-collections"])
|
|
assert result.exit_code == 0
|
|
mock_install_ansible.assert_not_called()
|
|
|
|
@patch("devx.tools.setup._configure_tea_login")
|
|
@patch("devx.tools.setup._verify")
|
|
@patch("devx.tools.setup._install_pre_commit_hooks")
|
|
@patch("devx.tools.setup._install_ansible_collections")
|
|
@patch("devx.tools.setup._install_python_deps")
|
|
def test_main_custom_extras(
|
|
self,
|
|
mock_install_deps: MagicMock,
|
|
mock_install_ansible: MagicMock,
|
|
mock_install_hooks: MagicMock,
|
|
mock_verify: MagicMock,
|
|
mock_tea: MagicMock,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
bin_dir = tmp_path / "bin"
|
|
bin_dir.mkdir()
|
|
runner = CliRunner()
|
|
result = runner.invoke(main, ["--bin", str(bin_dir), "--extras", "ci"])
|
|
assert result.exit_code == 0
|
|
mock_install_deps.assert_called_once_with(str(bin_dir), "ci")
|
|
|
|
@patch("devx.tools.setup._configure_tea_login")
|
|
@patch("devx.tools.setup._verify")
|
|
@patch("devx.tools.setup._install_pre_commit_hooks")
|
|
@patch("devx.tools.setup._install_ansible_collections")
|
|
@patch("devx.tools.setup._install_python_deps")
|
|
def test_main_no_tea_login(
|
|
self,
|
|
mock_install_deps: MagicMock,
|
|
mock_install_ansible: MagicMock,
|
|
mock_install_hooks: MagicMock,
|
|
mock_verify: MagicMock,
|
|
mock_tea: MagicMock,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
bin_dir = tmp_path / "bin"
|
|
bin_dir.mkdir()
|
|
runner = CliRunner()
|
|
result = runner.invoke(main, ["--bin", str(bin_dir), "--no-tea-login"])
|
|
assert result.exit_code == 0
|
|
mock_tea.assert_not_called()
|
|
|
|
@patch("devx.tools.setup._run")
|
|
@patch("devx.tools.setup._configure_tea_login")
|
|
@patch("devx.tools.setup._install_python_deps")
|
|
@patch("devx.tools.setup._verify")
|
|
def test_main_missing_bin_dir(
|
|
self, mock_tea: MagicMock, mock_deps: MagicMock, mock_verify: MagicMock, mock_run: MagicMock
|
|
) -> None:
|
|
runner = CliRunner()
|
|
result = runner.invoke(main, ["--bin", "/nonexistent/path"])
|
|
assert result.exit_code != 0
|
|
assert "Bin directory not found" in result.output
|
|
|
|
@patch("devx.tools.setup._verify")
|
|
@patch("devx.tools.setup._configure_tea_login")
|
|
@patch("devx.tools.setup._install_pre_commit_hooks")
|
|
@patch("devx.tools.setup._install_ansible_collections")
|
|
@patch("devx.tools.setup._install_python_deps")
|
|
def test_main_skip_install(
|
|
self,
|
|
mock_install_deps: MagicMock,
|
|
mock_install_ansible: MagicMock,
|
|
mock_install_hooks: MagicMock,
|
|
mock_verify: MagicMock,
|
|
mock_tea: MagicMock,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
bin_dir = tmp_path / "bin"
|
|
bin_dir.mkdir()
|
|
runner = CliRunner()
|
|
result = runner.invoke(main, ["--bin", str(bin_dir), "--skip-install"])
|
|
assert result.exit_code == 0
|
|
mock_install_deps.assert_not_called()
|
|
assert "Skipping pip install" in result.output
|
|
|
|
|
|
def test_main_module_block(tmp_path: Path) -> None:
|
|
"""Test the __main__ block execution."""
|
|
bin_dir = tmp_path / "bin"
|
|
bin_dir.mkdir()
|
|
with patch.dict("os.environ", {}, clear=True):
|
|
with patch("devx.tools.setup._install_python_deps") as mock_deps:
|
|
with patch("devx.tools.setup._install_pre_commit_hooks"):
|
|
with patch("devx.tools.setup._install_ansible_collections"):
|
|
with patch("devx.tools.setup._configure_tea_login"):
|
|
with patch("devx.tools.setup._verify"):
|
|
runner = CliRunner()
|
|
result = runner.invoke(main, ["--bin", str(bin_dir)])
|
|
assert result.exit_code == 0
|
|
mock_deps.assert_called_once()
|