rriviere commited on
Commit
36ee2f7
·
1 Parent(s): 5289082

test: add pytest suite for API endpoints

Browse files

- Add tests for /health, /predict (valid, invalid data, missing auth)
- Add conftest.py with mocked pipeline fixture and test payload
- Add pytest.ini with pythonpath config
- Add pytest and pytest-cov dependencies to requirements.txt

Files changed (4) hide show
  1. pytest.ini +2 -0
  2. requirements.txt +6 -0
  3. tests/conftest.py +36 -0
  4. tests/test_api.py +40 -0
pytest.ini ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ [pytest]
2
+ pythonpath = .
requirements.txt CHANGED
@@ -3,6 +3,7 @@ annotated-types==0.7.0
3
  anyio==4.12.1
4
  certifi==2026.2.25
5
  click==8.3.1
 
6
  dnspython==2.8.0
7
  email-validator==2.3.0
8
  fastapi==0.133.1
@@ -14,18 +15,23 @@ httpcore==1.0.9
14
  httptools==0.7.1
15
  httpx==0.28.1
16
  idna==3.11
 
17
  Jinja2==3.1.6
18
  joblib==1.5.3
19
  markdown-it-py==4.0.0
20
  MarkupSafe==3.0.3
21
  mdurl==0.1.2
22
  numpy==2.4.2
 
23
  pandas==3.0.1
 
24
  pydantic==2.12.5
25
  pydantic-extra-types==2.11.0
26
  pydantic-settings==2.13.1
27
  pydantic_core==2.41.5
28
  Pygments==2.19.2
 
 
29
  python-dateutil==2.9.0.post0
30
  python-dotenv==1.2.1
31
  python-multipart==0.0.22
 
3
  anyio==4.12.1
4
  certifi==2026.2.25
5
  click==8.3.1
6
+ coverage==7.13.4
7
  dnspython==2.8.0
8
  email-validator==2.3.0
9
  fastapi==0.133.1
 
15
  httptools==0.7.1
16
  httpx==0.28.1
17
  idna==3.11
18
+ iniconfig==2.3.0
19
  Jinja2==3.1.6
20
  joblib==1.5.3
21
  markdown-it-py==4.0.0
22
  MarkupSafe==3.0.3
23
  mdurl==0.1.2
24
  numpy==2.4.2
25
+ packaging==26.0
26
  pandas==3.0.1
27
+ pluggy==1.6.0
28
  pydantic==2.12.5
29
  pydantic-extra-types==2.11.0
30
  pydantic-settings==2.13.1
31
  pydantic_core==2.41.5
32
  Pygments==2.19.2
33
+ pytest==9.0.2
34
+ pytest-cov==7.0.0
35
  python-dateutil==2.9.0.post0
36
  python-dotenv==1.2.1
37
  python-multipart==0.0.22
tests/conftest.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+ from unittest.mock import MagicMock, patch
3
+ from fastapi.testclient import TestClient
4
+ from app.main import app
5
+
6
+ TEST_API_KEY = "test-secret-key"
7
+
8
+ VALID_PAYLOAD = {
9
+ "age": 35, "genre": 1, "statut_marital": "Marié(e)",
10
+ "poste": "Consultant", "domaine_etude": "Infra & Cloud",
11
+ "niveau_education": 3, "nombre_experiences_precedentes": 2,
12
+ "annee_experience_totale": 10, "annees_dans_l_entreprise": 5,
13
+ "annees_dans_le_poste_actuel": 2, "annees_sous_responsable_actuel": 3,
14
+ "annees_depuis_la_derniere_promotion": 1,
15
+ "note_evaluation_actuelle": 3, "note_evaluation_precedente": 3,
16
+ "augmentation_salaire_precedente": 15, "nb_formations_suivies": 2,
17
+ "nombre_participation_pee": 1,
18
+ "satisfaction_employee_environnement": 3,
19
+ "satisfaction_employee_nature_travail": 4,
20
+ "satisfaction_employee_equipe": 3,
21
+ "satisfaction_employee_equilibre_pro_perso": 2,
22
+ "heure_supplementaires": 0, "frequence_deplacement": 1,
23
+ "distance_domicile_travail": 10, "revenu_mensuel": 5000,
24
+ }
25
+
26
+ @pytest.fixture(scope="module")
27
+ def client():
28
+ mock_pipeline = MagicMock()
29
+ # Simule predict_proba → [[proba_reste, proba_quitte]]
30
+ # 0.3 < seuil 0.40 → prédiction "Reste"
31
+ mock_pipeline.predict_proba.return_value = [[0.70, 0.30]]
32
+
33
+ with patch("app.main.load_pipeline", return_value=(mock_pipeline, 0.40)), \
34
+ patch.dict("os.environ", {"API_KEY": TEST_API_KEY}):
35
+ with TestClient(app) as c:
36
+ yield c
tests/test_api.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from tests.conftest import VALID_PAYLOAD, TEST_API_KEY
2
+
3
+
4
+ # Tester GET /health
5
+ def test_health_check(client):
6
+ response = client.get("/health")
7
+ assert response.status_code == 200
8
+ assert response.json()["status"] == "ok"
9
+
10
+
11
+ # Tester POST /predict avec données valides
12
+ def test_predict_valid(client):
13
+ response = client.post(
14
+ "/predict",
15
+ json=VALID_PAYLOAD,
16
+ headers={"X-API-Key": TEST_API_KEY},
17
+ )
18
+ assert response.status_code == 200
19
+ body = response.json()
20
+ assert "label" in body
21
+ assert body["label"] in ("Reste", "Quitte")
22
+ assert "probabilite" in body
23
+ assert 0.0 <= body["probabilite"] <= 1.0
24
+
25
+
26
+ # Tester POST /predict avec données invalides
27
+ def test_predict_invalid_data(client):
28
+ invalid_payload = {"age": 999, "genre": 99} # hors bornes + champs manquants
29
+ response = client.post(
30
+ "/predict",
31
+ json=invalid_payload,
32
+ headers={"X-API-Key": TEST_API_KEY},
33
+ )
34
+ assert response.status_code == 422 # Pydantic validation error
35
+
36
+
37
+ # Tester POST /predict sans API Key → 401
38
+ def test_predict_no_api_key(client):
39
+ response = client.post("/predict", json=VALID_PAYLOAD)
40
+ assert response.status_code == 401