| import pytest |
| from unittest.mock import AsyncMock, MagicMock, patch |
| from pydantic import BaseModel |
| from src.config import settings |
| from src.llm.gemini import GeminiProvider |
|
|
| class DummyResponseModel(BaseModel): |
| name: str |
| score: int |
|
|
| @pytest.mark.asyncio |
| async def test_gemini_generate_structured(monkeypatch): |
| monkeypatch.setattr(settings, "gemini_api_key", "mock-key") |
| provider = GeminiProvider(model_name="gemini-2.5-flash") |
| |
| |
| mock_response = MagicMock() |
| mock_response.text = '{"name": "test_name", "score": 42}' |
| |
| |
| with patch.object(provider.client.aio.models, "generate_content", new_callable=AsyncMock) as mock_gen: |
| mock_gen.return_value = mock_response |
| |
| result = await provider.generate_structured( |
| prompt="Dummy prompt", |
| response_schema=DummyResponseModel |
| ) |
| |
| assert isinstance(result, DummyResponseModel) |
| assert result.name == "test_name" |
| assert result.score == 42 |
| mock_gen.assert_called_once() |
|
|
| @pytest.mark.asyncio |
| async def test_gemini_generate_text(monkeypatch): |
| monkeypatch.setattr(settings, "gemini_api_key", "mock-key") |
| provider = GeminiProvider(model_name="gemini-2.5-flash") |
| |
| mock_response = MagicMock() |
| mock_response.text = "Hello, world!" |
| |
| with patch.object(provider.client.aio.models, "generate_content", new_callable=AsyncMock) as mock_gen: |
| mock_gen.return_value = mock_response |
| |
| result = await provider.generate_text(prompt="Say hello") |
| |
| assert result == "Hello, world!" |
| mock_gen.assert_called_once() |
|
|
|
|
| @pytest.mark.asyncio |
| async def test_gemini_key_rotation_on_429(monkeypatch): |
| |
| monkeypatch.setattr(settings, "gemini_api_key", "") |
| monkeypatch.setattr(settings, "gemini_api_key_1", "key1") |
| monkeypatch.setattr(settings, "gemini_api_key_2", "key2") |
| monkeypatch.setattr(settings, "gemini_api_key_3", "key3") |
| |
| provider = GeminiProvider(model_name="gemini-2.5-flash") |
| |
| assert len(provider._clients) == 3 |
| assert provider._key_index == 0 |
| |
| mock_responses = [MagicMock() for _ in range(3)] |
| mock_responses[1].text = "Hello from rotated key!" |
| |
| |
| mock_gen_0 = AsyncMock(side_effect=Exception("429 ResourceExhausted")) |
| mock_gen_1 = AsyncMock(return_value=mock_responses[1]) |
| mock_gen_2 = AsyncMock() |
| |
| with patch.object(provider._clients[0].aio.models, "generate_content", mock_gen_0), \ |
| patch.object(provider._clients[1].aio.models, "generate_content", mock_gen_1), \ |
| patch.object(provider._clients[2].aio.models, "generate_content", mock_gen_2): |
| |
| result = await provider.generate_text(prompt="Say hello") |
| |
| assert result == "Hello from rotated key!" |
| mock_gen_0.assert_called_once() |
| mock_gen_1.assert_called_once() |
| mock_gen_2.assert_not_called() |
| |
| assert provider._key_index == 1 |
|
|
|
|
| @pytest.mark.asyncio |
| async def test_gemini_all_keys_cooldown(monkeypatch): |
| monkeypatch.setattr(settings, "gemini_api_key", "") |
| monkeypatch.setattr(settings, "gemini_api_key_1", "key1") |
| monkeypatch.setattr(settings, "gemini_api_key_2", "key2") |
| monkeypatch.setattr(settings, "gemini_api_key_3", "") |
| |
| provider = GeminiProvider(model_name="gemini-2.5-flash") |
| |
| import time |
| now = time.time() |
| |
| mock_response = MagicMock() |
| mock_response.text = "Hello after cooldown!" |
| mock_gen = AsyncMock(return_value=mock_response) |
| |
| sleep_calls = [] |
| async def mock_sleep(seconds): |
| sleep_calls.append(seconds) |
| |
| with patch("asyncio.sleep", mock_sleep), \ |
| patch.object(provider._clients[0].aio.models, "generate_content", mock_gen), \ |
| patch.object(provider._clients[1].aio.models, "generate_content", mock_gen): |
| |
| |
| provider._cooldown_until[0] = now + 10.0 |
| provider._cooldown_until[1] = now + 20.0 |
| |
| |
| |
| |
| |
| |
| |
| |
| time_values = [now, now, now + 15.0] |
| time_call_count = 0 |
| def mock_time(): |
| nonlocal time_call_count |
| val = time_values[min(time_call_count, len(time_values) - 1)] |
| time_call_count += 1 |
| return val |
| |
| with patch("time.time", mock_time): |
| result = await provider.generate_text(prompt="Say hello") |
| |
| assert result == "Hello after cooldown!" |
| assert len(sleep_calls) >= 1 |
| assert sleep_calls[0] >= 9.0 |
|
|
|
|
|
|