Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/revenueholdings/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,11 @@ def show_versions(
tool: str | None = typer.Argument(None, help="Check version of a specific tool."),
):
"""Show installed tool versions."""
targets = ([tool] if tool in TOOLS else []) if tool else _builtins.list(TOOLS.keys())
if tool is not None and tool not in TOOLS:
console.print(f"[red]Unknown tool: {tool}[/red]")
console.print(f"Available: {', '.join(TOOLS.keys())}")
raise typer.Exit(code=1)
targets = [tool] if tool else _builtins.list(TOOLS.keys())

for t in targets:
info = TOOLS[t]
Expand Down
64 changes: 63 additions & 1 deletion tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from revenueholdings import TOOLS, __version__
from revenueholdings.cli import app
from typer.testing import CliRunner
from unittest import mock

runner = CliRunner()

Expand Down Expand Up @@ -35,12 +36,73 @@ def test_unknown_tool(self):
assert "Unknown" in result.stdout


class TestInstallCommand:
@mock.patch("revenueholdings.cli.subprocess.run")
def test_install_specific_tool(self, mock_run):
"""Install a specific tool by name."""
mock_run.return_value = mock.MagicMock(returncode=0, stdout="", stderr="")
result = runner.invoke(app, ["install", "guard"])
assert result.exit_code == 0
assert "Successfully" in result.stdout
mock_run.assert_called_once()

@mock.patch("revenueholdings.cli.subprocess.run")
def test_install_all(self, mock_run):
"""Install all tools via the 'all' alias."""
mock_run.return_value = mock.MagicMock(returncode=0, stdout="", stderr="")
result = runner.invoke(app, ["install", "all"])
assert result.exit_code == 0
assert "Successfully" in result.stdout
mock_run.assert_called_once()

def test_install_unknown_tool(self):
"""Error on unknown tool name."""
result = runner.invoke(app, ["install", "nonexistent"])
assert result.exit_code == 1
assert "Unknown" in result.stdout
assert "Available:" in result.stdout

@mock.patch("revenueholdings.cli.subprocess.run")
def test_install_failure(self, mock_run):
"""Handle pip install failure gracefully."""
mock_run.return_value = mock.MagicMock(returncode=1, stdout="", stderr="Error message")
result = runner.invoke(app, ["install", "guard"])
assert result.exit_code == 1
assert "failed" in result.stdout.lower()


class TestVersionsCommand:
def test_versions_runs(self):
"""List all tool versions without error."""
result = runner.invoke(app, ["versions"])
# Should succeed even if tools aren't installed
assert result.exit_code == 0

def test_versions_unknown_tool_fails(self):
"""Error on unknown tool name."""
result = runner.invoke(app, ["versions", "nonexistent"])
assert result.exit_code == 1
assert "Unknown" in result.stdout

@mock.patch("revenueholdings.cli.subprocess.run")
def test_versions_specific_tool_not_installed(self, mock_run):
"""Show 'not installed' for a tool that isn't installed."""
mock_run.return_value = mock.MagicMock(
returncode=1, stdout="", stderr=""
)
result = runner.invoke(app, ["versions", "guard"])
assert result.exit_code == 0
assert "guard" in result.stdout
assert "not installed" in result.stdout


class TestDispatchCommands:
def test_invalid_tool_subcommand(self):
"""Reject dispatch to an unknown tool subcommand."""
result = runner.invoke(app, ["nonexistent"])
# typer outputs error to stderr, not stdout
assert result.exit_code != 0
assert "No such command" in result.stdout or "No such command" in result.stderr


class TestHelp:
def test_help(self):
Expand Down