from unittest import mock import pytest from openhands.core.config import SandboxConfig,OpenHandsConfig from openhands.events.action import CmdRunAction from openhands.resolver.issue_resolver import IssueResolver def assert_sandbox_config( config: SandboxConfig, base_container_image=SandboxConfig.model_fields['base_container_image'].default, runtime_container_image='ghcr.io/all-hands-ai/runtime:mock-nikolaik', # Default to mock version local_runtime_url=SandboxConfig.model_fields['local_runtime_url'].default, ): """Helper function to assert the properties of the SandboxConfig object.""" assert isinstance(config, SandboxConfig) assert config.base_container_image == base_container_image assert config.runtime_container_image == runtime_container_image assert config.enable_auto_lint is False assert config.use_host_network is False assert config.timeout == 300 assert config.local_runtime_url == local_runtime_url def test_setup_sandbox_config_default(): """Test default configuration when no images provided and not experimental""" with mock.patch('openhands.__version__', 'mock'): openhands_config = OpenHandsConfig() IssueResolver.update_sandbox_config( openhands_config=openhands_config, base_container_image=None, runtime_container_image=None, is_experimental=False, ) assert_sandbox_config( openhands_config.sandbox, runtime_container_image='ghcr.io/all-hands-ai/runtime:mock-nikolaik' ) def test_setup_sandbox_config_both_images(): """Test that providing both container images raises ValueError""" with pytest.raises( ValueError, match='Cannot provide both runtime and base container images.' ): openhands_config = OpenHandsConfig() IssueResolver.update_sandbox_config( openhands_config=openhands_config, base_container_image='base-image', runtime_container_image='runtime-image', is_experimental=False, ) def test_setup_sandbox_config_base_only(): """Test configuration when only base_container_image is provided""" base_image = 'custom-base-image' openhands_config = OpenHandsConfig() IssueResolver.update_sandbox_config( openhands_config=openhands_config, base_container_image=base_image, runtime_container_image=None, is_experimental=False, ) assert_sandbox_config( openhands_config.sandbox, base_container_image=base_image, runtime_container_image=None ) def test_setup_sandbox_config_runtime_only(): """Test configuration when only runtime_container_image is provided""" runtime_image = 'custom-runtime-image' openhands_config = OpenHandsConfig() IssueResolver.update_sandbox_config( openhands_config=openhands_config, base_container_image=None, runtime_container_image=runtime_image, is_experimental=False, ) assert_sandbox_config(openhands_config.sandbox, runtime_container_image=runtime_image) def test_setup_sandbox_config_experimental(): """Test configuration when experimental mode is enabled""" with mock.patch('openhands.__version__', 'mock'): openhands_config = OpenHandsConfig() IssueResolver.update_sandbox_config( openhands_config=openhands_config, base_container_image=None, runtime_container_image=None, is_experimental=True, ) assert_sandbox_config(openhands_config.sandbox, runtime_container_image=None) @mock.patch('openhands.resolver.issue_resolver.os.getuid', return_value=0) @mock.patch('openhands.resolver.issue_resolver.get_unique_uid', return_value=1001) def test_setup_sandbox_config_gitlab_ci(mock_get_unique_uid, mock_getuid): """Test GitLab CI specific configuration when running as root""" with mock.patch('openhands.__version__', 'mock'): with mock.patch.object(IssueResolver, 'GITLAB_CI', True): openhands_config = OpenHandsConfig() IssueResolver.update_sandbox_config( openhands_config=openhands_config, base_container_image=None, runtime_container_image=None, is_experimental=False, ) assert_sandbox_config(openhands_config.sandbox, local_runtime_url='http://localhost') @mock.patch('openhands.resolver.issue_resolver.os.getuid', return_value=1000) def test_setup_sandbox_config_gitlab_ci_non_root(mock_getuid): """Test GitLab CI configuration when not running as root""" with mock.patch('openhands.__version__', 'mock'): with mock.patch.object(IssueResolver, 'GITLAB_CI', True): openhands_config = OpenHandsConfig() IssueResolver.update_sandbox_config( openhands_config=openhands_config, base_container_image=None, runtime_container_image=None, is_experimental=False, ) assert_sandbox_config(openhands_config.sandbox, local_runtime_url='http://localhost') @mock.patch('openhands.events.observation.CmdOutputObservation') @mock.patch('openhands.runtime.base.Runtime') def test_initialize_runtime_runs_setup_script_and_git_hooks( mock_runtime, mock_cmd_output ): """Test that initialize_runtime calls maybe_run_setup_script and maybe_setup_git_hooks""" # Create a minimal resolver instance with just the methods we need class MinimalResolver: def initialize_runtime(self, runtime): # This is the method we're testing action = CmdRunAction(command='git config --global core.pager ""') runtime.run_action(action) # Run setup script if it exists runtime.maybe_run_setup_script() # Setup git hooks if they exist runtime.maybe_setup_git_hooks() resolver = MinimalResolver() # Mock the runtime's run_action method to return a successful CmdOutputObservation mock_cmd_output.return_value.exit_code = 0 mock_runtime.run_action.return_value = mock_cmd_output.return_value # Call the method resolver.initialize_runtime(mock_runtime) # Verify that both methods were called mock_runtime.maybe_run_setup_script.assert_called_once() mock_runtime.maybe_setup_git_hooks.assert_called_once()