"""Tests for OpenCode Zen provider.""" import os from unittest.mock import Mock, patch import pytest from providers.registry import ModelProviderRegistry from providers.shared import ProviderType from providers.zen import ZenProvider class TestZenProvider: """Test cases for OpenCode Zen provider.""" def test_provider_initialization(self): """Test Zen provider initialization.""" provider = ZenProvider(api_key="test-key") assert provider.api_key == "test-key" assert provider.base_url == "https://opencode.ai/zen/v1" assert provider.FRIENDLY_NAME == "OpenCode Zen" def test_get_provider_type(self): """Test provider type identification.""" provider = ZenProvider(api_key="test-key") assert provider.get_provider_type() == ProviderType.ZEN def test_model_validation(self): """Test model validation.""" provider = ZenProvider(api_key="test-key") # Zen accepts models that are in the registry assert provider.validate_model_name("claude-sonnet-4-5") is True assert provider.validate_model_name("gpt-5.1-codex") is True # Unknown models are rejected assert provider.validate_model_name("unknown-model") is False def test_get_capabilities(self): """Test capability generation.""" provider = ZenProvider(api_key="test-key") # Test with a model in the registry caps = provider.get_capabilities("claude-sonnet-4-5") assert caps.provider == ProviderType.ZEN assert caps.model_name == "claude-sonnet-4-5" assert caps.friendly_name == "OpenCode Zen (claude-sonnet-4-5)" # Test with a model not in registry - should raise error with pytest.raises(ValueError, match="Unsupported model 'unknown-model' for provider zen"): provider.get_capabilities("unknown-model") def test_model_alias_resolution(self): """Test model alias resolution.""" provider = ZenProvider(api_key="test-key") # Test alias resolution assert provider._resolve_model_name("zen-sonnet") == "claude-sonnet-4-5" assert provider._resolve_model_name("zen-sonnet4.5") == "claude-sonnet-4-5" assert provider._resolve_model_name("zen-codex") == "gpt-5.1-codex" assert provider._resolve_model_name("zen-gpt-codex") == "gpt-5.1-codex" # Test case-insensitive assert provider._resolve_model_name("ZEN-SONNET") == "claude-sonnet-4-5" assert provider._resolve_model_name("Zen-Codex") == "gpt-5.1-codex" # Test direct model names (should pass through unchanged) assert provider._resolve_model_name("claude-sonnet-4-5") == "claude-sonnet-4-5" assert provider._resolve_model_name("gpt-5.1-codex") == "gpt-5.1-codex" # Test unknown models pass through assert provider._resolve_model_name("unknown-model") == "unknown-model" def test_list_models(self): """Test model listing with various options.""" provider = ZenProvider(api_key="test-key") # Test basic model listing models = provider.list_models() assert isinstance(models, list) assert len(models) > 0 # Should include our configured models assert "claude-sonnet-4-5" in models assert "gpt-5.1-codex" in models # Should include aliases assert "zen-sonnet" in models assert "zen-codex" in models def test_list_models_with_options(self): """Test model listing with different options.""" provider = ZenProvider(api_key="test-key") # Test without aliases models_no_aliases = provider.list_models(include_aliases=False) assert "zen-sonnet" not in models_no_aliases assert "claude-sonnet-4-5" in models_no_aliases # Test lowercase models_lower = provider.list_models(lowercase=True) assert all(model == model.lower() for model in models_lower) def test_registry_capabilities(self): """Test that registry capabilities are properly loaded.""" provider = ZenProvider(api_key="test-key") # Test that we have a registry assert provider._registry is not None # Test getting all capabilities capabilities = provider.get_all_model_capabilities() assert isinstance(capabilities, dict) assert len(capabilities) > 0 # Should include our configured models assert "claude-sonnet-4-5" in capabilities assert "gpt-5.1-codex" in capabilities # Check capability structure caps = capabilities["claude-sonnet-4-5"] assert caps.provider == ProviderType.ZEN assert caps.context_window == 200000 assert caps.intelligence_score == 17 def test_model_capabilities_lookup(self): """Test capability lookup for known and unknown models.""" provider = ZenProvider(api_key="test-key") # Test known model caps = provider._lookup_capabilities("claude-sonnet-4-5") assert caps is not None assert caps.provider == ProviderType.ZEN # Test unknown model returns None (base class handles error) caps = provider._lookup_capabilities("unknown-zen-model") assert caps is None def test_zen_registration(self): """Test Zen can be registered and retrieved.""" with patch.dict(os.environ, {"ZEN_API_KEY": "test-key"}): # Clean up any existing registration ModelProviderRegistry.unregister_provider(ProviderType.ZEN) # Register the provider ModelProviderRegistry.register_provider(ProviderType.ZEN, ZenProvider) # Retrieve and verify provider = ModelProviderRegistry.get_provider(ProviderType.ZEN) assert provider is not None assert isinstance(provider, ZenProvider) class TestZenAutoMode: """Test auto mode functionality when only Zen is configured.""" def setup_method(self): """Store original state before each test.""" self.registry = ModelProviderRegistry() self._original_providers = self.registry._providers.copy() self._original_initialized = self.registry._initialized_providers.copy() # Clear the registry state for this test self.registry._providers.clear() self.registry._initialized_providers.clear() self._original_env = {} for key in ["ZEN_API_KEY", "GEMINI_API_KEY", "OPENAI_API_KEY", "DEFAULT_MODEL"]: self._original_env[key] = os.environ.get(key) def teardown_method(self): """Restore original state after each test.""" self.registry._providers.clear() self.registry._initialized_providers.clear() self.registry._providers.update(self._original_providers) self.registry._initialized_providers.update(self._original_initialized) for key, value in self._original_env.items(): if value is None: os.environ.pop(key, None) else: os.environ[key] = value @pytest.mark.no_mock_provider def test_zen_only_auto_mode(self): """Test that auto mode works when only Zen is configured.""" os.environ.pop("GEMINI_API_KEY", None) os.environ.pop("OPENAI_API_KEY", None) os.environ["ZEN_API_KEY"] = "test-zen-key" os.environ["DEFAULT_MODEL"] = "auto" mock_registry = Mock() model_names = [ "claude-sonnet-4-5", "claude-haiku-4-5", "gpt-5.1-codex", "gemini-3-pro", "glm-4.6", ] mock_registry.list_models.return_value = model_names # Mock resolve to return a ModelCapabilities-like object for each model def mock_resolve(model_name): if model_name in model_names: mock_config = Mock() mock_config.provider = ProviderType.ZEN mock_config.aliases = [] # Empty list of aliases mock_config.get_effective_capability_rank = Mock(return_value=50) # Add ranking method return mock_config return None mock_registry.resolve.side_effect = mock_resolve ModelProviderRegistry.register_provider(ProviderType.ZEN, ZenProvider) provider = ModelProviderRegistry.get_provider(ProviderType.ZEN) assert provider is not None, "Zen provider should be available with API key" provider._registry = mock_registry available_models = ModelProviderRegistry.get_available_models(respect_restrictions=True) assert len(available_models) > 0, "Should find Zen models in auto mode" assert all(provider_type == ProviderType.ZEN for provider_type in available_models.values()) for model in model_names: assert model in available_models, f"Model {model} should be available" class TestZenIntegration: """Integration tests for Zen provider with server components.""" def test_zen_provider_in_server_init(self): """Test that Zen provider is properly handled during server initialization.""" # This test verifies that the server can handle Zen provider configuration # without actual server startup with patch.dict(os.environ, {"ZEN_API_KEY": "test-integration-key"}): # Import server module to trigger provider setup from providers.registry import ModelProviderRegistry # Verify Zen provider can be registered ModelProviderRegistry.register_provider(ProviderType.ZEN, ZenProvider) provider = ModelProviderRegistry.get_provider(ProviderType.ZEN) assert provider is not None assert isinstance(provider, ZenProvider) def test_zen_config_loading(self): """Test that Zen configuration loads properly in integration context.""" with patch.dict(os.environ, {"ZEN_API_KEY": "test-config-key"}): from providers.registries.zen import ZenModelRegistry # Test registry loads configuration registry = ZenModelRegistry() models = registry.list_models() aliases = registry.list_aliases() assert len(models) > 0, "Should load models from zen_models.json" assert len(aliases) > 0, "Should load aliases from zen_models.json" # Verify specific models are loaded assert "claude-sonnet-4-5" in models assert "zen-sonnet" in aliases def test_zen_provider_priority(self): """Test that Zen provider follows correct priority order.""" # Zen should be prioritized after native APIs but before OpenRouter from providers.registry import ModelProviderRegistry priority_order = ModelProviderRegistry.PROVIDER_PRIORITY_ORDER zen_index = priority_order.index(ProviderType.ZEN) openrouter_index = priority_order.index(ProviderType.OPENROUTER) # Zen should come before OpenRouter in priority assert zen_index < openrouter_index, "Zen should have higher priority than OpenRouter" class TestZenAPIMocking: """Test API interactions with mocked OpenAI SDK.""" def test_chat_completion_mock(self): """Test chat completion with mocked API response.""" provider = ZenProvider(api_key="test-key") # Mock the OpenAI client and response mock_response = Mock() mock_response.choices = [Mock()] mock_response.choices[0].message.content = "Mocked response from Zen" mock_response.usage = Mock() mock_response.usage.prompt_tokens = 10 mock_response.usage.completion_tokens = 20 with patch.object(provider.client.chat.completions, "create", return_value=mock_response): # Test the completion method - this will initialize the client response = provider.complete( model="claude-sonnet-4-5", messages=[{"role": "user", "content": "Hello"}], temperature=0.7 ) assert response.content == "Mocked response from Zen" def test_streaming_completion_mock(self): """Test streaming completion with mocked API.""" provider = ZenProvider(api_key="test-key") # Mock streaming response mock_chunk1 = Mock() mock_chunk1.choices = [Mock()] mock_chunk1.choices[0].delta.content = "Hello" mock_chunk1.choices[0].finish_reason = None mock_chunk2 = Mock() mock_chunk2.choices = [Mock()] mock_chunk2.choices[0].delta.content = " world!" mock_chunk2.choices[0].finish_reason = "stop" mock_stream = [mock_chunk1, mock_chunk2] # Access client to initialize it first _ = provider.client with patch.object(provider.client.chat.completions, "create", return_value=mock_stream): # Test streaming completion stream = provider.complete_stream( model="gpt-5.1-codex", messages=[{"role": "user", "content": "Say hello"}], ) chunks = list(stream) assert len(chunks) == 2 assert chunks[0].content == "Hello" assert chunks[1].content == " world!" def test_api_error_handling(self): """Test error handling for API failures.""" provider = ZenProvider(api_key="test-key") # Mock API error from openai import APIError api_error = APIError("Mock API error", request=Mock(), body="error details") with patch.object(provider._client.chat.completions, "create", side_effect=api_error): with pytest.raises(APIError): provider.complete(model="claude-sonnet-4-5", messages=[{"role": "user", "content": "Test"}]) def test_invalid_model_error(self): """Test error handling for invalid models.""" provider = ZenProvider(api_key="test-key") with pytest.raises(ValueError, match="Unsupported model 'invalid-model' for provider zen"): provider.get_capabilities("invalid-model") def test_authentication_error(self): """Test handling of authentication errors.""" provider = ZenProvider(api_key="invalid-key") # Mock authentication error from openai import AuthenticationError auth_error = AuthenticationError("Invalid API key", request=Mock(), body="auth failed") with patch.object(provider._client.chat.completions, "create", side_effect=auth_error): with pytest.raises(AuthenticationError): provider.complete(model="claude-sonnet-4-5", messages=[{"role": "user", "content": "Test"}])