feat: update test mocks for google-genai SDK (refs #235)
Replace engine._model/engine._generation_config mocks with engine._client/engine._model_name. Update sys.modules patches from vertexai to google.genai. Remove dead if-False branch. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
Covers: GeminiEngine initialization, PDF size validation,
|
Covers: GeminiEngine initialization, PDF size validation,
|
||||||
successful extraction, empty results, and error handling.
|
successful extraction, empty results, and error handling.
|
||||||
All Vertex AI SDK calls are mocked.
|
All google-genai SDK calls are mocked.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
from unittest.mock import MagicMock, patch, PropertyMock
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@@ -156,22 +156,16 @@ class TestExtractMaintenance:
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
mock_model = MagicMock()
|
mock_client = MagicMock()
|
||||||
mock_model.generate_content.return_value = _make_gemini_response(schedule)
|
mock_client.models.generate_content.return_value = _make_gemini_response(schedule)
|
||||||
|
|
||||||
with (
|
with patch.dict("sys.modules", {
|
||||||
patch(
|
"google.genai": MagicMock(),
|
||||||
"app.engines.gemini_engine.importlib_vertex_ai"
|
"google.genai.types": MagicMock(),
|
||||||
) if False else patch.dict("sys.modules", {
|
}):
|
||||||
"google.cloud": MagicMock(),
|
|
||||||
"google.cloud.aiplatform": MagicMock(),
|
|
||||||
"vertexai": MagicMock(),
|
|
||||||
"vertexai.generative_models": MagicMock(),
|
|
||||||
}),
|
|
||||||
):
|
|
||||||
engine = GeminiEngine()
|
engine = GeminiEngine()
|
||||||
engine._model = mock_model
|
engine._client = mock_client
|
||||||
engine._generation_config = MagicMock()
|
engine._model_name = "gemini-2.5-flash"
|
||||||
|
|
||||||
result = engine.extract_maintenance(_make_pdf_bytes())
|
result = engine.extract_maintenance(_make_pdf_bytes())
|
||||||
|
|
||||||
@@ -200,12 +194,12 @@ class TestExtractMaintenance:
|
|||||||
mock_settings.vertex_ai_location = "us-central1"
|
mock_settings.vertex_ai_location = "us-central1"
|
||||||
mock_settings.gemini_model = "gemini-2.5-flash"
|
mock_settings.gemini_model = "gemini-2.5-flash"
|
||||||
|
|
||||||
mock_model = MagicMock()
|
mock_client = MagicMock()
|
||||||
mock_model.generate_content.return_value = _make_gemini_response([])
|
mock_client.models.generate_content.return_value = _make_gemini_response([])
|
||||||
|
|
||||||
engine = GeminiEngine()
|
engine = GeminiEngine()
|
||||||
engine._model = mock_model
|
engine._client = mock_client
|
||||||
engine._generation_config = MagicMock()
|
engine._model_name = "gemini-2.5-flash"
|
||||||
|
|
||||||
result = engine.extract_maintenance(_make_pdf_bytes())
|
result = engine.extract_maintenance(_make_pdf_bytes())
|
||||||
|
|
||||||
@@ -223,12 +217,12 @@ class TestExtractMaintenance:
|
|||||||
|
|
||||||
schedule = [{"serviceName": "Brake Fluid Replacement"}]
|
schedule = [{"serviceName": "Brake Fluid Replacement"}]
|
||||||
|
|
||||||
mock_model = MagicMock()
|
mock_client = MagicMock()
|
||||||
mock_model.generate_content.return_value = _make_gemini_response(schedule)
|
mock_client.models.generate_content.return_value = _make_gemini_response(schedule)
|
||||||
|
|
||||||
engine = GeminiEngine()
|
engine = GeminiEngine()
|
||||||
engine._model = mock_model
|
engine._client = mock_client
|
||||||
engine._generation_config = MagicMock()
|
engine._model_name = "gemini-2.5-flash"
|
||||||
|
|
||||||
result = engine.extract_maintenance(_make_pdf_bytes())
|
result = engine.extract_maintenance(_make_pdf_bytes())
|
||||||
|
|
||||||
@@ -264,7 +258,8 @@ class TestErrorHandling:
|
|||||||
with (
|
with (
|
||||||
patch("app.engines.gemini_engine.settings") as mock_settings,
|
patch("app.engines.gemini_engine.settings") as mock_settings,
|
||||||
patch.dict("sys.modules", {
|
patch.dict("sys.modules", {
|
||||||
"google.cloud.aiplatform": None,
|
"google": None,
|
||||||
|
"google.genai": None,
|
||||||
}),
|
}),
|
||||||
):
|
):
|
||||||
mock_settings.google_vision_key_path = "/fake/creds.json"
|
mock_settings.google_vision_key_path = "/fake/creds.json"
|
||||||
@@ -283,12 +278,12 @@ class TestErrorHandling:
|
|||||||
mock_settings.vertex_ai_location = "us-central1"
|
mock_settings.vertex_ai_location = "us-central1"
|
||||||
mock_settings.gemini_model = "gemini-2.5-flash"
|
mock_settings.gemini_model = "gemini-2.5-flash"
|
||||||
|
|
||||||
mock_model = MagicMock()
|
mock_client = MagicMock()
|
||||||
mock_model.generate_content.side_effect = RuntimeError("API quota exceeded")
|
mock_client.models.generate_content.side_effect = RuntimeError("API quota exceeded")
|
||||||
|
|
||||||
engine = GeminiEngine()
|
engine = GeminiEngine()
|
||||||
engine._model = mock_model
|
engine._client = mock_client
|
||||||
engine._generation_config = MagicMock()
|
engine._model_name = "gemini-2.5-flash"
|
||||||
|
|
||||||
with pytest.raises(GeminiProcessingError, match="maintenance extraction failed"):
|
with pytest.raises(GeminiProcessingError, match="maintenance extraction failed"):
|
||||||
engine.extract_maintenance(_make_pdf_bytes())
|
engine.extract_maintenance(_make_pdf_bytes())
|
||||||
@@ -307,12 +302,12 @@ class TestErrorHandling:
|
|||||||
mock_response = MagicMock()
|
mock_response = MagicMock()
|
||||||
mock_response.text = "not valid json {{"
|
mock_response.text = "not valid json {{"
|
||||||
|
|
||||||
mock_model = MagicMock()
|
mock_client = MagicMock()
|
||||||
mock_model.generate_content.return_value = mock_response
|
mock_client.models.generate_content.return_value = mock_response
|
||||||
|
|
||||||
engine = GeminiEngine()
|
engine = GeminiEngine()
|
||||||
engine._model = mock_model
|
engine._client = mock_client
|
||||||
engine._generation_config = MagicMock()
|
engine._model_name = "gemini-2.5-flash"
|
||||||
|
|
||||||
with pytest.raises(GeminiProcessingError, match="invalid JSON"):
|
with pytest.raises(GeminiProcessingError, match="invalid JSON"):
|
||||||
engine.extract_maintenance(_make_pdf_bytes())
|
engine.extract_maintenance(_make_pdf_bytes())
|
||||||
@@ -322,32 +317,32 @@ class TestErrorHandling:
|
|||||||
|
|
||||||
|
|
||||||
class TestLazyInitialization:
|
class TestLazyInitialization:
|
||||||
"""Verify the model is not created until first use."""
|
"""Verify the client is not created until first use."""
|
||||||
|
|
||||||
def test_model_is_none_after_construction(self):
|
def test_client_is_none_after_construction(self):
|
||||||
"""GeminiEngine should not initialize the model in __init__."""
|
"""GeminiEngine should not initialize the client in __init__."""
|
||||||
engine = GeminiEngine()
|
engine = GeminiEngine()
|
||||||
assert engine._model is None
|
assert engine._client is None
|
||||||
|
|
||||||
@patch("app.engines.gemini_engine.settings")
|
@patch("app.engines.gemini_engine.settings")
|
||||||
@patch("app.engines.gemini_engine.os.path.isfile", return_value=True)
|
@patch("app.engines.gemini_engine.os.path.isfile", return_value=True)
|
||||||
def test_model_reused_on_second_call(self, mock_isfile, mock_settings):
|
def test_client_reused_on_second_call(self, mock_isfile, mock_settings):
|
||||||
"""Once initialized, the same model instance is reused."""
|
"""Once initialized, the same client instance is reused."""
|
||||||
mock_settings.google_vision_key_path = "/fake/creds.json"
|
mock_settings.google_vision_key_path = "/fake/creds.json"
|
||||||
mock_settings.vertex_ai_project = "test-project"
|
mock_settings.vertex_ai_project = "test-project"
|
||||||
mock_settings.vertex_ai_location = "us-central1"
|
mock_settings.vertex_ai_location = "us-central1"
|
||||||
mock_settings.gemini_model = "gemini-2.5-flash"
|
mock_settings.gemini_model = "gemini-2.5-flash"
|
||||||
|
|
||||||
schedule = [{"serviceName": "Oil Change", "intervalMiles": 5000}]
|
schedule = [{"serviceName": "Oil Change", "intervalMiles": 5000}]
|
||||||
mock_model = MagicMock()
|
mock_client = MagicMock()
|
||||||
mock_model.generate_content.return_value = _make_gemini_response(schedule)
|
mock_client.models.generate_content.return_value = _make_gemini_response(schedule)
|
||||||
|
|
||||||
engine = GeminiEngine()
|
engine = GeminiEngine()
|
||||||
engine._model = mock_model
|
engine._client = mock_client
|
||||||
engine._generation_config = MagicMock()
|
engine._model_name = "gemini-2.5-flash"
|
||||||
|
|
||||||
engine.extract_maintenance(_make_pdf_bytes())
|
engine.extract_maintenance(_make_pdf_bytes())
|
||||||
engine.extract_maintenance(_make_pdf_bytes())
|
engine.extract_maintenance(_make_pdf_bytes())
|
||||||
|
|
||||||
# Model's generate_content should have been called twice
|
# Client's generate_content should have been called twice
|
||||||
assert mock_model.generate_content.call_count == 2
|
assert mock_client.models.generate_content.call_count == 2
|
||||||
|
|||||||
Reference in New Issue
Block a user