Files
office_translator/tests/test_worker_tier_routing.py
sepehr 6c26712687
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m51s
feat(abonnements): paliers LLM Essentielle/Premium, admin modeles par forfait, statistiques revenus, emails marketing
- Gamme officielle : Essentielle (Pro) = deepseek-v4-flash, glm-5.3-flash,
  minimax-m3 ; Premium (Business) = claude-sonnet-5, deepseek-v4-pro, glm-5.3
- Routage reel : le modele employe suit le palier IA du forfait (reglages
  admin > defaut du plan), garde anti-croisement de palier
- Nouvelle page admin « Modeles & abonnements » (matrice forfaits/paliers,
  modele par defaut, catalogue OpenRouter)
- Statistiques enrichies : revenus (30 j + total), MRR estime, credits,
  liste d'attente, paliers utilises
- Nouvelle page admin « Marketing » : audiences avec compteurs, apercu,
  envoi test obligatoire, historique, desabonnement public + en-tetes
  List-Unsubscribe ; .gitignore pour les donnees d'execution
- Interface (accueil + tarifs) alignee sur la gamme, 13 langues completes
- Tests : routage par palier (fonction + tache), marketing, revenus en base
2026-09-05 12:51:49 +02:00

153 lines
5.7 KiB
Python

"""
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"