"""Tests for CLI entry point.""" import pytest import sys from unittest.mock import MagicMock, patch from io import StringIO from gsd_openclaw.tmux_session import TmuxSession, TmuxSessionError, CleanupResult, SessionInfo from gsd_openclaw.cli import main, cmd_create, cmd_list, cmd_destroy, cmd_resume, cmd_cleanup class TestCliCreate: """Tests for CLI create command.""" def test_cli_create_creates_session(self): """`gsd-ocl create ` creates session.""" mock_session = MagicMock() mock_session.tmux_session_name = "gsd-test-project" mock_session.session_id = "test-123" mock_session.project_name = "test-project" with patch('gsd_openclaw.cli.TmuxSessionManager') as MockManager: mock_manager = MagicMock() mock_manager.create_session.return_value = mock_session MockManager.return_value = mock_manager with patch.object(sys, 'argv', ['gsd-ocl', 'create', 'test-project']): main() mock_manager.create_session.assert_called_once_with( project_name='test-project', display_name=None ) def test_cli_create_with_display_name(self): """`gsd-ocl create --display-name` sets display name.""" mock_session = MagicMock() mock_session.tmux_session_name = "gsd-test-project" mock_session.session_id = "test-123" mock_session.project_name = "test-project" with patch('gsd_openclaw.cli.TmuxSessionManager') as MockManager: mock_manager = MagicMock() mock_manager.create_session.return_value = mock_session MockManager.return_value = mock_manager with patch.object(sys, 'argv', ['gsd-ocl', 'create', 'test-project', '-d', 'My Project']): main() mock_manager.create_session.assert_called_once_with( project_name='test-project', display_name='My Project' ) def test_cli_create_exits_on_error(self): """CLI exits with code 1 on error.""" with patch('gsd_openclaw.cli.TmuxSessionManager') as MockManager: mock_manager = MagicMock() mock_manager.create_session.side_effect = Exception("Create failed") MockManager.return_value = mock_manager with patch.object(sys, 'argv', ['gsd-ocl', 'create', 'test-project']): with pytest.raises(SystemExit) as exc_info: main() assert exc_info.value.code == 1 class TestCliList: """Tests for CLI list command.""" def test_cli_list_shows_sessions(self): """`gsd-ocl list` shows sessions.""" mock_session_info = SessionInfo( tmux_name="gsd-test-project", project_name="test-project", session_id="test-123", created_at="2026-03-10T10:00:00", last_active_at="2026-03-10T12:00:00", command_count=5, display_name="Test Project", is_orphan=False ) with patch('gsd_openclaw.cli.TmuxSessionManager') as MockManager: mock_manager = MagicMock() mock_manager.list_gsd_sessions.return_value = [mock_session_info] MockManager.return_value = mock_manager stdout = StringIO() with patch.object(sys, 'argv', ['gsd-ocl', 'list']): with patch('sys.stdout', stdout): main() output = stdout.getvalue() assert "test-project" in output or "Test Project" in output assert "test-123" in output assert "active" in output def test_cli_list_shows_no_sessions_message(self): """`gsd-ocl list` shows message when no sessions.""" with patch('gsd_openclaw.cli.TmuxSessionManager') as MockManager: mock_manager = MagicMock() mock_manager.list_gsd_sessions.return_value = [] MockManager.return_value = mock_manager stdout = StringIO() with patch.object(sys, 'argv', ['gsd-ocl', 'list']): with patch('sys.stdout', stdout): main() output = stdout.getvalue() assert "No active sessions" in output def test_cli_list_shows_orphan_status(self): """`gsd-ocl list` shows ORPHAN status for orphan sessions.""" mock_session_info = SessionInfo( tmux_name="gsd-orphan", project_name="orphan", session_id="?", created_at="?", last_active_at="?", command_count=0, display_name=None, is_orphan=True ) with patch('gsd_openclaw.cli.TmuxSessionManager') as MockManager: mock_manager = MagicMock() mock_manager.list_gsd_sessions.return_value = [mock_session_info] MockManager.return_value = mock_manager stdout = StringIO() with patch.object(sys, 'argv', ['gsd-ocl', 'list']): with patch('sys.stdout', stdout): main() output = stdout.getvalue() assert "ORPHAN" in output class TestCliDestroy: """Tests for CLI destroy command.""" def test_cli_destroy_removes_session(self): """`gsd-ocl destroy ` removes session.""" with patch('gsd_openclaw.cli.TmuxSessionManager') as MockManager: mock_manager = MagicMock() mock_manager.destroy_session.return_value = True MockManager.return_value = mock_manager with patch.object(sys, 'argv', ['gsd-ocl', 'destroy', 'test-project']): main() mock_manager.destroy_session.assert_called_once_with( project_name='test-project', cleanup_workspace=False ) def test_cli_destroy_with_cleanup(self): """`gsd-ocl destroy --cleanup` removes workspace too.""" with patch('gsd_openclaw.cli.TmuxSessionManager') as MockManager: mock_manager = MagicMock() mock_manager.destroy_session.return_value = True MockManager.return_value = mock_manager with patch.object(sys, 'argv', ['gsd-ocl', 'destroy', 'test-project', '-c']): main() mock_manager.destroy_session.assert_called_once_with( project_name='test-project', cleanup_workspace=True ) def test_cli_destroy_exits_if_not_found(self): """`gsd-ocl destroy` exits with code 1 if session not found.""" with patch('gsd_openclaw.cli.TmuxSessionManager') as MockManager: mock_manager = MagicMock() mock_manager.destroy_session.return_value = False MockManager.return_value = mock_manager with patch.object(sys, 'argv', ['gsd-ocl', 'destroy', 'nonexistent']): with pytest.raises(SystemExit) as exc_info: main() assert exc_info.value.code == 1 class TestCliResume: """Tests for CLI resume command.""" def test_cli_resume_resumes_session(self): """`gsd-ocl resume ` resumes session.""" mock_session = MagicMock() mock_session.tmux_session_name = "gsd-test-project" mock_session.session_id = "test-123" mock_session.project_name = "test-project" with patch('gsd_openclaw.cli.TmuxSessionManager') as MockManager: mock_manager = MagicMock() mock_manager.resume_session.return_value = mock_session MockManager.return_value = mock_manager with patch.object(sys, 'argv', ['gsd-ocl', 'resume', 'test-project']): main() mock_manager.resume_session.assert_called_once_with(project_name='test-project') def test_cli_resume_exits_on_error(self): """`gsd-ocl resume` exits with code 1 on error.""" with patch('gsd_openclaw.cli.TmuxSessionManager') as MockManager: mock_manager = MagicMock() mock_manager.resume_session.side_effect = TmuxSessionError("No session found") MockManager.return_value = mock_manager with patch.object(sys, 'argv', ['gsd-ocl', 'resume', 'nonexistent']): with pytest.raises(SystemExit) as exc_info: main() assert exc_info.value.code == 1 class TestCliCleanup: """Tests for CLI cleanup command.""" def test_cli_cleanup_removes_orphans(self): """`gsd-ocl cleanup` removes orphan sessions.""" result = CleanupResult( cleaned_sessions=["gsd-orphan1", "gsd-orphan2"], errors=[] ) with patch('gsd_openclaw.cli.TmuxSessionManager') as MockManager: mock_manager = MagicMock() mock_manager.cleanup_orphans.return_value = result MockManager.return_value = mock_manager stdout = StringIO() with patch.object(sys, 'argv', ['gsd-ocl', 'cleanup']): with patch('sys.stdout', stdout): main() output = stdout.getvalue() assert "2" in output assert "gsd-orphan1" in output assert "gsd-orphan2" in output def test_cli_cleanup_shows_errors(self): """`gsd-ocl cleanup` shows errors.""" result = CleanupResult( cleaned_sessions=[], errors=["Failed to kill gsd-failing: error"] ) with patch('gsd_openclaw.cli.TmuxSessionManager') as MockManager: mock_manager = MagicMock() mock_manager.cleanup_orphans.return_value = result MockManager.return_value = mock_manager stdout = StringIO() with patch.object(sys, 'argv', ['gsd-ocl', 'cleanup']): with patch('sys.stdout', stdout): main() output = stdout.getvalue() assert "error" in output.lower() or "Error" in output def test_cli_cleanup_no_orphans_found(self): """`gsd-ocl cleanup` shows message when no orphans found.""" result = CleanupResult( cleaned_sessions=[], errors=[] ) with patch('gsd_openclaw.cli.TmuxSessionManager') as MockManager: mock_manager = MagicMock() mock_manager.cleanup_orphans.return_value = result MockManager.return_value = mock_manager stdout = StringIO() with patch.object(sys, 'argv', ['gsd-ocl', 'cleanup']): with patch('sys.stdout', stdout): main() output = stdout.getvalue() assert "No orphan sessions found" in output class TestCliHelp: """Tests for CLI help.""" def test_cli_help_works(self): """`gsd-ocl --help` shows help message.""" stdout = StringIO() with patch.object(sys, 'argv', ['gsd-ocl', '--help']): with patch('sys.stdout', stdout): with pytest.raises(SystemExit) as exc_info: main() # --help exits with code 0 assert exc_info.value.code == 0 output = stdout.getvalue() assert "gsd-ocl" in output def test_cli_requires_subcommand(self): """CLI requires a subcommand.""" with patch.object(sys, 'argv', ['gsd-ocl']): with pytest.raises(SystemExit) as exc_info: main() assert exc_info.value.code != 0