technova-api / tests /test_api.py
SteGONZALEZ's picture
commentaires dans les tests
6d68c0d
Raw
History Blame Contribute Delete
5.74 kB
import pytest
import numpy as np
from domain.domain import ModelResponse
from service.technova_service import TechNovaService
# Payload d’exemple complet correspondant aux features attendues par l’API
# Il est réutilisé dans plusieurs tests pour éviter les duplications
def sample_payload():
return {
"note_evaluation_precedente": 3,
"niveau_hierarchique_poste": 2,
"note_evaluation_actuelle": 3,
"heures_supplementaires": 1,
"augmentation_salaire_precedente": 11.0,
"age": 41,
"genre": 0,
"revenu_mensuel": 5993,
"statut_marital": "célibataire",
"departement": "commercial",
"poste": "cadre commercial",
"nombre_experiences_precedentes": 8,
"annee_experience_totale": 8,
"annees_dans_l_entreprise": 6,
"annees_dans_le_poste_actuel": 4,
"nombre_participation_pee": 0,
"nb_formations_suivies": 0,
"distance_domicile_travail": 1,
"niveau_education": 2,
"domaine_etude": "infra & cloud",
"frequence_deplacement": 1,
"annees_depuis_la_derniere_promotion": 0,
"annees_sous_responsable_actuel": 5,
"satisfaction_moyenne": 2.0,
"nonlineaire_participation_pee": 0.0,
"ratio_heures_sup_salaire": 0.0001668335001668335,
"nonlinaire_charge_contrainte": 0.008264462809917356,
"nonlinaire_surmenage_insatisfaction": -1.0,
"jeune_surcharge": 0,
"anciennete_sans_promotion": 0.8571428571428571,
"mobilite_carriere": 0.8888888888888888,
"risque_global": -0.000143000143000143,
}
# Fixture appliquée automatiquement à tous les tests du fichier
# Elle permet de remplacer le vrai service de prédiction par une version contrôlée
@pytest.fixture(autouse=True)
def patch_service(monkeypatch):
# Faux modèle pour éviter de charger un vrai modèle ML
class FakeModel:
def predict_proba(self, X):
# Retourne toujours la même probabilité pour rendre les tests déterministes
return np.array([[0.2, 0.8]])
# Faux __init__ pour éviter toute logique lourde (chargement joblib, fichiers, etc.)
def fake_init(self):
self.threshold = 0.5
self.feature_columns = list(sample_payload().keys())
self.model = FakeModel()
# Faux comportement pour la prédiction par ID
# ID = 1 -> prédiction valide
# autre ID -> erreur "not found"
def fake_predict_from_clean(self, db, employee_id: int):
if employee_id == 1:
return ModelResponse(
employee_id=1,
turnover_probability=0.7,
will_leave=True
)
raise ValueError("not found")
# Application des patches sur le service réel
monkeypatch.setattr(TechNovaService, "__init__", fake_init)
monkeypatch.setattr(TechNovaService, "predict_from_clean", fake_predict_from_clean)
# Test simple de l’endpoint de santé
def test_health(client):
r = client.get("/health")
assert r.status_code == 200
# Test de l’endpoint de prédiction à partir des features
def test_predict_by_features_ok(client):
r = client.post("/predict/by-features", json=sample_payload())
assert r.status_code == 200, r.text
data = r.json()
# Vérifie la présence des champs attendus dans la réponse
assert "turnover_probability" in data
assert "will_leave" in data
# Test de la prédiction par ID :
# - ID existant -> 200
# - ID inexistant -> 404
def test_predict_by_id_200_and_404(client):
assert client.post("/predict/by-id/1").status_code == 200
assert client.post("/predict/by-id/2").status_code == 404
# Test de l’endpoint qui retourne les dernières prédictions
def test_latest_predictions_returns_list(client):
# On crée d’abord une prédiction
client.post("/predict/by-features", json=sample_payload())
# Puis on vérifie que l’endpoint retourne bien une liste
r = client.get("/predictions/latest", params={"limit": 3})
assert r.status_code == 200, r.text
assert isinstance(r.json(), list)
# Test destiné à couvrir la branche d’erreur inattendue (HTTP 500)
def test_predict_by_id_returns_500_on_unexpected_error(client, monkeypatch):
# Force une exception non prévue dans le service
def boom(self, db, employee_id: int):
raise Exception("boom")
monkeypatch.setattr(TechNovaService, "predict_from_clean", boom)
r = client.post("/predict/by-id/1")
assert r.status_code == 500, r.text
# Test de l’endpoint de readiness :
# il doit renvoyer 503 si la table clean.ml_features_employees est absente
def test_ready_returns_503_when_clean_table_missing(client, monkeypatch):
import app.api as api
# Faux service indiquant que le modèle est chargé
class FakeService:
model = object()
# Faux accès base de données simulant l’absence de la table clean
class FakeDB:
def execute(self, stmt, *_a, **_kw):
s = str(stmt)
if "FROM clean.ml_features_employees" in s:
raise Exception("relation does not exist")
return True
def fake_get_service():
return FakeService()
def fake_get_db():
yield FakeDB()
# Override des dépendances FastAPI pour le test
api.app.dependency_overrides[api.get_service] = fake_get_service
api.app.dependency_overrides[api.get_db] = fake_get_db
try:
r = client.get("/ready")
assert r.status_code == 503, r.text
assert "Clean table not ready" in r.text
finally:
# Nettoyage des overrides pour ne pas impacter les autres tests
api.app.dependency_overrides.clear()