"""Unit tests for devx.tools.check_deps.""" from pathlib import Path from unittest.mock import MagicMock, patch from click.testing import CliRunner from devx.tools.check_deps import ( _check_python_version, _check_tool, cli, ) class TestCheckTool: @patch("devx.tools.check_deps.shutil.which", return_value="/usr/bin/tofu") def test_found(self, mock_which: MagicMock) -> None: assert _check_tool("tofu") is True @patch("devx.tools.check_deps.shutil.which", return_value=None) def test_not_found_required(self, mock_which: MagicMock) -> None: assert _check_tool("tofu") is False @patch("devx.tools.check_deps.shutil.which", return_value=None) def test_not_found_optional(self, mock_which: MagicMock) -> None: assert _check_tool("checkmake", optional=True) is False class TestCheckPythonVersion: @patch("devx.tools.check_deps.subprocess.run") def test_valid_version(self, mock_run: MagicMock, tmp_path: Path) -> None: venv_bin = tmp_path / "bin" venv_bin.mkdir() (venv_bin / "python").touch() mock_run.return_value = MagicMock(returncode=0, stdout="Python 3.12.3\n", stderr="") _check_python_version(venv_bin) @patch("devx.tools.check_deps.subprocess.run") def test_old_version(self, mock_run: MagicMock, tmp_path: Path) -> None: venv_bin = tmp_path / "bin" venv_bin.mkdir() (venv_bin / "python").touch() mock_run.return_value = MagicMock(returncode=0, stdout="Python 3.11.0\n", stderr="") _check_python_version(venv_bin) def test_no_venv(self, tmp_path: Path) -> None: venv_bin = tmp_path / "bin" _check_python_version(venv_bin) @patch("devx.tools.check_deps.subprocess.run") def test_command_fails(self, mock_run: MagicMock, tmp_path: Path) -> None: venv_bin = tmp_path / "bin" venv_bin.mkdir() (venv_bin / "python").touch() mock_run.return_value = MagicMock(returncode=1, stdout="", stderr="error") _check_python_version(venv_bin) @patch("devx.tools.check_deps.subprocess.run") def test_unparseable_version(self, mock_run: MagicMock, tmp_path: Path) -> None: venv_bin = tmp_path / "bin" venv_bin.mkdir() (venv_bin / "python").touch() mock_run.return_value = MagicMock(returncode=0, stdout="garbage\n", stderr="") _check_python_version(venv_bin) class TestCli: @patch("devx.tools.check_deps._check_python_version") @patch("devx.tools.check_deps._check_tool") def test_all_present(self, mock_check: MagicMock, mock_py: MagicMock) -> None: mock_check.return_value = True runner = CliRunner() result = runner.invoke(cli, []) assert result.exit_code == 0 assert "All core tools present" in result.output @patch("devx.tools.check_deps._check_python_version") @patch("devx.tools.check_deps._check_tool") def test_missing_required(self, mock_check: MagicMock, mock_py: MagicMock) -> None: mock_check.side_effect = lambda name, optional=False: name != "tofu" runner = CliRunner() result = runner.invoke(cli, []) assert result.exit_code != 0 @patch("devx.tools.check_deps._check_python_version") @patch("devx.tools.check_deps._check_tool") def test_missing_optional_with_fallback(self, mock_check: MagicMock, mock_py: MagicMock, tmp_path: Path) -> None: checkmake_bin = tmp_path / "checkmake" checkmake_bin.touch() def _side(name: str, optional: bool = False) -> bool: return name != "checkmake" mock_check.side_effect = _side runner = CliRunner() result = runner.invoke(cli, ["--checkmake-bin", str(checkmake_bin)]) assert result.exit_code == 0 @patch("devx.tools.check_deps._check_python_version") @patch("devx.tools.check_deps._check_tool") def test_missing_optional_no_fallback(self, mock_check: MagicMock, mock_py: MagicMock) -> None: def _side(name: str, optional: bool = False) -> bool: return name != "checkmake" mock_check.side_effect = _side runner = CliRunner() result = runner.invoke(cli, []) assert result.exit_code == 0