"""Moteur OpenRouter : même modèle Gemini, facturé et routé par OpenRouter. Aucun test ne touche le réseau : l'envoi HTTP et la lecture de la clé sont injectés. Le repli local, lui, est déjà éprouvé dans test_gemini.py. """ import base64 import numpy as np import pytest from moteur import choisir_transcripteur from moteur.gemini import ErreurGemini, TranscripteurAvecRepli from moteur.openrouter import TranscripteurOpenRouter, lire_cle_openrouter def _reponse(texte): return {"choices": [{"message": {"role": "assistant", "content": texte}}]} def _transcripteur(envoyer, cle="cle-test"): return TranscripteurOpenRouter( "google/gemini-x", "fr", "Vocabulaire : Gitea, iCloud.", lire_cle=lambda: cle, envoyer=envoyer, ) def test_la_requete_porte_l_audio_le_modele_et_le_vocabulaire(): vu = {} def envoyer(url, entetes, corps, delai_s): vu.update(url=url, entetes=entetes, corps=corps) return _reponse("Bonjour.") _transcripteur(envoyer).transcrire(np.zeros(1600, dtype="float32")) assert vu["url"] == "https://openrouter.ai/api/v1/chat/completions" assert vu["entetes"]["Authorization"] == "Bearer cle-test" assert vu["corps"]["model"] == "google/gemini-x" systeme, utilisateur = vu["corps"]["messages"] assert "Gitea" in systeme["content"] and "iCloud" in systeme["content"] audio = next(p["input_audio"] for p in utilisateur["content"] if p["type"] == "input_audio") assert audio["format"] == "wav" assert base64.b64decode(audio["data"])[:4] == b"RIFF" def test_la_cle_ne_part_jamais_dans_l_url(): vu = {} _transcripteur(lambda url, e, c, d: vu.setdefault("url", url) and _reponse("x")) \ .transcrire(np.zeros(1600, dtype="float32")) assert "cle-test" not in vu["url"] def test_le_texte_rendu_est_nettoye(): t = _transcripteur(lambda *a: _reponse(" Je pousse sur Gitea. ")) assert t.transcrire(np.zeros(1600, dtype="float32")) == "Je pousse sur Gitea." def test_une_erreur_rendue_dans_un_200_est_une_erreur_explicite(): # OpenRouter peut répondre 200 avec un objet `error` (fournisseur en panne). t = _transcripteur(lambda *a: {"error": {"code": 502, "message": "fournisseur indisponible"}}) with pytest.raises(ErreurGemini) as e: t.transcrire(np.zeros(1600, dtype="float32")) assert "fournisseur indisponible" in str(e.value) def test_une_reponse_sans_choix_est_une_erreur_explicite(): t = _transcripteur(lambda *a: {"choices": []}) with pytest.raises(ErreurGemini): t.transcrire(np.zeros(1600, dtype="float32")) def test_une_cle_absente_est_signalee_des_le_warmup_sans_appel(): appels = [] t = _transcripteur(lambda *a: appels.append(a), cle=None) with pytest.raises(ErreurGemini) as e: t.warmup() assert "zonza-openrouter" in str(e.value) assert appels == [] def test_la_cle_se_lit_sous_son_propre_nom(): assert lire_cle_openrouter(trousseau=lambda: None, environ={"OPENROUTER_API_KEY": "or", "GEMINI_API_KEY": "g"}) == "or" assert lire_cle_openrouter(trousseau=lambda: None, environ={"GEMINI_API_KEY": "g"}) is None def test_la_fabrique_rend_openrouter_enveloppe_d_un_repli(): t = choisir_transcripteur(nom="openrouter", modele="google/gemini-x", langue="fr", amorce="") assert isinstance(t, TranscripteurAvecRepli) assert isinstance(t.principal, TranscripteurOpenRouter)