github-actions[bot]
Deploy snapshot 73053884eaccbd2ab4535c17ecba37a677a3fc0d
be9501c
Raw
History Blame Contribute Delete
4.65 kB
from __future__ import annotations
import os
from typing import Any
import pytest
from fastapi.testclient import TestClient
import main
from src.utils import load_env
TEST_API_KEY = "test-api-key"
AUTH_HEADERS = {"X-API-Key": TEST_API_KEY}
def _build_valid_record() -> dict[str, Any]:
record: dict[str, Any] = {}
for feature, spec in main.FEATURE_SPECS.items():
expected_type = str(spec.get("type", "")).strip().lower()
if expected_type == "number":
record[feature] = 1.0
elif expected_type == "category":
choices = spec.get("choices", []) or []
record[feature] = choices[0]
else:
raise AssertionError(f"Type de feature non supporté dans le schéma: {expected_type!r}")
return record
@pytest.fixture
def client(monkeypatch: pytest.MonkeyPatch) -> TestClient:
monkeypatch.setattr(main, "API_KEY", TEST_API_KEY)
monkeypatch.setattr(main, "get_engine_from_env", lambda: None)
monkeypatch.setattr(main, "init_api_logging_tables", lambda _engine: None)
monkeypatch.setattr(main, "init_feature_tables_if_missing", lambda _engine: None)
monkeypatch.setattr(
main,
"predict_with_artifact_model",
lambda X, threshold=0.5: ([0.8 for _ in range(len(X))], [1 for _ in range(len(X))]),
)
with TestClient(main.app) as test_client:
yield test_client
# ---------------------------------------------------------------------
# Tests API
# ---------------------------------------------------------------------
def test_health_returns_ok(client: TestClient) -> None:
response = client.get("/health")
assert response.status_code == 200
assert response.json() == {"status": "ok"}
def test_predict_returns_proba_and_label(client: TestClient) -> None:
payload = {"records": [_build_valid_record()]}
response = client.post("/predict", json=payload, headers=AUTH_HEADERS)
assert response.status_code == 200
body = response.json()
assert set(body.keys()) == {"proba_leave", "label"}
assert len(body["proba_leave"]) == 1
assert len(body["label"]) == 1
assert isinstance(body["proba_leave"][0], float)
assert body["label"][0] in (0, 1)
def test_predict_returns_401_without_api_key(client: TestClient) -> None:
payload = {"records": [_build_valid_record()]}
response = client.post("/predict", json=payload)
assert response.status_code == 401
def test_predict_returns_401_with_wrong_api_key(client: TestClient) -> None:
payload = {"records": [_build_valid_record()]}
response = client.post("/predict", json=payload, headers={"X-API-Key": "wrong-api-key"})
assert response.status_code == 401
def test_predict_returns_422_when_record_has_missing_key(client: TestClient) -> None:
record = _build_valid_record()
missing_feature = next(iter(main.REQUIRED_FEATURES_ORDER))
record.pop(missing_feature)
payload = {"records": [record]}
response = client.post("/predict", json=payload, headers=AUTH_HEADERS)
assert response.status_code == 422
assert "features manquantes" in response.text
def test_predict_returns_422_when_feature_has_invalid_type(client: TestClient) -> None:
record = _build_valid_record()
numeric_feature = next(
feature
for feature, spec in main.FEATURE_SPECS.items()
if str(spec.get("type", "")).strip().lower() == "number"
)
record[numeric_feature] = "not-a-number"
payload = {"records": [record]}
response = client.post("/predict", json=payload, headers=AUTH_HEADERS)
assert response.status_code == 422
assert "doit être un nombre" in response.text
# ---------------------------------------------------------------------
# Tests load_env (migré depuis main._resolve_env_file_path)
# ---------------------------------------------------------------------
def test_load_env_loads_root_env_if_present(tmp_path) -> None:
root_env = tmp_path / ".env"
root_env.write_text("TEST_VAR_ROOT=root\n", encoding="utf-8")
load_env(root_dir=tmp_path)
assert os.getenv("TEST_VAR_ROOT") == "root"
def test_load_env_does_nothing_if_no_env_file(tmp_path) -> None:
# Ne doit pas lever d'exception
load_env(root_dir=tmp_path)
def test_load_env_falls_back_to_confs_env(tmp_path) -> None:
confs_env = tmp_path / "confs" / "dev" / ".env"
confs_env.parent.mkdir(parents=True, exist_ok=True)
confs_env.write_text("TEST_VAR_CONFS=confs\n", encoding="utf-8")
load_env(root_dir=tmp_path)
assert os.getenv("TEST_VAR_CONFS") == "confs"
def test_load_env_falls_back_to_confs_env_pattern(tmp_path) -> None:
confs_env = tmp_path / "confs" / "dev" / ".env.dev"
confs_env.parent.mkdir(parents=True, exist_ok=True)
confs_env.write_text("TEST_VAR_PATTERN=pattern\n", encoding="utf-8")
load_env(root_dir=tmp_path)
assert os.getenv("TEST_VAR_PATTERN") == "pattern"