"""Tests for myclaude.cli.""" from pathlib import Path from unittest.mock import MagicMock, patch from click.testing import CliRunner from myclaude.cli import cli class TestCliHelp: """Tests for CLI help and basic invocation.""" def test_help_shows_options(self) -> None: """--help should show cwd option only.""" runner = CliRunner() result = runner.invoke(cli, ["--help"]) assert result.exit_code == 0 assert "--cwd" in result.output assert "--dangerously-skip-permissions" not 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_update_subcommand_exists(self) -> None: """update subcommand should be registered.""" 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: """upgrade should be an alias for update.""" runner = CliRunner() result = runner.invoke(cli, ["upgrade", "--help"]) assert result.exit_code == 0 class TestCliDefaultBehavior: """Tests for default chat behavior.""" def test_no_subcommand_runs_claude(self) -> None: """Running without subcommand should invoke claude binary with skip-permissions.""" runner = CliRunner() with ( patch("myclaude.cli.shutil.which", return_value="/usr/bin/claude"), patch("myclaude.cli.subprocess.run") as mock_run, ): mock_run.return_value = MagicMock(returncode=0) result = runner.invoke(cli, []) assert result.exit_code == 0 mock_run.assert_called_once() call_args = mock_run.call_args assert call_args[0][0] == [ "/usr/bin/claude", "--dangerously-skip-permissions", ] def test_missing_claude_binary_error(self) -> None: """Should exit with error when claude binary not found.""" runner = CliRunner() with patch("myclaude.cli.shutil.which", return_value=None): result = runner.invoke(cli, []) assert result.exit_code == 127 assert "not found" in result.output.lower() def test_cwd_option_passed(self, tmp_path: Path) -> None: """--cwd should be passed as working directory.""" runner = CliRunner() test_dir = tmp_path / "test_cwd" test_dir.mkdir() with ( patch("myclaude.cli.shutil.which", return_value="/usr/bin/claude"), patch("myclaude.cli.subprocess.run") as mock_run, ): mock_run.return_value = MagicMock(returncode=0) result = runner.invoke(cli, ["--cwd", str(test_dir)]) assert result.exit_code == 0 call_kwargs = mock_run.call_args.kwargs assert call_kwargs.get("cwd") == str(test_dir.resolve()) def test_cwd_invalid_directory(self, tmp_path: Path) -> None: """--cwd pointing to non-existent directory should error.""" runner = CliRunner() bad_dir = tmp_path / "does_not_exist" result = runner.invoke(cli, ["--cwd", str(bad_dir)]) assert result.exit_code == 1 assert "not a directory" in result.output.lower() class TestCliPassthrough: """Unknown leading flags forward to claude instead of erroring.""" def _invoke(self, args: list[str], tmp_path: Path) -> list[str]: """Run cli with mocked claude and return the command claude was called with.""" runner = CliRunner() with ( patch("myclaude.cli.shutil.which", return_value="/usr/bin/claude"), patch("myclaude.cli.subprocess.run") as mock_run, ): mock_run.return_value = MagicMock(returncode=0) result = runner.invoke(cli, ["--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: """`--resume` must reach claude, not be parsed as a subcommand.""" 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: """`-r ` forwards both the flag and its value.""" 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 TestCliListSessions: """`--list` reports resumable sessions without launching claude.""" def test_list_empty_directory(self, tmp_path: Path) -> None: """A directory with no recorded sessions reports none and exits 0.""" runner = CliRunner() with patch("myclaude.cli.subprocess.run") as mock_run: result = runner.invoke(cli, ["--cwd", str(tmp_path), "--list"]) assert result.exit_code == 0 assert "no sessions" in result.output.lower() mock_run.assert_not_called()