Skip to main content
Glama
test_client.py13.2 kB
""" Tests for the simplified centralized Google AI and Instructor client management. This module contains unit tests for the functional client module using the new Google GenAI SDK with instructor's from_provider approach. """ import os from unittest.mock import MagicMock, patch import pytest # type: ignore from elrond_mcp.client import ( DEFAULT_CRITIQUE_MODEL, DEFAULT_SYNTHESIS_MODEL, configure, get_critique_client, get_synthesis_client, is_configured, reset, ) class TestConfiguration: """Test configuration functionality.""" def setup_method(self): """Reset module state before each test.""" reset() def teardown_method(self): """Clean up after each test.""" reset() def test_configure_with_api_key(self): """Test explicit configuration with API key.""" api_key = "test-api-key-12345" configure(api_key) # Should set environment variable for instructor assert os.environ.get("GEMINI_API_KEY") == api_key assert is_configured() def test_configure_empty_key_fails(self): """Test that empty API key fails validation.""" with pytest.raises(ValueError) as exc_info: configure("") assert "API key cannot be empty" in str(exc_info.value) def test_configure_none_key_fails(self): """Test that None API key fails validation.""" with pytest.raises(ValueError) as exc_info: configure(None) # type: ignore assert "API key cannot be empty" in str(exc_info.value) def test_auto_configure_from_gemini_api_key(self): """Test auto-configuration from GEMINI_API_KEY environment variable.""" api_key = "env-gemini-key" with patch.dict(os.environ, {"GEMINI_API_KEY": api_key}, clear=True): with patch("instructor.from_provider") as mock_instructor: mock_client = MagicMock() mock_instructor.return_value = mock_client client = get_critique_client() assert client == mock_client assert is_configured() mock_instructor.assert_called_once_with( f"google/{DEFAULT_CRITIQUE_MODEL}", async_client=True ) def test_auto_configure_from_google_api_key(self): """Test auto-configuration from GOOGLE_API_KEY environment variable.""" api_key = "env-google-key" with patch.dict(os.environ, {"GOOGLE_API_KEY": api_key}, clear=True): with patch("instructor.from_provider") as mock_instructor: mock_client = MagicMock() mock_instructor.return_value = mock_client client = get_critique_client() assert client == mock_client assert is_configured() def test_auto_configure_no_api_key_fails(self): """Test that auto-configuration fails when no API key is available.""" with patch.dict(os.environ, {}, clear=True): with pytest.raises(ValueError) as exc_info: get_critique_client() assert "Google AI API key is required" in str(exc_info.value) def test_is_configured_states(self): """Test is_configured returns correct state.""" # Initially not configured assert not is_configured() # After explicit configuration configure("test-key") assert is_configured() # After reset reset() assert not is_configured() # With environment variable with patch.dict(os.environ, {"GEMINI_API_KEY": "env-key"}): assert is_configured() class TestClientCreation: """Test client creation and caching functionality.""" def setup_method(self): """Reset module state before each test.""" reset() def teardown_method(self): """Clean up after each test.""" reset() def test_get_critique_client_default_model(self): """Test getting critique client with default model.""" api_key = "test-key" with patch("instructor.from_provider") as mock_instructor: mock_client = MagicMock() mock_instructor.return_value = mock_client configure(api_key) client = get_critique_client() mock_instructor.assert_called_once_with( f"google/{DEFAULT_CRITIQUE_MODEL}", async_client=True ) assert client == mock_client def test_get_critique_client_custom_model(self): """Test getting critique client with custom model.""" api_key = "test-key" custom_model = "gemini-1.5-pro" with patch("instructor.from_provider") as mock_instructor: mock_client = MagicMock() mock_instructor.return_value = mock_client configure(api_key) client = get_critique_client(custom_model) mock_instructor.assert_called_once_with( f"google/{custom_model}", async_client=True ) assert client == mock_client def test_get_synthesis_client_default_model(self): """Test getting synthesis client with default model.""" api_key = "test-key" with patch("instructor.from_provider") as mock_instructor: mock_client = MagicMock() mock_instructor.return_value = mock_client configure(api_key) client = get_synthesis_client() mock_instructor.assert_called_once_with( f"google/{DEFAULT_SYNTHESIS_MODEL}", async_client=True ) assert client == mock_client def test_get_synthesis_client_custom_model(self): """Test getting synthesis client with custom model.""" api_key = "test-key" custom_model = "gemini-1.5-flash" with patch("instructor.from_provider") as mock_instructor: mock_client = MagicMock() mock_instructor.return_value = mock_client configure(api_key) client = get_synthesis_client(custom_model) mock_instructor.assert_called_once_with( f"google/{custom_model}", async_client=True ) assert client == mock_client def test_client_caching(self): """Test that clients are cached and reused.""" api_key = "test-key" with patch("instructor.from_provider") as mock_instructor: mock_client = MagicMock() mock_instructor.return_value = mock_client configure(api_key) # First call should create client client1 = get_critique_client() # Second call should return cached client client2 = get_critique_client() assert client1 is client2 # Should only call instructor once assert mock_instructor.call_count == 1 def test_different_clients_are_separate(self): """Test that critique and synthesis clients are separate instances.""" api_key = "test-key" with patch("instructor.from_provider") as mock_instructor: mock_critique_client = MagicMock() mock_synthesis_client = MagicMock() mock_instructor.side_effect = [mock_critique_client, mock_synthesis_client] configure(api_key) critique_client = get_critique_client() synthesis_client = get_synthesis_client() assert critique_client is not synthesis_client assert critique_client == mock_critique_client assert synthesis_client == mock_synthesis_client assert mock_instructor.call_count == 2 def test_client_creation_failure(self): """Test client creation failure handling.""" api_key = "test-key" with patch("instructor.from_provider") as mock_instructor: mock_instructor.side_effect = Exception("Provider creation failed") configure(api_key) with pytest.raises(Exception) as exc_info: get_critique_client() assert "Provider creation failed" in str(exc_info.value) class TestReset: """Test reset functionality.""" def setup_method(self): """Reset module state before each test.""" reset() def teardown_method(self): """Clean up after each test.""" reset() def test_reset_clears_cached_clients(self): """Test that reset clears cached clients.""" api_key = "test-key" with patch("instructor.from_provider") as mock_instructor: mock_client1 = MagicMock() mock_client2 = MagicMock() mock_instructor.side_effect = [mock_client1, mock_client2] configure(api_key) # Get initial client client1 = get_critique_client() assert client1 == mock_client1 # Reset and configure again reset() configure(api_key) client2 = get_critique_client() # Should be a new client instance assert client2 == mock_client2 assert client1 is not client2 def test_reset_clears_environment_variable(self): """Test that reset clears the GEMINI_API_KEY environment variable.""" api_key = "test-key" configure(api_key) assert os.environ.get("GEMINI_API_KEY") == api_key reset() assert "GEMINI_API_KEY" not in os.environ def test_reset_after_auto_configure(self): """Test reset after auto-configuration from environment.""" api_key = "env-key" with patch.dict(os.environ, {"GEMINI_API_KEY": api_key}): with patch("instructor.from_provider"): get_critique_client() # Triggers auto-config assert is_configured() reset() # Should not be configured anymore # (even though env var still exists, internal state is cleared) with patch.dict(os.environ, {}, clear=True): assert not is_configured() class TestConstants: """Test module constants.""" def test_default_models(self): """Test that default model constants are properly set.""" assert DEFAULT_CRITIQUE_MODEL == "gemini-2.5-flash" assert DEFAULT_SYNTHESIS_MODEL == "gemini-2.5-pro" class TestIntegration: """Test integration scenarios.""" def setup_method(self): """Reset module state before each test.""" reset() def teardown_method(self): """Clean up after each test.""" reset() def test_full_workflow_explicit_config(self): """Test complete workflow with explicit configuration.""" api_key = "integration-test-key" with patch("instructor.from_provider") as mock_instructor: mock_critique_client = MagicMock() mock_synthesis_client = MagicMock() mock_instructor.side_effect = [mock_critique_client, mock_synthesis_client] # Explicit configuration configure(api_key) assert is_configured() # Get clients critique_client = get_critique_client() synthesis_client = get_synthesis_client() # Verify clients are distinct assert critique_client is not synthesis_client assert critique_client == mock_critique_client assert synthesis_client == mock_synthesis_client # Verify correct instructor calls expected_calls = [ (f"google/{DEFAULT_CRITIQUE_MODEL}",), (f"google/{DEFAULT_SYNTHESIS_MODEL}",), ] actual_calls = [call[0] for call in mock_instructor.call_args_list] assert actual_calls == expected_calls def test_full_workflow_auto_config(self): """Test complete workflow with auto-configuration.""" api_key = "auto-config-key" with patch.dict(os.environ, {"GEMINI_API_KEY": api_key}): with patch("instructor.from_provider") as mock_instructor: mock_client = MagicMock() mock_instructor.return_value = mock_client # Should auto-configure on first client access client = get_critique_client() # Should have auto-configured assert is_configured() assert client == mock_client mock_instructor.assert_called_once_with( f"google/{DEFAULT_CRITIQUE_MODEL}", async_client=True ) def test_mixed_configuration_sources(self): """Test behavior with both explicit config and environment variables.""" env_key = "env-key" explicit_key = "explicit-key" # Set environment variable with patch.dict(os.environ, {"GEMINI_API_KEY": env_key}): # But configure explicitly with different key configure(explicit_key) # Explicit configuration should take precedence assert os.environ.get("GEMINI_API_KEY") == explicit_key assert is_configured()

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/dogonthehorizon/elrond-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server