attrition-api-dev / tests /test_model.py
rriviere's picture
test: add model integration and threshold tests
63dacf7
Raw History Blame Contribute Delete
2.14 kB
import pytest
from unittest.mock import MagicMock, patch
from fastapi.testclient import TestClient
from ml_model.loader import load_pipeline
from app.main import app
from tests.conftest import VALID_PAYLOAD, TEST_API_KEY
# Fixture : client avec le VRAI pipeline (pas de mock)
@pytest.fixture(scope="module")
def real_client():
with patch.dict("os.environ", {"API_KEY": TEST_API_KEY}):
with TestClient(app) as c:
yield c
# Tester que le pipeline .pkl se charge correctement
def test_load_pipeline():
pipeline, threshold = load_pipeline()
assert pipeline is not None
assert isinstance(threshold, float)
# Tester que la prédiction retourne bien "Reste" ou "Quitte"
def test_predict_label_valid(real_client):
response = real_client.post(
"/predict",
json=VALID_PAYLOAD,
headers={"X-API-Key": TEST_API_KEY},
)
assert response.status_code == 200
assert response.json()["label"] in ("Reste", "Quitte")
# Tester que la probabilité est bien comprise entre 0 et 1
def test_predict_probabilite_range(real_client):
response = real_client.post(
"/predict",
json=VALID_PAYLOAD,
headers={"X-API-Key": TEST_API_KEY},
)
assert 0.0 <= response.json()["probabilite"] <= 1.0
# Tester que le seuil 0.40 est bien appliqué
@pytest.mark.parametrize("proba_quitte,expected_label", [
(0.50, "Quitte"), # clairement au-dessus du seuil
(0.30, "Reste"), # clairement en-dessous du seuil
])
def test_threshold_applied(proba_quitte, expected_label):
_, threshold = load_pipeline() # ← vrai seuil (0.3999...7)
mock_pipeline = MagicMock()
mock_pipeline.predict_proba.return_value = [[1 - proba_quitte, proba_quitte]]
with patch("app.main.load_pipeline", return_value=(mock_pipeline, threshold)), \
patch.dict("os.environ", {"API_KEY": TEST_API_KEY}):
with TestClient(app) as c:
response = c.post(
"/predict",
json=VALID_PAYLOAD,
headers={"X-API-Key": TEST_API_KEY},
)
assert response.json()["label"] == expected_label