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>
225 lines
6.9 KiB
Python
225 lines
6.9 KiB
Python
"""
|
|
Tests for the ProviderRegistry singleton.
|
|
"""
|
|
|
|
import pytest
|
|
import threading
|
|
|
|
from services.providers.registry import ProviderRegistry, get_registry
|
|
from services.providers.base import TranslationProvider
|
|
from services.providers.schemas import TranslationRequest, TranslationResponse
|
|
|
|
|
|
class MockProvider(TranslationProvider):
|
|
"""Mock provider for testing."""
|
|
|
|
def __init__(self, name: str, available: bool = True):
|
|
self._name = name
|
|
self._available = available
|
|
|
|
def translate_text(self, request: TranslationRequest) -> TranslationResponse:
|
|
return TranslationResponse(
|
|
translated_text=f"[{self._name}] {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 TestProviderRegistry:
|
|
"""Tests for the ProviderRegistry class."""
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def setup_registry(self):
|
|
"""Clear registry before each test."""
|
|
registry = get_registry()
|
|
registry.clear()
|
|
yield
|
|
registry.clear()
|
|
|
|
def test_singleton_pattern(self):
|
|
"""Test that ProviderRegistry is a singleton."""
|
|
registry1 = ProviderRegistry()
|
|
registry2 = ProviderRegistry()
|
|
|
|
assert registry1 is registry2
|
|
|
|
def test_get_registry_function(self):
|
|
"""Test get_registry returns the singleton."""
|
|
registry1 = get_registry()
|
|
registry2 = get_registry()
|
|
|
|
assert registry1 is registry2
|
|
|
|
def test_register_and_get(self):
|
|
"""Test registering and retrieving a provider."""
|
|
registry = get_registry()
|
|
provider = MockProvider("test")
|
|
|
|
registry.register("test", provider)
|
|
retrieved = registry.get("test")
|
|
|
|
assert retrieved is provider
|
|
assert retrieved.get_name() == "test"
|
|
|
|
def test_get_nonexistent_provider(self):
|
|
"""Test getting a provider that doesn't exist."""
|
|
registry = get_registry()
|
|
|
|
result = registry.get("nonexistent")
|
|
|
|
assert result is None
|
|
|
|
def test_unregister(self):
|
|
"""Test unregistering a provider."""
|
|
registry = get_registry()
|
|
provider = MockProvider("test")
|
|
|
|
registry.register("test", provider)
|
|
assert registry.get("test") is not None
|
|
|
|
result = registry.unregister("test")
|
|
|
|
assert result is True
|
|
assert registry.get("test") is None
|
|
|
|
def test_unregister_nonexistent(self):
|
|
"""Test unregistering a provider that doesn't exist."""
|
|
registry = get_registry()
|
|
|
|
result = registry.unregister("nonexistent")
|
|
|
|
assert result is False
|
|
|
|
def test_list_all(self):
|
|
"""Test listing all registered providers."""
|
|
registry = get_registry()
|
|
registry.register("google", MockProvider("google"))
|
|
registry.register("deepl", MockProvider("deepl"))
|
|
registry.register("openai", MockProvider("openai"))
|
|
|
|
names = registry.list_all()
|
|
|
|
assert len(names) == 3
|
|
assert "google" in names
|
|
assert "deepl" in names
|
|
assert "openai" in names
|
|
|
|
def test_list_available(self):
|
|
"""Test listing only available providers."""
|
|
registry = get_registry()
|
|
registry.register("available1", MockProvider("available1", available=True))
|
|
registry.register("available2", MockProvider("available2", available=True))
|
|
registry.register("unavailable", MockProvider("unavailable", available=False))
|
|
|
|
names = registry.list_available()
|
|
|
|
assert len(names) == 2
|
|
assert "available1" in names
|
|
assert "available2" in names
|
|
assert "unavailable" not in names
|
|
|
|
def test_get_first_available(self):
|
|
"""Test getting first available provider from a list."""
|
|
registry = get_registry()
|
|
registry.register("google", MockProvider("google", available=True))
|
|
registry.register("deepl", MockProvider("deepl", available=True))
|
|
registry.register("openai", MockProvider("openai", available=False))
|
|
|
|
provider = registry.get_first_available(["openai", "deepl", "google"])
|
|
|
|
assert provider is not None
|
|
assert provider.get_name() == "deepl"
|
|
|
|
def test_get_first_available_all_unavailable(self):
|
|
"""Test getting first available when all are unavailable."""
|
|
registry = get_registry()
|
|
registry.register("google", MockProvider("google", available=False))
|
|
registry.register("deepl", MockProvider("deepl", available=False))
|
|
|
|
provider = registry.get_first_available(["google", "deepl"])
|
|
|
|
assert provider is None
|
|
|
|
def test_get_first_available_not_registered(self):
|
|
"""Test getting first available when provider not registered."""
|
|
registry = get_registry()
|
|
|
|
provider = registry.get_first_available(["google", "deepl"])
|
|
|
|
assert provider is None
|
|
|
|
def test_clear(self):
|
|
"""Test clearing all providers."""
|
|
registry = get_registry()
|
|
registry.register("google", MockProvider("google"))
|
|
registry.register("deepl", MockProvider("deepl"))
|
|
|
|
registry.clear()
|
|
|
|
assert len(registry) == 0
|
|
assert registry.list_all() == []
|
|
|
|
def test_len(self):
|
|
"""Test __len__ method."""
|
|
registry = get_registry()
|
|
|
|
assert len(registry) == 0
|
|
|
|
registry.register("google", MockProvider("google"))
|
|
assert len(registry) == 1
|
|
|
|
registry.register("deepl", MockProvider("deepl"))
|
|
assert len(registry) == 2
|
|
|
|
def test_contains(self):
|
|
"""Test __contains__ method."""
|
|
registry = get_registry()
|
|
registry.register("google", MockProvider("google"))
|
|
|
|
assert "google" in registry
|
|
assert "deepl" not in registry
|
|
|
|
def test_thread_safety(self):
|
|
"""Test that registry operations are thread-safe."""
|
|
registry = get_registry()
|
|
registry.clear()
|
|
errors = []
|
|
|
|
def register_providers(prefix: str, count: int):
|
|
try:
|
|
for i in range(count):
|
|
registry.register(f"{prefix}_{i}", MockProvider(f"{prefix}_{i}"))
|
|
except Exception as e:
|
|
errors.append(e)
|
|
|
|
threads = [
|
|
threading.Thread(target=register_providers, args=(f"thread_{t}", 10))
|
|
for t in range(5)
|
|
]
|
|
|
|
for t in threads:
|
|
t.start()
|
|
for t in threads:
|
|
t.join()
|
|
|
|
assert len(errors) == 0
|
|
assert len(registry) == 50
|
|
|
|
def test_overwrite_registration(self):
|
|
"""Test that registering with same name overwrites."""
|
|
registry = get_registry()
|
|
provider1 = MockProvider("test1")
|
|
provider2 = MockProvider("test2")
|
|
|
|
registry.register("test", provider1)
|
|
assert registry.get("test").get_name() == "test1"
|
|
|
|
registry.register("test", provider2)
|
|
assert registry.get("test").get_name() == "test2"
|