229 lines
8.5 KiB
Python
229 lines
8.5 KiB
Python
"""Tests for myagents CLI."""
|
|
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from click.testing import CliRunner
|
|
|
|
from myagents.cli import cli
|
|
|
|
|
|
class TestMyagentsHelp:
|
|
"""Tests for top-level myagents command."""
|
|
|
|
def test_help_shows_agent_subcommands(self) -> None:
|
|
"""--help should list claude and kimi subcommands."""
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli, ["--help"])
|
|
assert result.exit_code == 0
|
|
assert "claude" in result.output
|
|
assert "kimi" in result.output
|
|
assert "update" in result.output
|
|
assert "upgrade" in result.output
|
|
|
|
def test_version_shows_version(self) -> None:
|
|
"""--version should show package version."""
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli, ["--version"])
|
|
assert result.exit_code == 0
|
|
assert "version" in result.output.lower()
|
|
|
|
def test_bare_invocation_shows_help(self) -> None:
|
|
"""Running myagents without subcommands should show help, not launch agent."""
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli, [])
|
|
assert result.exit_code == 0
|
|
assert "Usage:" in result.output
|
|
|
|
|
|
class TestClaudeSubcommand:
|
|
"""Tests for ``myagents claude``."""
|
|
|
|
def test_help_shows_options(self) -> None:
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli, ["claude", "--help"])
|
|
assert result.exit_code == 0
|
|
assert "--cwd" in result.output
|
|
assert "--list" in result.output
|
|
assert "--dangerously-skip-permissions" not in result.output
|
|
|
|
def test_runs_claude(self) -> None:
|
|
runner = CliRunner()
|
|
with (
|
|
patch("myagents.launcher.shutil.which", return_value="/usr/bin/claude"),
|
|
patch("myagents.launcher.subprocess.run") as mock_run,
|
|
):
|
|
mock_run.return_value = MagicMock(returncode=0)
|
|
result = runner.invoke(cli, ["claude"])
|
|
assert result.exit_code == 0
|
|
mock_run.assert_called_once()
|
|
assert mock_run.call_args[0][0] == [
|
|
"/usr/bin/claude",
|
|
"--dangerously-skip-permissions",
|
|
]
|
|
|
|
def test_missing_binary_error(self) -> None:
|
|
runner = CliRunner()
|
|
with patch("myagents.launcher.shutil.which", return_value=None):
|
|
result = runner.invoke(cli, ["claude"])
|
|
assert result.exit_code == 127
|
|
assert "not found" in result.output.lower()
|
|
|
|
def test_cwd_option_passed(self, tmp_path: Path) -> None:
|
|
runner = CliRunner()
|
|
test_dir = tmp_path / "test_cwd"
|
|
test_dir.mkdir()
|
|
|
|
with (
|
|
patch("myagents.launcher.shutil.which", return_value="/usr/bin/claude"),
|
|
patch("myagents.launcher.subprocess.run") as mock_run,
|
|
):
|
|
mock_run.return_value = MagicMock(returncode=0)
|
|
result = runner.invoke(cli, ["claude", "--cwd", str(test_dir)])
|
|
assert result.exit_code == 0
|
|
assert mock_run.call_args.kwargs.get("cwd") == str(test_dir.resolve())
|
|
|
|
def test_cwd_invalid_directory(self, tmp_path: Path) -> None:
|
|
runner = CliRunner()
|
|
bad_dir = tmp_path / "does_not_exist"
|
|
result = runner.invoke(cli, ["claude", "--cwd", str(bad_dir)])
|
|
assert result.exit_code == 1
|
|
assert "not a directory" in result.output.lower()
|
|
|
|
|
|
class TestClaudePassthrough:
|
|
"""Unknown leading flags forward to claude instead of erroring."""
|
|
|
|
def _invoke(self, args: list[str], tmp_path: Path) -> list[str]:
|
|
runner = CliRunner()
|
|
with (
|
|
patch("myagents.launcher.shutil.which", return_value="/usr/bin/claude"),
|
|
patch("myagents.launcher.subprocess.run") as mock_run,
|
|
):
|
|
mock_run.return_value = MagicMock(returncode=0)
|
|
result = runner.invoke(
|
|
cli, ["claude", "--cwd", str(tmp_path), *args]
|
|
)
|
|
assert result.exit_code == 0, result.output
|
|
mock_run.assert_called_once()
|
|
return mock_run.call_args[0][0]
|
|
|
|
def test_resume_flag_passes_through(self, tmp_path: Path) -> None:
|
|
cmd = self._invoke(["--resume"], tmp_path)
|
|
assert cmd == [
|
|
"/usr/bin/claude",
|
|
"--dangerously-skip-permissions",
|
|
"--resume",
|
|
]
|
|
|
|
def test_resume_with_session_id(self, tmp_path: Path) -> None:
|
|
cmd = self._invoke(["-r", "abc123"], tmp_path)
|
|
assert cmd[-2:] == ["-r", "abc123"]
|
|
|
|
def test_continue_flag_passes_through(self, tmp_path: Path) -> None:
|
|
cmd = self._invoke(["--continue"], tmp_path)
|
|
assert cmd[-1] == "--continue"
|
|
|
|
|
|
class TestClaudeListSessions:
|
|
"""``myagents claude --list`` reports resumable sessions."""
|
|
|
|
def test_list_empty_directory(self, tmp_path: Path) -> None:
|
|
runner = CliRunner()
|
|
with patch("myagents.launcher.subprocess.run") as mock_run:
|
|
result = runner.invoke(cli, ["claude", "--cwd", str(tmp_path), "--list"])
|
|
assert result.exit_code == 0
|
|
assert "no sessions" in result.output.lower()
|
|
mock_run.assert_not_called()
|
|
|
|
|
|
class TestKimiSubcommand:
|
|
"""Tests for ``myagents kimi``."""
|
|
|
|
def test_help_shows_options(self) -> None:
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli, ["kimi", "--help"])
|
|
assert result.exit_code == 0
|
|
assert "--cwd" in result.output
|
|
assert "--list" in result.output
|
|
assert "--dangerously-skip-permissions" not in result.output
|
|
|
|
def test_runs_kimi(self) -> None:
|
|
runner = CliRunner()
|
|
with (
|
|
patch("myagents.launcher.shutil.which", return_value="/usr/bin/kimi"),
|
|
patch("myagents.launcher.subprocess.run") as mock_run,
|
|
):
|
|
mock_run.return_value = MagicMock(returncode=0)
|
|
result = runner.invoke(cli, ["kimi"])
|
|
assert result.exit_code == 0
|
|
mock_run.assert_called_once()
|
|
assert mock_run.call_args[0][0] == ["/usr/bin/kimi"]
|
|
|
|
def test_missing_binary_error(self) -> None:
|
|
runner = CliRunner()
|
|
with patch("myagents.launcher.shutil.which", return_value=None):
|
|
result = runner.invoke(cli, ["kimi"])
|
|
assert result.exit_code == 127
|
|
assert "not found" in result.output.lower()
|
|
|
|
def test_cwd_option_passed(self, tmp_path: Path) -> None:
|
|
runner = CliRunner()
|
|
test_dir = tmp_path / "test_cwd"
|
|
test_dir.mkdir()
|
|
|
|
with (
|
|
patch("myagents.launcher.shutil.which", return_value="/usr/bin/kimi"),
|
|
patch("myagents.launcher.subprocess.run") as mock_run,
|
|
):
|
|
mock_run.return_value = MagicMock(returncode=0)
|
|
result = runner.invoke(cli, ["kimi", "--cwd", str(test_dir)])
|
|
assert result.exit_code == 0
|
|
assert mock_run.call_args.kwargs.get("cwd") == str(test_dir.resolve())
|
|
|
|
|
|
class TestKimiPassthrough:
|
|
"""Unknown leading flags forward to kimi instead of erroring."""
|
|
|
|
def _invoke(self, args: list[str], tmp_path: Path) -> list[str]:
|
|
runner = CliRunner()
|
|
with (
|
|
patch("myagents.launcher.shutil.which", return_value="/usr/bin/kimi"),
|
|
patch("myagents.launcher.subprocess.run") as mock_run,
|
|
):
|
|
mock_run.return_value = MagicMock(returncode=0)
|
|
result = runner.invoke(cli, ["kimi", "--cwd", str(tmp_path), *args])
|
|
assert result.exit_code == 0, result.output
|
|
mock_run.assert_called_once()
|
|
return mock_run.call_args[0][0]
|
|
|
|
def test_resume_flag_passes_through(self, tmp_path: Path) -> None:
|
|
cmd = self._invoke(["--resume"], tmp_path)
|
|
assert cmd == ["/usr/bin/kimi", "--resume"]
|
|
|
|
def test_resume_with_session_id(self, tmp_path: Path) -> None:
|
|
cmd = self._invoke(["-r", "abc123"], tmp_path)
|
|
assert cmd[-2:] == ["-r", "abc123"]
|
|
|
|
def test_continue_flag_passes_through(self, tmp_path: Path) -> None:
|
|
cmd = self._invoke(["--continue"], tmp_path)
|
|
assert cmd[-1] == "--continue"
|
|
|
|
|
|
class TestUpdateSubcommand:
|
|
"""Tests for update/upgrade subcommands registration."""
|
|
|
|
def test_update_subcommand_exists(self) -> None:
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli, ["update", "--help"])
|
|
assert result.exit_code == 0
|
|
assert (
|
|
"update" in result.output.lower()
|
|
or "reinstall" in result.output.lower()
|
|
)
|
|
|
|
def test_upgrade_alias_exists(self) -> None:
|
|
runner = CliRunner()
|
|
result = runner.invoke(cli, ["upgrade", "--help"])
|
|
assert result.exit_code == 0
|