RSCE / tests /test_gemini.py
LP1205's picture
Deploy RSCE backend to Hugging Face Space
5e952bb
Raw
History Blame Contribute Delete
5.35 kB
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 object returned by the client
mock_response = MagicMock()
mock_response.text = '{"name": "test_name", "score": 42}'
# client.aio.models.generate_content is an async method
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):
# Mock settings to return three keys
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 first client to raise a 429 rate limit error
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):
# Set cooldowns explicitly to be ahead of 'now'
provider._cooldown_until[0] = now + 10.0
provider._cooldown_until[1] = now + 20.0
# Patch time.time to simulate passage of time or just let it sleep
# Since we mock asyncio.sleep to not block, we can simulate the cooldown check.
# But wait! If time.time() doesn't change, the while loop will check cooldown,
# see both on cooldown, sleep, then check again, and since time didn't change,
# it will sleep again indefinitely!
# To prevent this infinite loop in test, we can make time.time() increment
# every time it is called, or patch time.time() to return increasing values!
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