"""Tests for sibilla.ai — GeminiClient with mocked API.""" import pytest from unittest import mock from sibilla.ai import GeminiClient, SynthesisResult class TestSynthesisResult: def test_defaults(self): sr = SynthesisResult(answer="") assert sr.answer == "" assert sr.model == "" assert sr.tokens_used == 0 assert sr.error is None class TestGeminiClient: def test_no_api_key_returns_error(self): client = GeminiClient(api_key="") result = client.synthesize("system", "user") assert result.answer == "" assert "GOOGLE_API_KEY" in result.error def test_lazy_client_raises_without_key(self): client = GeminiClient(api_key="") with pytest.raises(ValueError, match="GOOGLE_API_KEY"): _ = client.client def test_synthesize_success(self): client = GeminiClient(api_key="fake-key") mock_genai = mock.MagicMock() client._client = mock_genai mock_part = mock.MagicMock() mock_part.text = "This is the answer." mock_content = mock.MagicMock() mock_content.parts = [mock_part] mock_candidate = mock.MagicMock() mock_candidate.content = mock_content mock_usage = mock.MagicMock() mock_usage.total_token_count = 42 mock_response = mock.MagicMock() mock_response.candidates = [mock_candidate] mock_response.usage_metadata = mock_usage mock_genai.models.generate_content.return_value = mock_response result = client.synthesize("system prompt", "user query") assert result.answer == "This is the answer." assert result.tokens_used == 42 assert result.model == "gemini-2.5-flash" assert result.error is None def test_synthesize_no_candidates(self): client = GeminiClient(api_key="fake-key") mock_genai = mock.MagicMock() client._client = mock_genai mock_response = mock.MagicMock() mock_response.candidates = [] mock_response.usage_metadata = None mock_genai.models.generate_content.return_value = mock_response result = client.synthesize("s", "u") assert result.answer == "" assert result.error is None def test_synthesize_api_exception(self): client = GeminiClient(api_key="fake-key") mock_genai = mock.MagicMock() client._client = mock_genai mock_genai.models.generate_content.side_effect = RuntimeError("API down") result = client.synthesize("s", "u") assert result.error == "API down" assert result.answer == "" def test_rewrite_query_no_api_key(self): client = GeminiClient(api_key="") variants = client.rewrite_query("test query", num_variants=3) assert variants == ["test query"] def test_rewrite_query_success(self): client = GeminiClient(api_key="fake-key") mock_genai = mock.MagicMock() client._client = mock_genai mock_part = mock.MagicMock() mock_part.text = "rewritten query 1\nrewritten query 2\nrewritten query 3" mock_content = mock.MagicMock() mock_content.parts = [mock_part] mock_candidate = mock.MagicMock() mock_candidate.content = mock_content mock_response = mock.MagicMock() mock_response.candidates = [mock_candidate] mock_genai.models.generate_content.return_value = mock_response variants = client.rewrite_query("original", num_variants=3) assert len(variants) == 4 assert variants[0] == "original" assert "rewritten query 1" in variants def test_rewrite_query_strips_bullets(self): client = GeminiClient(api_key="fake-key") mock_genai = mock.MagicMock() client._client = mock_genai mock_part = mock.MagicMock() mock_part.text = "- bullet query\n• dot query" mock_content = mock.MagicMock() mock_content.parts = [mock_part] mock_candidate = mock.MagicMock() mock_candidate.content = mock_content mock_response = mock.MagicMock() mock_response.candidates = [mock_candidate] mock_genai.models.generate_content.return_value = mock_response variants = client.rewrite_query("original", num_variants=2) assert "bullet query" in variants assert "dot query" in variants assert not any(v.startswith("-") for v in variants) def test_rewrite_query_exception_fallback(self): client = GeminiClient(api_key="fake-key") mock_genai = mock.MagicMock() client._client = mock_genai mock_genai.models.generate_content.side_effect = RuntimeError("boom") variants = client.rewrite_query("original", num_variants=2) assert variants == ["original"]