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:
182
tests/test_providers/test_base.py
Normal file
182
tests/test_providers/test_base.py
Normal file
@@ -0,0 +1,182 @@
|
||||
"""
|
||||
Tests for the TranslationProvider base class and schemas.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from abc import ABC
|
||||
|
||||
from services.providers.base import TranslationProvider
|
||||
from services.providers.schemas import (
|
||||
TranslationRequest,
|
||||
TranslationResponse,
|
||||
BatchTranslationRequest,
|
||||
BatchTranslationResponse,
|
||||
ProviderHealthStatus,
|
||||
)
|
||||
|
||||
|
||||
class ConcreteTranslationProvider(TranslationProvider):
|
||||
"""Concrete implementation for testing abstract base class."""
|
||||
|
||||
def __init__(self, name: str = "test", available: bool = True):
|
||||
self._name = name
|
||||
self._available = available
|
||||
|
||||
def translate_text(self, request: TranslationRequest) -> TranslationResponse:
|
||||
return TranslationResponse(
|
||||
translated_text=f"[{request.target_language}] {request.text}",
|
||||
provider_name=self._name,
|
||||
from_cache=False,
|
||||
)
|
||||
|
||||
def get_name(self) -> str:
|
||||
return self._name
|
||||
|
||||
def is_available(self) -> bool:
|
||||
return self._available
|
||||
|
||||
|
||||
class TestSchemas:
|
||||
"""Tests for Pydantic schema models."""
|
||||
|
||||
def test_translation_request_defaults(self):
|
||||
"""Test TranslationRequest with default values."""
|
||||
request = TranslationRequest(text="Hello", target_language="fr")
|
||||
assert request.text == "Hello"
|
||||
assert request.target_language == "fr"
|
||||
assert request.source_language == "auto"
|
||||
|
||||
def test_translation_request_custom_source(self):
|
||||
"""Test TranslationRequest with custom source language."""
|
||||
request = TranslationRequest(
|
||||
text="Hello", target_language="fr", source_language="en"
|
||||
)
|
||||
assert request.source_language == "en"
|
||||
|
||||
def test_translation_response_defaults(self):
|
||||
"""Test TranslationResponse with default values."""
|
||||
response = TranslationResponse(translated_text="Bonjour", provider_name="test")
|
||||
assert response.translated_text == "Bonjour"
|
||||
assert response.provider_name == "test"
|
||||
assert response.from_cache is False
|
||||
assert response.source_language is None
|
||||
assert response.error is None
|
||||
assert response.success is True
|
||||
|
||||
def test_translation_response_with_error(self):
|
||||
"""Test TranslationResponse with error."""
|
||||
response = TranslationResponse(
|
||||
translated_text="Hello",
|
||||
provider_name="test",
|
||||
error="API Error",
|
||||
)
|
||||
assert response.error == "API Error"
|
||||
assert response.success is False
|
||||
|
||||
def test_translation_request_invalid_language(self):
|
||||
"""Test TranslationRequest rejects invalid language codes."""
|
||||
with pytest.raises(ValueError):
|
||||
TranslationRequest(text="Hello", target_language="invalid123")
|
||||
|
||||
def test_batch_translation_request(self):
|
||||
"""Test BatchTranslationRequest."""
|
||||
request = BatchTranslationRequest(
|
||||
texts=["Hello", "World"], target_language="fr"
|
||||
)
|
||||
assert len(request.texts) == 2
|
||||
assert request.source_language == "auto"
|
||||
|
||||
def test_batch_translation_response(self):
|
||||
"""Test BatchTranslationResponse."""
|
||||
response = BatchTranslationResponse(
|
||||
translated_texts=["Bonjour", "Monde"],
|
||||
provider_name="test",
|
||||
from_cache_count=1,
|
||||
)
|
||||
assert len(response.translated_texts) == 2
|
||||
assert response.from_cache_count == 1
|
||||
|
||||
def test_provider_health_status(self):
|
||||
"""Test ProviderHealthStatus."""
|
||||
status = ProviderHealthStatus(name="test", available=True, latency_ms=50.5)
|
||||
assert status.name == "test"
|
||||
assert status.available is True
|
||||
assert status.latency_ms == 50.5
|
||||
assert status.error is None
|
||||
|
||||
def test_provider_health_status_with_error(self):
|
||||
"""Test ProviderHealthStatus with error."""
|
||||
status = ProviderHealthStatus(
|
||||
name="test", available=False, error="Connection refused"
|
||||
)
|
||||
assert status.available is False
|
||||
assert status.error == "Connection refused"
|
||||
|
||||
|
||||
class TestTranslationProviderBaseClass:
|
||||
"""Tests for the TranslationProvider abstract base class."""
|
||||
|
||||
def test_is_abstract(self):
|
||||
"""Test that TranslationProvider cannot be instantiated directly."""
|
||||
assert issubclass(TranslationProvider, ABC)
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
TranslationProvider()
|
||||
|
||||
def test_concrete_implementation_works(self):
|
||||
"""Test that a concrete implementation can be instantiated."""
|
||||
provider = ConcreteTranslationProvider()
|
||||
assert provider.get_name() == "test"
|
||||
assert provider.is_available() is True
|
||||
|
||||
def test_translate_text(self):
|
||||
"""Test translate_text method."""
|
||||
provider = ConcreteTranslationProvider()
|
||||
request = TranslationRequest(text="Hello", target_language="fr")
|
||||
response = provider.translate_text(request)
|
||||
|
||||
assert response.translated_text == "[fr] Hello"
|
||||
assert response.provider_name == "test"
|
||||
|
||||
def test_translate_batch_default_implementation(self):
|
||||
"""Test default translate_batch implementation."""
|
||||
provider = ConcreteTranslationProvider()
|
||||
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 == "[fr] Hello"
|
||||
assert responses[1].translated_text == "[fr] World"
|
||||
|
||||
def test_health_check_available(self):
|
||||
"""Test health_check for available provider."""
|
||||
provider = ConcreteTranslationProvider(available=True)
|
||||
status = provider.health_check()
|
||||
|
||||
assert status.name == "test"
|
||||
assert status.available is True
|
||||
assert status.error is None
|
||||
assert status.latency_ms is not None
|
||||
|
||||
def test_health_check_unavailable(self):
|
||||
"""Test health_check for unavailable provider."""
|
||||
provider = ConcreteTranslationProvider(available=False)
|
||||
status = provider.health_check()
|
||||
|
||||
assert status.available is False
|
||||
assert status.error == "Provider not available"
|
||||
|
||||
def test_get_name_abstract(self):
|
||||
"""Test that get_name is abstract."""
|
||||
assert "get_name" in TranslationProvider.__abstractmethods__
|
||||
|
||||
def test_is_available_abstract(self):
|
||||
"""Test that is_available is abstract."""
|
||||
assert "is_available" in TranslationProvider.__abstractmethods__
|
||||
|
||||
def test_translate_text_abstract(self):
|
||||
"""Test that translate_text is abstract."""
|
||||
assert "translate_text" in TranslationProvider.__abstractmethods__
|
||||
Reference in New Issue
Block a user