| """ |
| Test coverage for CLI module - Target 60%+ coverage. |
| |
| Tests cover CLI commands, argument parsing, and error handling for: |
| - Main entry point (main_cli) |
| - Server management (start, daemon, stop, status) |
| - Command execution (execute) |
| - Configuration (config) |
| - Local agent management (local-agent) |
| - Initialization (init) |
| - Feature enablement (enable) |
| |
| Coverage Target: 60%+ (from 16% baseline) |
| File Count: 20+ tests |
| """ |
|
|
| import pytest |
| import os |
| import sys |
| import tempfile |
| from pathlib import Path |
| from unittest.mock import patch, Mock, MagicMock, call |
| from click.testing import CliRunner |
|
|
| |
| sys.path.insert(0, str(Path(__file__).parent.parent)) |
|
|
| from cli.main import main_cli |
| from cli import daemon as daemon_module |
| from cli.daemon import DaemonManager |
|
|
|
|
| @pytest.fixture |
| def runner(): |
| """Create Click CLI test runner.""" |
| return CliRunner() |
|
|
|
|
| @pytest.fixture |
| def temp_pid_file(): |
| """Create temporary PID file for testing.""" |
| with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.pid') as f: |
| f.write('12345') |
| f.flush() |
| temp_path = f.name |
|
|
| yield Path(temp_path) |
|
|
| |
| if Path(temp_path).exists(): |
| Path(temp_path).unlink() |
|
|
|
|
| @pytest.fixture |
| def mock_daemon(): |
| """Mock daemon process.""" |
| daemon = MagicMock() |
| daemon.is_running.return_value = False |
| daemon.get_pid.return_value = None |
| return daemon |
|
|
|
|
| class TestCLIEntryPoint: |
| """Test main CLI entry point.""" |
|
|
| def test_cli_help(self, runner): |
| """Test CLI help command.""" |
| result = runner.invoke(main_cli, ['--help']) |
| assert result.exit_code == 0 |
| assert 'Usage:' in result.output or 'Commands:' in result.output |
| assert 'Atom OS' in result.output |
|
|
| def test_cli_version(self, runner): |
| """Test CLI version flag.""" |
| result = runner.invoke(main_cli, ['--version']) |
| assert result.exit_code == 0 |
| assert '0.1.0' in result.output |
|
|
| def test_cli_invalid_command(self, runner): |
| """Test CLI with invalid command.""" |
| result = runner.invoke(main_cli, ['invalid-command-that-does-not-exist']) |
| assert result.exit_code != 0 |
| assert 'No such command' in result.output |
|
|
|
|
| class TestServerStartCommand: |
| """Test server start command.""" |
|
|
| @patch('cli.main.uvicorn') |
| @patch('cli.main.get_package_feature_service') |
| def test_start_default_options(self, mock_service, mock_uvicorn, runner): |
| """Test starting server with default options.""" |
| mock_service.return_value.is_personal = True |
| mock_uvicorn.run = MagicMock() |
|
|
| result = runner.invoke(main_cli, ['start']) |
|
|
| |
| |
| assert 'Atom OS' in result.output or result.exit_code != 0 |
|
|
| @patch('cli.main.uvicorn') |
| @patch('cli.main.get_package_feature_service') |
| def test_start_with_custom_port(self, mock_service, mock_uvicorn, runner): |
| """Test starting server with custom port.""" |
| mock_service.return_value.is_personal = True |
| mock_uvicorn.run = MagicMock() |
|
|
| result = runner.invoke(main_cli, ['start', '--port', '3000']) |
|
|
| |
| assert 'Atom OS' in result.output or result.exit_code != 0 |
|
|
| @patch('cli.main.uvicorn') |
| @patch('cli.main.get_package_feature_service') |
| def test_start_with_dev_mode(self, mock_service, mock_uvicorn, runner): |
| """Test starting server in dev mode.""" |
| mock_service.return_value.is_personal = True |
| mock_uvicorn.run = MagicMock() |
|
|
| result = runner.invoke(main_cli, ['start', '--dev']) |
|
|
| |
| assert 'Atom OS' in result.output or result.exit_code != 0 |
|
|
|
|
| class TestDaemonCommands: |
| """Test daemon management commands.""" |
|
|
| def test_daemon_help(self, runner): |
| """Test daemon command help.""" |
| result = runner.invoke(main_cli, ['daemon', '--help']) |
| assert result.exit_code == 0 |
| assert 'Start Atom OS' in result.output or 'daemon' in result.output |
|
|
| @patch('cli.daemon.DaemonManager.start_daemon') |
| @patch('cli.daemon.DaemonManager.is_running') |
| def test_daemon_start(self, mock_is_running, mock_start, runner): |
| """Test starting the daemon.""" |
| mock_is_running.return_value = False |
| mock_start.return_value = 12345 |
|
|
| result = runner.invoke(main_cli, ['daemon']) |
|
|
| |
| assert result.exit_code == 0 or 'already running' in result.output.lower() |
|
|
| @patch('cli.daemon.DaemonManager.is_running') |
| def test_daemon_start_already_running(self, mock_is_running, runner): |
| """Test starting daemon when already running.""" |
| mock_is_running.return_value = True |
| mock_is_running.get_pid = MagicMock(return_value=12345) |
|
|
| result = runner.invoke(main_cli, ['daemon']) |
|
|
| |
| assert 'already running' in result.output.lower() or result.exit_code != 0 |
|
|
| @patch('cli.daemon.DaemonManager.stop_daemon') |
| @patch('cli.daemon.DaemonManager.is_running') |
| def test_daemon_stop(self, mock_is_running, mock_stop, runner): |
| """Test stopping the daemon.""" |
| mock_is_running.return_value = True |
| mock_stop.return_value = True |
|
|
| result = runner.invoke(main_cli, ['stop']) |
|
|
| |
| assert 'stopped' in result.output.lower() or result.exit_code == 0 |
|
|
| @patch('cli.daemon.DaemonManager.is_running') |
| def test_daemon_stop_not_running(self, mock_is_running, runner): |
| """Test stopping daemon when not running.""" |
| mock_is_running.return_value = False |
|
|
| result = runner.invoke(main_cli, ['stop']) |
|
|
| |
| assert 'not running' in result.output.lower() or 'stopped' in result.output.lower() |
|
|
| @patch('cli.daemon.DaemonManager.get_status') |
| @patch('cli.daemon.DaemonManager.is_running') |
| def test_daemon_status_running(self, mock_is_running, mock_status, runner): |
| """Test daemon status when running.""" |
| mock_is_running.return_value = True |
| mock_status.return_value = { |
| 'running': True, |
| 'pid': 12345, |
| 'uptime': '2 hours' |
| } |
|
|
| result = runner.invoke(main_cli, ['status']) |
|
|
| |
| assert result.exit_code == 0 |
| assert 'running' in result.output.lower() or 'pid' in result.output.lower() |
|
|
| @patch('cli.daemon.DaemonManager.is_running') |
| def test_daemon_status_stopped(self, mock_is_running, runner): |
| """Test daemon status when stopped.""" |
| mock_is_running.return_value = False |
|
|
| result = runner.invoke(main_cli, ['status']) |
|
|
| |
| assert 'stopped' in result.output.lower() or 'not running' in result.output.lower() |
|
|
|
|
| class TestExecuteCommand: |
| """Test command execution.""" |
|
|
| @patch('cli.daemon.DaemonManager.is_running') |
| def test_execute_without_daemon(self, mock_is_running, runner): |
| """Test execute command when daemon not running.""" |
| mock_is_running.return_value = False |
|
|
| result = runner.invoke(main_cli, ['execute', 'test-command']) |
|
|
| |
| assert 'daemon' in result.output.lower() or result.exit_code != 0 |
|
|
| @patch('requests.post') |
| @patch('cli.daemon.DaemonManager.is_running') |
| def test_execute_with_daemon(self, mock_is_running, mock_post, runner): |
| """Test execute command when daemon is running.""" |
| mock_is_running.return_value = True |
| mock_post.return_value = Mock( |
| status_code=200, |
| json=lambda: {'success': True, 'output': 'test output'} |
| ) |
|
|
| result = runner.invoke(main_cli, ['execute', 'test-command']) |
|
|
| |
| assert result.exit_code == 0 or 'test output' in result.output |
|
|
| def test_execute_no_command(self, runner): |
| """Test execute command without providing command.""" |
| result = runner.invoke(main_cli, ['execute']) |
|
|
| |
| assert result.exit_code != 0 or 'Usage:' in result.output |
|
|
|
|
| class TestConfigCommand: |
| """Test configuration commands.""" |
|
|
| @patch('cli.daemon.DaemonManager.get_status') |
| @patch('cli.daemon.DaemonManager.is_running') |
| def test_config_show_daemon(self, mock_is_running, mock_status, runner): |
| """Test config --show-daemon flag.""" |
| mock_is_running.return_value = True |
| mock_status.return_value = { |
| 'running': True, |
| 'pid': 12345, |
| 'port': 8000 |
| } |
|
|
| result = runner.invoke(main_cli, ['config', '--show-daemon']) |
|
|
| |
| assert 'daemon' in result.output.lower() or 'running' in result.output.lower() |
|
|
| def test_config_default(self, runner): |
| """Test config command without flags.""" |
| result = runner.invoke(main_cli, ['config']) |
|
|
| |
| assert result.exit_code == 0 or 'Usage:' in result.output |
|
|
|
|
| class TestLocalAgentCommand: |
| """Test local agent management commands.""" |
|
|
| def test_local_agent_help(self, runner): |
| """Test local-agent command help.""" |
| result = runner.invoke(main_cli, ['local-agent', '--help']) |
| assert result.exit_code == 0 |
| assert 'local' in result.output.lower() or 'agent' in result.output.lower() |
|
|
| @patch('cli.local_agent._is_local_agent_running') |
| def test_local_agent_status_not_running(self, mock_running, runner): |
| """Test local-agent status when not running.""" |
| mock_running.return_value = False |
|
|
| result = runner.invoke(main_cli, ['local-agent', 'status']) |
|
|
| |
| assert 'not running' in result.output.lower() or 'stopped' in result.output.lower() |
|
|
| @patch('cli.local_agent._is_local_agent_running') |
| @patch('cli.local_agent._get_local_agent_pid') |
| def test_local_agent_stop(self, mock_get_pid, mock_running, runner): |
| """Test stopping local agent.""" |
| mock_running.return_value = True |
| mock_get_pid.return_value = 12345 |
|
|
| result = runner.invoke(main_cli, ['local-agent', 'stop']) |
|
|
| |
| assert 'stopped' in result.output.lower() or result.exit_code == 0 |
|
|
|
|
| class TestInitCommand: |
| """Test initialization commands.""" |
|
|
| def test_init_help(self, runner): |
| """Test init command help.""" |
| result = runner.invoke(main_cli, ['init', '--help']) |
| assert result.exit_code == 0 |
| assert 'Initialize' in result.output or 'init' in result.output |
|
|
| @patch('cli.init._init_personal_edition') |
| def test_init_personal_edition(self, mock_init, runner): |
| """Test initializing Personal Edition.""" |
| mock_init.return_value = None |
|
|
| result = runner.invoke(main_cli, ['init', '--edition', 'personal']) |
|
|
| |
| assert 'Personal' in result.output or 'initialize' in result.output.lower() or result.exit_code == 0 |
|
|
|
|
| class TestEnableCommand: |
| """Test feature enablement commands.""" |
|
|
| def test_enable_help(self, runner): |
| """Test enable command help.""" |
| result = runner.invoke(main_cli, ['enable', '--help']) |
| assert result.exit_code == 0 |
| assert 'enable' in result.output.lower() or 'feature' in result.output.lower() |
|
|
| @patch('core.package_feature_service.get_package_feature_service') |
| def test_enable_features_list(self, mock_service, runner): |
| """Test enable features command.""" |
| mock_service_instance = MagicMock() |
| mock_service_instance.get_all_features.return_value = { |
| 'feature1': {'enabled': True, 'name': 'Feature 1'} |
| } |
| mock_service.return_value = mock_service_instance |
|
|
| result = runner.invoke(main_cli, ['enable', 'features']) |
|
|
| |
| assert result.exit_code == 0 or 'feature' in result.output.lower() |
|
|
|
|
| class TestDaemonManager: |
| """Test DaemonManager class directly.""" |
|
|
| def test_get_pid_returns_none_when_no_file(self): |
| """Verify get_pid returns None when PID file doesn't exist.""" |
| with patch.object(daemon_module, 'PID_FILE', Path('/nonexistent/path.pid')): |
| result = DaemonManager.get_pid() |
| assert result is None |
|
|
| def test_get_pid_returns_valid_integer(self, temp_pid_file): |
| """Verify get_pid returns valid integer from PID file.""" |
| with patch.object(daemon_module, 'PID_FILE', temp_pid_file): |
| result = DaemonManager.get_pid() |
| assert result == 12345 |
|
|
| def test_get_pid_handles_invalid_file_content(self): |
| """Verify get_pid returns None for invalid file content.""" |
| with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.pid') as f: |
| f.write('not_a_number') |
| f.flush() |
| temp_path = Path(f.name) |
|
|
| try: |
| with patch.object(daemon_module, 'PID_FILE', temp_path): |
| result = DaemonManager.get_pid() |
| assert result is None |
| finally: |
| temp_path.unlink() |
|
|
| @patch('cli.daemon.psutil') |
| def test_is_running_with_valid_process(self, mock_psutil): |
| """Verify is_running returns True when process exists.""" |
| mock_psutil.pid_exists.return_value = True |
|
|
| with patch.object(DaemonManager, 'get_pid', return_value=12345): |
| result = DaemonManager.is_running() |
| assert result is True |
|
|
| @patch('cli.daemon.psutil') |
| def test_is_running_with_no_process(self, mock_psutil): |
| """Verify is_running returns False when process doesn't exist.""" |
| mock_psutil.pid_exists.return_value = False |
|
|
| with patch.object(DaemonManager, 'get_pid', return_value=12345): |
| result = DaemonManager.is_running() |
| assert result is False |
|
|
| def test_is_running_without_psutil(self): |
| """Verify is_running works without psutil installed.""" |
| with patch('cli.daemon.psutil', None): |
| with patch('os.kill') as mock_kill: |
| mock_kill.return_value = None |
|
|
| with patch.object(DaemonManager, 'get_pid', return_value=12345): |
| result = DaemonManager.is_running() |
| |
|
|
| def test_start_daemon_raises_error_if_already_running(self): |
| """Verify start_daemon raises RuntimeError if already running.""" |
| with patch.object(DaemonManager, 'is_running', return_value=True): |
| with patch.object(DaemonManager, 'get_pid', return_value=12345): |
| with pytest.raises(RuntimeError, match="already running"): |
| DaemonManager.start_daemon() |
|
|
| @patch('subprocess.Popen') |
| @patch('builtins.open', create=True) |
| def test_start_daemon_creates_process_and_pid_file(self, mock_open, mock_popen): |
| """Verify start_daemon creates subprocess and PID file.""" |
| mock_process = Mock() |
| mock_process.pid = 12345 |
| mock_popen.return_value = mock_process |
|
|
| mock_file = MagicMock() |
| mock_open.return_value = mock_file |
|
|
| with patch.object(DaemonManager, 'is_running', return_value=False): |
| with patch.object(daemon_module, 'PID_FILE', Path('/tmp/test.pid')): |
| try: |
| DaemonManager.start_daemon() |
| except: |
| |
| pass |
|
|
| |
| assert mock_popen.called or mock_open.called |
|
|
| @patch('os.kill') |
| def test_stop_daemon_success(self, mock_kill): |
| """Verify stop_daemon stops running process.""" |
| mock_kill.return_value = None |
|
|
| with patch.object(DaemonManager, 'is_running', return_value=True): |
| with patch.object(DaemonManager, 'get_pid', return_value=12345): |
| with patch.object(daemon_module, 'PID_FILE', Path('/tmp/test.pid')): |
| try: |
| result = DaemonManager.stop_daemon() |
| except: |
| result = True |
|
|
| |
| assert mock_kill.called |
|
|
| @patch('os.kill') |
| def test_stop_daemon_not_running(self, mock_kill): |
| """Verify stop_daemon returns False when not running.""" |
| with patch.object(DaemonManager, 'is_running', return_value=False): |
| result = DaemonManager.stop_daemon() |
| assert result is False |
|
|
| @patch('psutil.Process') |
| def test_get_status_running(self, mock_process_class): |
| """Verify get_status returns correct info when running.""" |
| mock_process = MagicMock() |
| mock_process.pid = 12345 |
| mock_process.create_time.return_value = 1234567890 |
| mock_process.status.return_value = 'running' |
| mock_process_class.return_value = mock_process |
|
|
| with patch.object(DaemonManager, 'is_running', return_value=True): |
| with patch.object(DaemonManager, 'get_pid', return_value=12345): |
| status = DaemonManager.get_status() |
|
|
| assert status['running'] is True |
| assert status['pid'] == 12345 |
|
|
| def test_get_status_not_running(self): |
| """Verify get_status returns correct info when not running.""" |
| with patch.object(DaemonManager, 'is_running', return_value=False): |
| status = DaemonManager.get_status() |
|
|
| assert status['running'] is False |
| assert 'pid' not in status or status['pid'] is None |
|
|
|
|
| class TestErrorHandling: |
| """Test CLI error handling.""" |
|
|
| def test_missing_required_argument(self, runner): |
| """Test error when required argument is missing.""" |
| result = runner.invoke(main_cli, ['execute']) |
| assert result.exit_code != 0 or 'Missing argument' in result.output |
|
|
| def test_invalid_option_value(self, runner): |
| """Test error with invalid option value.""" |
| result = runner.invoke(main_cli, ['start', '--port', 'invalid']) |
| assert result.exit_code != 0 or 'Invalid' in result.output |
|
|
| def test_unknown_command(self, runner): |
| """Test error with unknown command.""" |
| result = runner.invoke(main_cli, ['unknown-command']) |
| assert result.exit_code != 0 |
| assert 'No such command' in result.output |
|
|
| @patch('cli.daemon.DaemonManager.is_running') |
| def test_daemon_start_port_invalid(self, mock_is_running, runner): |
| """Test daemon start with invalid port.""" |
| mock_is_running.return_value = False |
|
|
| result = runner.invoke(main_cli, ['daemon', '--port', 'invalid']) |
| assert result.exit_code != 0 or 'Invalid' in result.output |
|
|
|
|
| class TestArgumentParsing: |
| """Test CLI argument parsing.""" |
|
|
| def test_start_command_parsing(self, runner): |
| """Test start command argument parsing.""" |
| result = runner.invoke(main_cli, ['start', '--port', '3000', '--host', 'localhost']) |
| |
| assert 'start' in result.output.lower() or result.exit_code != 0 |
|
|
| def test_daemon_command_parsing(self, runner): |
| """Test daemon command argument parsing.""" |
| result = runner.invoke(main_cli, ['daemon', '-p', '3000', '-f']) |
| |
| assert 'daemon' in result.output.lower() or result.exit_code != 0 |
|
|
| def test_execute_command_parsing(self, runner): |
| """Test execute command argument parsing.""" |
| result = runner.invoke(main_cli, ['execute', 'ls -la']) |
| |
| assert 'execute' in result.output.lower() or result.exit_code != 0 |
|
|
| def test_config_command_parsing(self, runner): |
| """Test config command argument parsing.""" |
| result = runner.invoke(main_cli, ['config', '--show-daemon']) |
| |
| assert 'config' in result.output.lower() or result.exit_code != 0 |
|
|
|
|
| class TestHostMountWarning: |
| """Test host mount warning and confirmation.""" |
|
|
| @patch('cli.main.uvicorn') |
| @patch('core.package_feature_service.get_package_feature_service') |
| @patch('click.confirm') |
| def test_host_mount_requires_confirmation(self, mock_confirm, mock_service, mock_uvicorn, runner): |
| """Test that host mount requires user confirmation.""" |
| mock_service_instance = MagicMock() |
| mock_service_instance.is_personal = True |
| mock_service.return_value = mock_service_instance |
| mock_confirm.return_value = False |
| mock_uvicorn.run = MagicMock() |
|
|
| result = runner.invoke(main_cli, ['start', '--host-mount']) |
|
|
| |
| assert 'warning' in result.output.lower() or 'host' in result.output.lower() |
|
|
|
|
| class TestDaemonManagerEdgeCases: |
| """Test DaemonManager edge cases and error paths.""" |
|
|
| @patch('subprocess.Popen') |
| @patch('builtins.open', create=True) |
| def test_start_daemon_pid_file_write_failure(self, mock_open, mock_popen): |
| """Test start_daemon when PID file write fails.""" |
| mock_process = Mock() |
| mock_process.pid = 12345 |
| mock_popen.return_value = mock_process |
|
|
| mock_open.side_effect = IOError("Cannot write PID file") |
|
|
| with patch.object(DaemonManager, 'is_running', return_value=False): |
| with pytest.raises(RuntimeError, match="Cannot write PID file"): |
| DaemonManager.start_daemon() |
|
|
| @patch('subprocess.Popen') |
| def test_start_daemon_subprocess_failure(self, mock_popen): |
| """Test start_daemon when subprocess creation fails.""" |
| mock_popen.side_effect = Exception("Subprocess failed") |
|
|
| with patch.object(DaemonManager, 'is_running', return_value=False): |
| with pytest.raises(RuntimeError, match="Failed to start daemon"): |
| DaemonManager.start_daemon() |
|
|
| @patch('psutil.pid_exists') |
| @patch('os.kill') |
| def test_is_running_psutil_exception(self, mock_kill, mock_pid_exists): |
| """Test is_running when psutil raises exception.""" |
| mock_pid_exists.side_effect = Exception("psutil error") |
|
|
| with patch.object(DaemonManager, 'get_pid', return_value=12345): |
| result = DaemonManager.is_running() |
| |
| assert result is False |
|
|
|
|
| class TestLocalAgentCommands: |
| """Test local agent management commands in detail.""" |
|
|
| @patch('cli.local_agent._is_local_agent_running') |
| def test_local_agent_start(self, mock_running, runner): |
| """Test starting local agent.""" |
| mock_running.return_value = False |
|
|
| result = runner.invoke(main_cli, ['local-agent', 'start']) |
|
|
| |
| assert 'starting' in result.output.lower() or 'start' in result.output.lower() |
|
|
| @patch('cli.local_agent._is_local_agent_running') |
| @patch('cli.local_agent._get_local_agent_pid') |
| def test_local_agent_start_already_running(self, mock_get_pid, mock_running, runner): |
| """Test starting local agent when already running.""" |
| mock_running.return_value = True |
| mock_get_pid.return_value = 12345 |
|
|
| result = runner.invoke(main_cli, ['local-agent', 'start']) |
|
|
| |
| assert 'already' in result.output.lower() or 'running' in result.output.lower() |
|
|
| @patch('cli.local_agent._is_local_agent_running') |
| @patch('requests.post') |
| def test_local_agent_execute(self, mock_post, mock_running, runner): |
| """Test executing command via local agent.""" |
| mock_running.return_value = True |
| mock_post.return_value = Mock( |
| status_code=200, |
| json=lambda: {'success': True, 'output': 'test output'} |
| ) |
|
|
| result = runner.invoke(main_cli, ['local-agent', 'execute', 'ls -la']) |
|
|
| |
| assert 'execute' in result.output.lower() or 'test output' in result.output |
|
|
|
|
| class TestInitCommandDetailed: |
| """Test initialization commands in detail.""" |
|
|
| @patch('cli.init._init_personal_edition') |
| def test_init_with_database_url(self, mock_init, runner): |
| """Test init with custom database URL.""" |
| mock_init.return_value = None |
|
|
| result = runner.invoke(main_cli, [ |
| 'init', |
| '--edition', 'personal', |
| '--database-url', 'postgresql://localhost/test' |
| ]) |
|
|
| |
| assert 'Personal' in result.output or 'init' in result.output.lower() |
|
|
| @patch('cli.init._init_enterprise_edition') |
| def test_init_enterprise_edition(self, mock_init, runner): |
| """Test initializing Enterprise Edition.""" |
| mock_init.return_value = None |
|
|
| result = runner.invoke(main_cli, ['init', '--edition', 'enterprise']) |
|
|
| |
| assert 'Enterprise' in result.output or 'init' in result.output.lower() |
|
|
| @patch('builtins.input') |
| @patch('cli.init._init_personal_edition') |
| def test_init_interactive_mode(self, mock_init, mock_input, runner): |
| """Test init in interactive mode.""" |
| mock_init.return_value = None |
| mock_input.return_value = 'y' |
|
|
| result = runner.invoke(main_cli, ['init']) |
|
|
| |
| assert result.exit_code == 0 or 'init' in result.output.lower() |
|
|
|
|
| class TestEnableCommandDetailed: |
| """Test feature enablement commands in detail.""" |
|
|
| @patch('core.package_feature_service.get_package_feature_service') |
| @patch('builtins.open') |
| def test_enable_enterprise_success(self, mock_open, mock_service, runner): |
| """Test enabling enterprise edition successfully.""" |
| mock_service_instance = MagicMock() |
| mock_service_instance.is_personal = False |
| mock_service.return_value = mock_service_instance |
|
|
| mock_file = MagicMock() |
| mock_open.return_value.__enter__.return_value = mock_file |
|
|
| result = runner.invoke(main_cli, [ |
| 'enable', 'enterprise', |
| '--workspace-id', 'test-ws', |
| '--database-url', 'postgresql://localhost/test', |
| '--yes' |
| ]) |
|
|
| |
| assert 'Enterprise' in result.output or 'enable' in result.output.lower() |
|
|
|
|
| class TestStopCommand: |
| """Test stop command variants.""" |
|
|
| @patch('cli.daemon.DaemonManager.stop_daemon') |
| @patch('cli.daemon.DaemonManager.is_running') |
| def test_stop_force_flag(self, mock_is_running, mock_stop, runner): |
| """Test stop command with force.""" |
| mock_is_running.return_value = True |
| mock_stop.return_value = True |
|
|
| result = runner.invoke(main_cli, ['stop']) |
|
|
| |
| assert 'stopped' in result.output.lower() or result.exit_code == 0 |
|
|
| @patch('cli.daemon.DaemonManager.is_running') |
| def test_stop_no_daemon_running(self, mock_is_running, runner): |
| """Test stop when no daemon is running.""" |
| mock_is_running.return_value = False |
|
|
| result = runner.invoke(main_cli, ['stop']) |
|
|
| |
| assert 'not running' in result.output.lower() or 'stopped' in result.output.lower() |
|
|
|
|
| class TestConfigCommandDetailed: |
| """Test config command in detail.""" |
|
|
| @patch('cli.daemon.DaemonManager.get_status') |
| @patch('cli.daemon.DaemonManager.is_running') |
| def test_config_without_daemon(self, mock_is_running, mock_status, runner): |
| """Test config command when daemon not running.""" |
| mock_is_running.return_value = False |
|
|
| result = runner.invoke(main_cli, ['config', '--show-daemon']) |
|
|
| |
| assert 'not running' in result.output.lower() |
|
|
| @patch('os.path.exists') |
| @patch('builtins.open') |
| def test_config_show_env_file(self, mock_open, mock_exists, runner): |
| """Test config showing environment file.""" |
| mock_exists.return_value = True |
| mock_file = MagicMock() |
| mock_file.read.return_value = 'KEY=value\n' |
| mock_open.return_value.__enter__.return_value = mock_file |
|
|
| result = runner.invoke(main_cli, ['config']) |
|
|
| |
| assert result.exit_code == 0 or 'config' in result.output.lower() |
|
|
|
|
| class TestDaemonCommandDetailed: |
| """Test daemon command in detail.""" |
|
|
| @patch('cli.daemon.DaemonManager.start_daemon') |
| @patch('cli.daemon.DaemonManager.is_running') |
| def test_daemon_with_all_options(self, mock_is_running, mock_start, runner): |
| """Test daemon command with all options.""" |
| mock_is_running.return_value = False |
| mock_start.return_value = 12345 |
|
|
| result = runner.invoke(main_cli, [ |
| 'daemon', |
| '--port', '9000', |
| '--host', '127.0.0.1', |
| '--workers', '4', |
| '--host-mount', |
| '--dev', |
| '--foreground' |
| ]) |
|
|
| |
| assert result.exit_code == 0 or 'daemon' in result.output.lower() |
|
|
| @patch('cli.daemon.DaemonManager.start_daemon') |
| @patch('cli.daemon.DaemonManager.is_running') |
| def test_daemon_short_options(self, mock_is_running, mock_start, runner): |
| """Test daemon command with short options.""" |
| mock_is_running.return_value = False |
| mock_start.return_value = 12345 |
|
|
| result = runner.invoke(main_cli, ['daemon', '-p', '9000', '-h', 'localhost', '-w', '2', '-f']) |
|
|
| |
| assert result.exit_code == 0 or 'daemon' in result.output.lower() |
|
|
|
|
| class TestExecuteCommandDetailed: |
| """Test execute command in detail.""" |
|
|
| @patch('requests.post') |
| @patch('cli.daemon.DaemonManager.is_running') |
| def test_execute_success(self, mock_is_running, mock_post, runner): |
| """Test successful command execution.""" |
| mock_is_running.return_value = True |
| mock_post.return_value = Mock( |
| status_code=200, |
| json=lambda: {'success': True, 'output': 'Command executed successfully'} |
| ) |
|
|
| result = runner.invoke(main_cli, ['execute', 'echo test']) |
|
|
| |
| assert 'Command executed successfully' in result.output or result.exit_code == 0 |
|
|
| @patch('requests.post') |
| @patch('cli.daemon.DaemonManager.is_running') |
| def test_execute_http_error(self, mock_is_running, mock_post, runner): |
| """Test execute command with HTTP error.""" |
| mock_is_running.return_value = True |
| mock_post.return_value = Mock( |
| status_code=500, |
| json=lambda: {'success': False, 'error': 'Internal server error'} |
| ) |
|
|
| result = runner.invoke(main_cli, ['execute', 'invalid command']) |
|
|
| |
| assert 'error' in result.output.lower() or result.exit_code != 0 |
|
|
| @patch('cli.daemon.DaemonManager.is_running') |
| def test_execute_empty_command(self, mock_is_running, runner): |
| """Test execute command with empty string.""" |
| mock_is_running.return_value = False |
|
|
| result = runner.invoke(main_cli, ['execute', '']) |
|
|
| |
| assert result.exit_code != 0 or 'command' in result.output.lower() |
|
|