#!/usr/bin/env python3 """ Test suite for the base command system functionality. Tests the Command class, command registry, and base command handling. """ import os import sys import pytest from unittest.mock import patch, Mock, MagicMock # Add src to path sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "src")) from cai.repl.commands.base import ( Command, COMMANDS, COMMAND_ALIASES, register_command, get_command, handle_command, ) class TestCommand: """Test cases for the base Command class.""" @pytest.fixture(autouse=True) def setup_and_cleanup(self): """Setup and cleanup for each test.""" # Store original command registry self.original_commands = COMMANDS.copy() self.original_aliases = COMMAND_ALIASES.copy() yield # Restore original command registry COMMANDS.clear() COMMANDS.update(self.original_commands) COMMAND_ALIASES.clear() COMMAND_ALIASES.update(self.original_aliases) @pytest.fixture def sample_command(self): """Create a sample command for testing.""" return Command( name="/test", description="Test command for unit testing", aliases=["/t", "/test-cmd"] ) def test_command_initialization(self, sample_command): """Test that Command initializes correctly.""" assert sample_command.name == "/test" assert sample_command.description == "Test command for unit testing" assert sample_command.aliases == ["/t", "/test-cmd"] assert isinstance(sample_command.subcommands, dict) assert len(sample_command.subcommands) == 0 def test_command_initialization_without_aliases(self): """Test Command initialization without aliases.""" cmd = Command("/test", "Test command") assert cmd.name == "/test" assert cmd.description == "Test command" assert cmd.aliases == [] def test_add_subcommand(self, sample_command): """Test adding a subcommand to a command.""" def test_handler(args): return True sample_command.add_subcommand("test_sub", "Test subcommand", test_handler) assert "test_sub" in sample_command.subcommands assert sample_command.subcommands["test_sub"]["description"] == "Test subcommand" assert sample_command.subcommands["test_sub"]["handler"] == test_handler def test_get_subcommands(self, sample_command): """Test getting list of subcommand names.""" def handler1(args): return True def handler2(args): return True sample_command.add_subcommand("sub1", "Description 1", handler1) sample_command.add_subcommand("sub2", "Description 2", handler2) subcommands = sample_command.get_subcommands() assert set(subcommands) == {"sub1", "sub2"} def test_get_subcommand_description(self, sample_command): """Test getting subcommand description.""" def test_handler(args): return True sample_command.add_subcommand("test_sub", "Test subcommand description", test_handler) description = sample_command.get_subcommand_description("test_sub") assert description == "Test subcommand description" # Test unknown subcommand unknown_description = sample_command.get_subcommand_description("unknown") assert unknown_description == "" def test_handle_with_subcommand(self, sample_command): """Test handling a command with a valid subcommand.""" def test_handler(args): return True sample_command.add_subcommand("test_sub", "Test subcommand", test_handler) result = sample_command.handle(["test_sub"]) assert result is True def test_handle_with_subcommand_and_args(self, sample_command): """Test handling a command with subcommand and additional arguments.""" def test_handler(args): assert args == ["arg1", "arg2"] return True sample_command.add_subcommand("test_sub", "Test subcommand", test_handler) result = sample_command.handle(["test_sub", "arg1", "arg2"]) assert result is True def test_handle_no_args(self, sample_command): """Test handling command with no arguments.""" result = sample_command.handle([]) assert result is False # Default implementation returns False def test_handle_unknown_subcommand(self, sample_command): """Test handling command with unknown subcommand.""" result = sample_command.handle(["unknown_subcommand"]) assert result is False # Default implementation returns False def test_handle_no_args_default_behavior(self, sample_command): """Test the default handle_no_args behavior.""" result = sample_command.handle_no_args() assert result is False def test_handle_unknown_subcommand_default_behavior(self, sample_command): """Test the default handle_unknown_subcommand behavior.""" result = sample_command.handle_unknown_subcommand("unknown") assert result is False class TestCommandRegistry: """Test cases for the command registry system.""" @pytest.fixture(autouse=True) def setup_and_cleanup(self): """Setup and cleanup for each test.""" # Store original command registry self.original_commands = COMMANDS.copy() self.original_aliases = COMMAND_ALIASES.copy() # Clear registry for clean tests COMMANDS.clear() COMMAND_ALIASES.clear() yield # Restore original command registry COMMANDS.clear() COMMANDS.update(self.original_commands) COMMAND_ALIASES.clear() COMMAND_ALIASES.update(self.original_aliases) @pytest.fixture def test_commands(self): """Create test commands for registry testing.""" cmd1 = Command("/test1", "First test command", ["/t1"]) cmd2 = Command("/test2", "Second test command", ["/t2", "/test-two"]) cmd3 = Command("/test3", "Third test command") return [cmd1, cmd2, cmd3] def test_register_command(self, test_commands): """Test registering a command.""" cmd = test_commands[0] register_command(cmd) assert cmd.name in COMMANDS assert COMMANDS[cmd.name] == cmd assert "/t1" in COMMAND_ALIASES assert COMMAND_ALIASES["/t1"] == cmd.name def test_register_multiple_commands(self, test_commands): """Test registering multiple commands.""" for cmd in test_commands: register_command(cmd) # Check all commands are registered assert len(COMMANDS) == 3 for cmd in test_commands: assert cmd.name in COMMANDS assert COMMANDS[cmd.name] == cmd # Check aliases assert COMMAND_ALIASES["/t1"] == "/test1" assert COMMAND_ALIASES["/t2"] == "/test2" assert COMMAND_ALIASES["/test-two"] == "/test2" def test_register_command_with_duplicate_name(self, test_commands): """Test registering commands with duplicate names (should overwrite).""" cmd1 = test_commands[0] cmd2 = Command("/test1", "Different description") register_command(cmd1) register_command(cmd2) # Should overwrite cmd1 assert COMMANDS["/test1"] == cmd2 assert COMMANDS["/test1"].description == "Different description" def test_get_command_by_name(self, test_commands): """Test getting a command by its name.""" cmd = test_commands[0] register_command(cmd) retrieved_cmd = get_command("/test1") assert retrieved_cmd == cmd def test_get_command_by_alias(self, test_commands): """Test getting a command by its alias.""" cmd = test_commands[0] register_command(cmd) retrieved_cmd = get_command("/t1") assert retrieved_cmd == cmd def test_get_command_nonexistent(self): """Test getting a non-existent command.""" result = get_command("/nonexistent") assert result is None def test_handle_command_by_name(self, test_commands): """Test handling a command by its name.""" cmd = test_commands[0] cmd.handle = Mock(return_value=True) register_command(cmd) result = handle_command("/test1", ["arg1", "arg2"]) assert result is True cmd.handle.assert_called_once_with(["arg1", "arg2"]) def test_handle_command_by_alias(self, test_commands): """Test handling a command by its alias.""" cmd = test_commands[0] cmd.handle = Mock(return_value=True) register_command(cmd) result = handle_command("/t1", ["arg1"]) assert result is True cmd.handle.assert_called_once_with(["arg1"]) def test_handle_command_nonexistent(self): """Test handling a non-existent command.""" result = handle_command("/nonexistent", ["args"]) assert result is False def test_handle_command_no_args(self, test_commands): """Test handling a command with no arguments.""" cmd = test_commands[0] cmd.handle = Mock(return_value=True) register_command(cmd) result = handle_command("/test1") assert result is True cmd.handle.assert_called_once_with(None) class TestCustomCommand: """Test cases using custom command implementations.""" @pytest.fixture(autouse=True) def setup_and_cleanup(self): """Setup and cleanup for each test.""" # Store original command registry self.original_commands = COMMANDS.copy() self.original_aliases = COMMAND_ALIASES.copy() # Clear registry for clean tests COMMANDS.clear() COMMAND_ALIASES.clear() yield # Restore original command registry COMMANDS.clear() COMMANDS.update(self.original_commands) COMMAND_ALIASES.clear() COMMAND_ALIASES.update(self.original_aliases) def test_custom_command_with_overridden_methods(self): """Test a custom command with overridden handle methods.""" class CustomCommand(Command): def __init__(self): super().__init__("/custom", "Custom test command", ["/c"]) self.handle_no_args_called = False self.handle_unknown_subcommand_called = False def handle_no_args(self): self.handle_no_args_called = True return True def handle_unknown_subcommand(self, subcommand): self.handle_unknown_subcommand_called = True self.last_unknown_subcommand = subcommand return True cmd = CustomCommand() register_command(cmd) # Test handle_no_args result1 = handle_command("/custom") assert result1 is True assert cmd.handle_no_args_called is True # Test handle_unknown_subcommand result2 = handle_command("/custom", ["unknown"]) assert result2 is True assert cmd.handle_unknown_subcommand_called is True assert cmd.last_unknown_subcommand == "unknown" def test_custom_command_with_subcommands(self): """Test a custom command with predefined subcommands.""" class CustomCommandWithSubcommands(Command): def __init__(self): super().__init__("/multi", "Multi-subcommand test", ["/m"]) self.add_subcommand("start", "Start something", self.handle_start) self.add_subcommand("stop", "Stop something", self.handle_stop) self.add_subcommand("status", "Check status", self.handle_status) self.start_called = False self.stop_called = False self.status_called = False def handle_start(self, args): self.start_called = True self.start_args = args return True def handle_stop(self, args): self.stop_called = True self.stop_args = args return True def handle_status(self, args): self.status_called = True self.status_args = args return True cmd = CustomCommandWithSubcommands() register_command(cmd) # Test each subcommand result1 = handle_command("/multi", ["start", "param1"]) assert result1 is True assert cmd.start_called is True assert cmd.start_args == ["param1"] result2 = handle_command("/m", ["stop"]) # Test alias assert result2 is True assert cmd.stop_called is True assert cmd.stop_args is None # When no args after subcommand, None is passed result3 = handle_command("/multi", ["status", "verbose"]) assert result3 is True assert cmd.status_called is True assert cmd.status_args == ["verbose"] def test_command_error_handling(self): """Test command error handling when handlers raise exceptions.""" class ErrorCommand(Command): def __init__(self): super().__init__("/error", "Error test command") self.add_subcommand("crash", "Crash handler", self.handle_crash) def handle_crash(self, args): raise ValueError("Test error") cmd = ErrorCommand() register_command(cmd) # The command should propagate the exception with pytest.raises(ValueError, match="Test error"): handle_command("/error", ["crash"]) @pytest.mark.integration class TestCommandIntegration: """Integration tests for the command system.""" @pytest.fixture(autouse=True) def setup_integration(self): """Setup for integration tests.""" # Store original command registry self.original_commands = COMMANDS.copy() self.original_aliases = COMMAND_ALIASES.copy() # Clear registry for clean tests COMMANDS.clear() COMMAND_ALIASES.clear() yield # Restore original command registry COMMANDS.clear() COMMANDS.update(self.original_commands) COMMAND_ALIASES.clear() COMMAND_ALIASES.update(self.original_aliases) def test_complete_command_lifecycle(self): """Test the complete lifecycle of commands.""" # Create multiple commands with various features class Command1(Command): def __init__(self): super().__init__("/cmd1", "First command", ["/c1"]) self.add_subcommand("action", "Do action", self.handle_action) self.action_count = 0 def handle_action(self, args): self.action_count += 1 return True class Command2(Command): def __init__(self): super().__init__("/cmd2", "Second command", ["/c2", "/second"]) self.call_count = 0 def handle_no_args(self): self.call_count += 1 return True # Register commands cmd1 = Command1() cmd2 = Command2() register_command(cmd1) register_command(cmd2) # Test command registry state assert len(COMMANDS) == 2 assert len(COMMAND_ALIASES) == 3 # Test various command executions assert handle_command("/cmd1", ["action"]) is True assert cmd1.action_count == 1 assert handle_command("/c1", ["action"]) is True # Using alias assert cmd1.action_count == 2 assert handle_command("/cmd2") is True assert cmd2.call_count == 1 assert handle_command("/c2") is True # Using alias assert cmd2.call_count == 2 assert handle_command("/second") is True # Using second alias assert cmd2.call_count == 3 # Test non-existent command assert handle_command("/nonexistent") is False def test_command_alias_conflicts(self): """Test handling of alias conflicts (last registered wins).""" cmd1 = Command("/cmd1", "First command", ["/shared"]) cmd2 = Command("/cmd2", "Second command", ["/shared"]) # Same alias register_command(cmd1) register_command(cmd2) # This should overwrite the alias # The alias should point to the last registered command assert COMMAND_ALIASES["/shared"] == "/cmd2" retrieved_cmd = get_command("/shared") assert retrieved_cmd == cmd2 def test_complex_subcommand_routing(self): """Test complex subcommand routing scenarios.""" class ComplexCommand(Command): def __init__(self): super().__init__("/complex", "Complex command") self.add_subcommand("sub1", "Subcommand 1", self.handle_sub1) self.add_subcommand("sub2", "Subcommand 2", self.handle_sub2) self.results = {} def handle_sub1(self, args): self.results["sub1"] = args return True def handle_sub2(self, args): self.results["sub2"] = args return False # Return False to test error handling def handle_unknown_subcommand(self, subcommand): self.results["unknown"] = subcommand return True cmd = ComplexCommand() register_command(cmd) # Test subcommand 1 result1 = handle_command("/complex", ["sub1", "arg1", "arg2"]) assert result1 is True assert cmd.results["sub1"] == ["arg1", "arg2"] # Test subcommand 2 (returns False) result2 = handle_command("/complex", ["sub2", "test"]) assert result2 is False assert cmd.results["sub2"] == ["test"] # Test unknown subcommand result3 = handle_command("/complex", ["unknown_sub"]) assert result3 is True assert cmd.results["unknown"] == "unknown_sub" if __name__ == "__main__": pytest.main([__file__, "-v"])