refactor(providers): address PR #772 review round 3 — hermetic test, lazy logging, defensive-guard tests
- test_registry.py: stub `mistralai.client.Mistral` in `test_registry_mistral_wins_over_ollama`, mirroring the sibling picker test, so the test doesn't depend on the SDK accepting arbitrary keys. - openai.py: convert remaining f-string `logger.info(...)` calls to lazy `%s` formatting, aligning with the pattern in mistral.py and the repo's logging convention. - test_mistral.py: add four tests covering the defensive RuntimeError guards in `embed()` and `_embed_batch_request()` — empty response.data, single null embedding, batch null embedding, and count-mismatch. - docs/configuration.md: add `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` rows to the env-var reference table; they were already mentioned in prose but missing from the table. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
20f1770794
commit
adcf13f082
@@ -200,6 +200,61 @@ async def test_mistral_base_url_passed_to_sdk(mocker):
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
async def test_mistral_embed_raises_on_empty_response_data(mock_mistral_client):
|
||||
"""embed(): empty response.data triggers the defensive RuntimeError guard."""
|
||||
empty_response = MagicMock()
|
||||
empty_response.data = []
|
||||
mock_mistral_client.embeddings.create_async = AsyncMock(return_value=empty_response)
|
||||
|
||||
provider = MistralProvider(api_key="test-key", embedding_model="mistral-embed")
|
||||
with pytest.raises(RuntimeError, match="returned no embedding"):
|
||||
await provider.embed("test")
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
async def test_mistral_embed_raises_on_null_embedding(mock_mistral_client):
|
||||
"""embed(): a single response item with embedding=None is rejected."""
|
||||
null_item = MagicMock()
|
||||
null_item.embedding = None
|
||||
null_item.index = 0
|
||||
null_response = MagicMock()
|
||||
null_response.data = [null_item]
|
||||
mock_mistral_client.embeddings.create_async = AsyncMock(return_value=null_response)
|
||||
|
||||
provider = MistralProvider(api_key="test-key", embedding_model="mistral-embed")
|
||||
with pytest.raises(RuntimeError, match="returned no embedding"):
|
||||
await provider.embed("test")
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
async def test_mistral_batch_raises_on_null_embedding(mock_mistral_client):
|
||||
"""_embed_batch_request: a null embedding inside a batch raises explicitly."""
|
||||
good = _make_data([0.1, 0.2], 0)
|
||||
bad = MagicMock()
|
||||
bad.embedding = None
|
||||
bad.index = 1
|
||||
response = MagicMock()
|
||||
response.data = [good, bad]
|
||||
mock_mistral_client.embeddings.create_async = AsyncMock(return_value=response)
|
||||
|
||||
provider = MistralProvider(api_key="test-key", embedding_model="mistral-embed")
|
||||
with pytest.raises(RuntimeError, match="null embedding"):
|
||||
await provider.embed_batch(["a", "b"])
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
async def test_mistral_batch_raises_on_count_mismatch(mock_mistral_client):
|
||||
"""_embed_batch_request: fewer embeddings returned than inputs sent."""
|
||||
# Two inputs sent, one embedding returned.
|
||||
response = _make_response([[0.1, 0.2]])
|
||||
mock_mistral_client.embeddings.create_async = AsyncMock(return_value=response)
|
||||
|
||||
provider = MistralProvider(api_key="test-key", embedding_model="mistral-embed")
|
||||
with pytest.raises(RuntimeError, match="returned 1 embeddings for 2 inputs"):
|
||||
await provider.embed_batch(["a", "b"])
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_mistral_is_rate_limit_predicate():
|
||||
"""_is_rate_limit returns True only for SDKErrors with status_code == 429."""
|
||||
|
||||
@@ -108,8 +108,11 @@ def test_registry_openai_wins_over_mistral_and_ollama(clean_provider_env):
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_registry_mistral_wins_over_ollama(clean_provider_env):
|
||||
def test_registry_mistral_wins_over_ollama(clean_provider_env, mocker):
|
||||
"""Mistral takes priority over Ollama when both are configured."""
|
||||
# Stub the Mistral SDK constructor for the same reason as the sibling
|
||||
# picker test — keeps the registry test independent of SDK key validation.
|
||||
mocker.patch("nextcloud_mcp_server.providers.mistral.Mistral")
|
||||
clean_provider_env.setenv("MISTRAL_API_KEY", "mistral-key")
|
||||
clean_provider_env.setenv("OLLAMA_BASE_URL", "http://localhost:11434")
|
||||
_reload_config()
|
||||
|
||||
Reference in New Issue
Block a user