Rohit Malhotra 23d325cb16
CLI(V1): GUI Launcher (#11257)
Co-authored-by: openhands <openhands@all-hands.dev>
2025-10-07 11:23:58 -04:00

155 lines
5.6 KiB
Python

"""Tests for main entry point functionality."""
import sys
from types import SimpleNamespace
from unittest.mock import MagicMock, patch
import pytest
from openhands_cli import simple_main
from openhands_cli.simple_main import main
class TestMainEntryPoint:
"""Test the main entry point behavior."""
@patch('openhands_cli.agent_chat.run_cli_entry')
@patch('sys.argv', ['openhands'])
def test_main_starts_agent_chat_directly(
self, mock_run_agent_chat: MagicMock
) -> None:
"""Test that main() starts agent chat directly when setup succeeds."""
# Mock run_cli_entry to raise KeyboardInterrupt to exit gracefully
mock_run_agent_chat.side_effect = KeyboardInterrupt()
# Should complete without raising an exception (graceful exit)
simple_main.main()
# Should call run_cli_entry with no resume conversation ID
mock_run_agent_chat.assert_called_once_with(resume_conversation_id=None)
@patch('openhands_cli.agent_chat.run_cli_entry')
@patch('sys.argv', ['openhands'])
def test_main_handles_import_error(self, mock_run_agent_chat: MagicMock) -> None:
"""Test that main() handles ImportError gracefully."""
mock_run_agent_chat.side_effect = ImportError('Missing dependency')
# Should raise ImportError (re-raised after handling)
with pytest.raises(ImportError) as exc_info:
simple_main.main()
assert str(exc_info.value) == 'Missing dependency'
@patch('openhands_cli.agent_chat.run_cli_entry')
@patch('sys.argv', ['openhands'])
def test_main_handles_keyboard_interrupt(
self, mock_run_agent_chat: MagicMock
) -> None:
"""Test that main() handles KeyboardInterrupt gracefully."""
# Mock run_cli_entry to raise KeyboardInterrupt
mock_run_agent_chat.side_effect = KeyboardInterrupt()
# Should complete without raising an exception (graceful exit)
simple_main.main()
@patch('openhands_cli.agent_chat.run_cli_entry')
@patch('sys.argv', ['openhands'])
def test_main_handles_eof_error(self, mock_run_agent_chat: MagicMock) -> None:
"""Test that main() handles EOFError gracefully."""
# Mock run_cli_entry to raise EOFError
mock_run_agent_chat.side_effect = EOFError()
# Should complete without raising an exception (graceful exit)
simple_main.main()
@patch('openhands_cli.agent_chat.run_cli_entry')
@patch('sys.argv', ['openhands'])
def test_main_handles_general_exception(
self, mock_run_agent_chat: MagicMock
) -> None:
"""Test that main() handles general exceptions."""
mock_run_agent_chat.side_effect = Exception('Unexpected error')
# Should raise Exception (re-raised after handling)
with pytest.raises(Exception) as exc_info:
simple_main.main()
assert str(exc_info.value) == 'Unexpected error'
@patch('openhands_cli.agent_chat.run_cli_entry')
@patch('sys.argv', ['openhands', '--resume', 'test-conversation-id'])
def test_main_with_resume_argument(self, mock_run_agent_chat: MagicMock) -> None:
"""Test that main() passes resume conversation ID when provided."""
# Mock run_cli_entry to raise KeyboardInterrupt to exit gracefully
mock_run_agent_chat.side_effect = KeyboardInterrupt()
# Should complete without raising an exception (graceful exit)
simple_main.main()
# Should call run_cli_entry with the provided resume conversation ID
mock_run_agent_chat.assert_called_once_with(
resume_conversation_id='test-conversation-id'
)
@pytest.mark.parametrize(
"argv,expected_kwargs",
[
(['openhands'], {"resume_conversation_id": None}),
(['openhands', '--resume', 'test-id'], {"resume_conversation_id": 'test-id'}),
],
)
def test_main_cli_calls_run_cli_entry(monkeypatch, argv, expected_kwargs):
# Patch sys.argv since main() takes no params
monkeypatch.setattr(sys, "argv", argv, raising=False)
called = {}
fake_agent_chat = SimpleNamespace(
run_cli_entry=lambda **kw: called.setdefault("kwargs", kw)
)
# Provide the symbol that main() will import
monkeypatch.setitem(sys.modules, "openhands_cli.agent_chat", fake_agent_chat)
# Execute (no SystemExit expected on success)
main()
assert called["kwargs"] == expected_kwargs
@pytest.mark.parametrize(
"argv,expected_kwargs",
[
(['openhands', 'serve'], {"mount_cwd": False, "gpu": False}),
(['openhands', 'serve', '--mount-cwd'], {"mount_cwd": True, "gpu": False}),
(['openhands', 'serve', '--gpu'], {"mount_cwd": False, "gpu": True}),
(['openhands', 'serve', '--mount-cwd', '--gpu'], {"mount_cwd": True, "gpu": True}),
],
)
def test_main_serve_calls_launch_gui_server(monkeypatch, argv, expected_kwargs):
monkeypatch.setattr(sys, "argv", argv, raising=False)
called = {}
fake_gui = SimpleNamespace(
launch_gui_server=lambda **kw: called.setdefault("kwargs", kw)
)
monkeypatch.setitem(sys.modules, "openhands_cli.gui_launcher", fake_gui)
main()
assert called["kwargs"] == expected_kwargs
@pytest.mark.parametrize(
"argv,expected_exit_code",
[
(['openhands', 'invalid-command'], 2), # argparse error
(['openhands', '--help'], 0), # top-level help
(['openhands', 'serve', '--help'], 0), # subcommand help
],
)
def test_help_and_invalid(monkeypatch, argv, expected_exit_code):
monkeypatch.setattr(sys, "argv", argv, raising=False)
with pytest.raises(SystemExit) as exc:
main()
assert exc.value.code == expected_exit_code