""" Intégration : ``_run_translation_job`` route bien le modèle par palier. Un faux fournisseur OpenAI-compatible capture le paramètre ``model`` reçu du worker ; ``load_settings`` (réglages admin) est substitué. On vérifie que : - provider="openrouter" → modèle Essentielle (réglages > plan) ; - provider="openrouter_premium" → modèle Premium ; - un ``ai_tiers.essential`` personnalisé est honoré sans redéploiement ; - un ``ai_tiers.essential`` contenant un modèle Premium est corrigé à l'exécution. """ import asyncio import pytest from pathlib import Path import routes.translate_routes as tr @pytest.fixture def minimal_xlsx(tmp_path): try: import openpyxl wb = openpyxl.Workbook() wb.active["A1"] = "Hello" p = tmp_path / "minimal.xlsx" wb.save(p) return p except ImportError: pytest.skip("openpyxl required") def _settings(ai_tiers: dict, api_key: str = "sk-or-test"): from routes.admin_routes import SettingsConfig return SettingsConfig.model_validate( { "openrouter": {"api_key": api_key}, "openrouter_premium": {"api_key": api_key}, "ai_tiers": ai_tiers, } ) def _register_job(job_id: str) -> None: tr._translation_jobs[job_id] = { "id": job_id, "status": "processing", "progress_percent": 0, "current_step": "", "provider": "openrouter", "created_at": "2026-09-05T00:00:00+00:00", } def _run_job(monkeypatch, tmp_path, minimal_xlsx, admin_settings, provider: str, user_plan: str) -> list: """Lance _run_translation_job avec des faux traducteurs/fournisseurs et renvoie la liste des modèles capturés par le faux fournisseur.""" import routes.admin_routes as admin_routes_mod import services.providers.openai_provider as openai_provider_mod captured: list = [] class FakeProvider: def __init__(self, api_key=None, model=None, base_url=None, timeout=60, **kwargs): self.model = model self._model = model captured.append(model) class FakeExcelTranslator: def __init__(self, provider=None): self.provider = provider def translate_file(self, input_path, output_path, target_lang, source_lang="auto", progress_callback=None, translate_images=False, **kwargs): out = Path(output_path) out.parent.mkdir(parents=True, exist_ok=True) out.write_bytes(minimal_xlsx.read_bytes()) def get_translation_stats(self): return {"attempted": 1, "changed": 1} monkeypatch.setattr(openai_provider_mod, "OpenAITranslationProvider", FakeProvider) monkeypatch.setattr(tr, "ExcelTranslator", FakeExcelTranslator) monkeypatch.setattr( admin_routes_mod, "load_settings", lambda: admin_settings, raising=True ) _register_job("job-test") try: asyncio.run( tr._run_translation_job( job_id="job-test", input_path=Path(minimal_xlsx), file_extension=".xlsx", target_lang="fr", source_lang="en", provider=provider, user_id=None, custom_prompt=None, glossary_id=None, prompt_id=None, webhook_url=None, user_plan=user_plan, ) ) finally: tr._translation_jobs.pop("job-test", None) return captured def test_openrouter_pro_uses_essential_tier_model(monkeypatch, tmp_path, minimal_xlsx): settings = _settings({"essential": {"models": [], "default_model": ""}, "premium": {"models": [], "default_model": ""}}) captured = _run_job(monkeypatch, tmp_path, minimal_xlsx, settings, provider="openrouter", user_plan="PlanType.PRO") assert captured, "le faux fournisseur n'a pas été instancié" assert captured[0] == "deepseek/deepseek-v4-flash" assert captured[0] not in ( "anthropic/claude-sonnet-5", "deepseek/deepseek-v4-pro", "z-ai/glm-5.3", ) def test_openrouter_premium_uses_premium_tier_model(monkeypatch, tmp_path, minimal_xlsx): settings = _settings({"essential": {"models": [], "default_model": ""}, "premium": {"models": [], "default_model": ""}}) captured = _run_job(monkeypatch, tmp_path, minimal_xlsx, settings, provider="openrouter_premium", user_plan="PlanType.BUSINESS") assert captured, "le faux fournisseur n'a pas été instancié" assert captured[0] == "anthropic/claude-sonnet-5" def test_admin_tier_default_applied_without_redeployment(monkeypatch, tmp_path, minimal_xlsx): settings = _settings({"essential": {"models": [], "default_model": "z-ai/glm-5.3-flash"}, "premium": {"models": [], "default_model": ""}}) captured = _run_job(monkeypatch, tmp_path, minimal_xlsx, settings, provider="openrouter", user_plan="PlanType.PRO") assert captured[0] == "z-ai/glm-5.3-flash" def test_essential_tier_resolving_premium_model_is_corrected(monkeypatch, tmp_path, minimal_xlsx): """Garde d'exécution : un ai_tiers essential contaminé par un modèle Premium ne doit pas partir en production — repli sur le défaut du plan.""" settings = _settings({"essential": {"models": [], "default_model": "anthropic/claude-sonnet-5"}, "premium": {"models": [], "default_model": ""}}) captured = _run_job(monkeypatch, tmp_path, minimal_xlsx, settings, provider="openrouter", user_plan="PlanType.PRO") assert captured[0] == "deepseek/deepseek-v4-flash"