feat: production deployment - full update with providers, admin, glossaries, pricing, tests
Major changes across backend, frontend, infrastructure: - Provider system with model selection (Google, DeepL, OpenAI, Ollama, Google Cloud) - Admin panel: user management, pricing, settings - Glossary system with CSV import/export - Subscription and tier quota management - Security hardening (rate limiting, API key auth, path traversal fixes) - Docker compose for dev, prod, and IONOS deployment - Alembic migrations for new tables - Frontend: dashboard, pricing page, landing page, i18n (en/fr) - Test suite and verification scripts Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
180
tests/test_providers/test_google_provider.py
Normal file
180
tests/test_providers/test_google_provider.py
Normal file
@@ -0,0 +1,180 @@
|
||||
"""
|
||||
Tests for the GoogleTranslationProvider.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
from services.providers.google_provider import (
|
||||
GoogleTranslationProvider,
|
||||
get_google_provider,
|
||||
)
|
||||
from services.providers.schemas import TranslationRequest, TranslationResponse
|
||||
|
||||
|
||||
class TestGoogleTranslationProvider:
|
||||
"""Tests for GoogleTranslationProvider."""
|
||||
|
||||
@pytest.fixture
|
||||
def provider(self):
|
||||
"""Create a Google provider instance."""
|
||||
return GoogleTranslationProvider(use_cache=False)
|
||||
|
||||
@pytest.fixture
|
||||
def provider_with_cache(self):
|
||||
"""Create a Google provider with caching enabled."""
|
||||
return GoogleTranslationProvider(use_cache=True)
|
||||
|
||||
def test_get_name(self, provider):
|
||||
"""Test provider name."""
|
||||
assert provider.get_name() == "google"
|
||||
|
||||
def test_is_available(self, provider):
|
||||
"""Test availability check."""
|
||||
result = provider.is_available()
|
||||
assert isinstance(result, bool)
|
||||
|
||||
def test_translate_text_empty(self, provider):
|
||||
"""Test translating empty text."""
|
||||
request = TranslationRequest(text="", target_language="fr")
|
||||
response = provider.translate_text(request)
|
||||
|
||||
assert response.translated_text == ""
|
||||
assert response.provider_name == "google"
|
||||
assert response.from_cache is False
|
||||
|
||||
def test_translate_text_whitespace(self, provider):
|
||||
"""Test translating whitespace-only text."""
|
||||
request = TranslationRequest(text=" ", target_language="fr")
|
||||
response = provider.translate_text(request)
|
||||
|
||||
assert response.translated_text == " "
|
||||
|
||||
@patch(
|
||||
"services.providers.google_provider.GoogleTranslationProvider._get_translator"
|
||||
)
|
||||
def test_translate_text_success(self, mock_get_translator, provider):
|
||||
"""Test successful translation."""
|
||||
mock_translator = MagicMock()
|
||||
mock_translator.translate.return_value = "Bonjour"
|
||||
mock_get_translator.return_value = mock_translator
|
||||
|
||||
request = TranslationRequest(text="Hello", target_language="fr")
|
||||
response = provider.translate_text(request)
|
||||
|
||||
assert response.translated_text == "Bonjour"
|
||||
assert response.provider_name == "google"
|
||||
assert response.from_cache is False
|
||||
|
||||
@patch(
|
||||
"services.providers.google_provider.GoogleTranslationProvider._get_translator"
|
||||
)
|
||||
def test_translate_text_with_source_language(self, mock_get_translator, provider):
|
||||
"""Test translation with explicit source language."""
|
||||
mock_translator = MagicMock()
|
||||
mock_translator.translate.return_value = "Bonjour"
|
||||
mock_get_translator.return_value = mock_translator
|
||||
|
||||
request = TranslationRequest(
|
||||
text="Hello", target_language="fr", source_language="en"
|
||||
)
|
||||
response = provider.translate_text(request)
|
||||
|
||||
mock_get_translator.assert_called_once_with("en", "fr")
|
||||
assert response.translated_text == "Bonjour"
|
||||
|
||||
@patch(
|
||||
"services.providers.google_provider.GoogleTranslationProvider._get_translator"
|
||||
)
|
||||
def test_translate_text_error_fallback(self, mock_get_translator, provider):
|
||||
"""Test that translation errors return original text and structured error (Story 2.2)."""
|
||||
mock_get_translator.side_effect = Exception("API Error")
|
||||
|
||||
request = TranslationRequest(text="Hello", target_language="fr")
|
||||
response = provider.translate_text(request)
|
||||
|
||||
assert response.translated_text == "Hello"
|
||||
assert response.provider_name == "google"
|
||||
assert response.error is not None
|
||||
assert response.error_code is not None
|
||||
|
||||
def test_translate_batch_empty(self, provider):
|
||||
"""Test batch translation with empty list."""
|
||||
responses = provider.translate_batch([])
|
||||
assert responses == []
|
||||
|
||||
@patch.object(GoogleTranslationProvider, "translate_text")
|
||||
def test_translate_batch(self, mock_translate, provider):
|
||||
"""Test batch translation."""
|
||||
mock_translate.side_effect = [
|
||||
TranslationResponse(translated_text="Bonjour", provider_name="google"),
|
||||
TranslationResponse(translated_text="Monde", provider_name="google"),
|
||||
]
|
||||
|
||||
requests = [
|
||||
TranslationRequest(text="Hello", target_language="fr"),
|
||||
TranslationRequest(text="World", target_language="fr"),
|
||||
]
|
||||
responses = provider.translate_batch(requests)
|
||||
|
||||
assert len(responses) == 2
|
||||
assert responses[0].translated_text == "Bonjour"
|
||||
assert responses[1].translated_text == "Monde"
|
||||
|
||||
def test_health_check(self, provider):
|
||||
"""Test health check."""
|
||||
status = provider.health_check()
|
||||
|
||||
assert status.name == "google"
|
||||
assert isinstance(status.available, bool)
|
||||
assert status.latency_ms is not None
|
||||
|
||||
def test_get_google_provider_singleton(self):
|
||||
"""Test that get_google_provider returns same instance."""
|
||||
provider1 = get_google_provider()
|
||||
provider2 = get_google_provider()
|
||||
|
||||
assert provider1 is provider2
|
||||
|
||||
|
||||
class TestGoogleProviderCaching:
|
||||
"""Tests for Google provider caching functionality."""
|
||||
|
||||
@pytest.fixture
|
||||
def mock_cache(self):
|
||||
"""Create a mock cache."""
|
||||
cache = MagicMock()
|
||||
cache.get.return_value = None
|
||||
return cache
|
||||
|
||||
def test_cache_hit(self, mock_cache):
|
||||
"""Test that cache hits return cached result."""
|
||||
mock_cache.get.return_value = "Cached Translation"
|
||||
|
||||
provider = GoogleTranslationProvider(use_cache=True)
|
||||
provider._cache = mock_cache
|
||||
|
||||
request = TranslationRequest(text="Hello", target_language="fr")
|
||||
response = provider.translate_text(request)
|
||||
|
||||
assert response.translated_text == "Cached Translation"
|
||||
assert response.from_cache is True
|
||||
|
||||
@patch(
|
||||
"services.providers.google_provider.GoogleTranslationProvider._get_translator"
|
||||
)
|
||||
def test_cache_set_on_miss(self, mock_get_translator, mock_cache):
|
||||
"""Test that translations are cached on miss."""
|
||||
mock_translator = MagicMock()
|
||||
mock_translator.translate.return_value = "Bonjour"
|
||||
mock_get_translator.return_value = mock_translator
|
||||
|
||||
provider = GoogleTranslationProvider(use_cache=True)
|
||||
provider._cache = mock_cache
|
||||
|
||||
request = TranslationRequest(text="Hello", target_language="fr")
|
||||
provider.translate_text(request)
|
||||
|
||||
mock_cache.set.assert_called_once_with(
|
||||
"Hello", "fr", "auto", "google", "Bonjour"
|
||||
)
|
||||
Reference in New Issue
Block a user