| """Integration tests that boot the FastAPI app via TestClient. |
| |
| The app is configured to use synthetic artefacts via env vars, so these |
| tests run in seconds and don't need the real 5 GB of training data. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import importlib |
|
|
| import pytest |
| from fastapi.testclient import TestClient |
|
|
|
|
| @pytest.fixture |
| def client(patched_settings): |
| """Build a TestClient against a freshly-loaded app. |
| |
| Re-import api.main so it picks up the patched api.settings module. |
| """ |
| import api.main as main_module |
|
|
| importlib.reload(main_module) |
| with TestClient(main_module.app) as test_client: |
| yield test_client |
|
|
|
|
| def test_health_endpoint(client): |
| resp = client.get("/health") |
| assert resp.status_code == 200 |
| payload = resp.json() |
| assert payload["status"] == "ok" |
| assert payload["model_version"] == "test-1" |
|
|
|
|
| def test_swagger_docs_available(client): |
| """The brief explicitly requires Swagger documentation.""" |
| resp = client.get("/docs") |
| assert resp.status_code == 200 |
|
|
|
|
| def test_openapi_schema(client): |
| resp = client.get("/openapi.json") |
| assert resp.status_code == 200 |
| schema = resp.json() |
| assert "/predict" in schema["paths"] |
|
|
|
|
| def test_model_info_endpoint(client): |
| resp = client.get("/model/info") |
| assert resp.status_code == 200 |
| payload = resp.json() |
| assert payload["threshold"] == 0.33 |
| assert payload["version"] == "test-1" |
| assert payload["n_features_expected"] > 0 |
|
|
|
|
| def test_predict_known_client(client, valid_payload): |
| resp = client.post("/predict", json=valid_payload) |
| assert resp.status_code == 200, resp.text |
| payload = resp.json() |
| assert payload["sk_id_curr"] == valid_payload["SK_ID_CURR"] |
| assert payload["client_known"] is True |
| assert payload["decision"] in {"GRANTED", "REFUSED"} |
| assert 0.0 <= payload["probability_default"] <= 1.0 |
| assert payload["threshold"] == 0.33 |
| assert payload["model_version"] == "test-1" |
|
|
|
|
| def test_predict_unknown_client(client, valid_payload): |
| valid_payload["SK_ID_CURR"] = 999_999_999 |
| resp = client.post("/predict", json=valid_payload) |
| assert resp.status_code == 200, resp.text |
| assert resp.json()["client_known"] is False |
|
|
|
|
| def test_predict_rejects_negative_income(client, valid_payload): |
| valid_payload["AMT_INCOME_TOTAL"] = -1.0 |
| resp = client.post("/predict", json=valid_payload) |
| assert resp.status_code == 422 |
|
|
|
|
| def test_predict_rejects_missing_required_field(client, valid_payload): |
| del valid_payload["DAYS_BIRTH"] |
| resp = client.post("/predict", json=valid_payload) |
| assert resp.status_code == 422 |
|
|
|
|
| def test_predict_rejects_extra_field(client, valid_payload): |
| valid_payload["__injection__"] = "evil" |
| resp = client.post("/predict", json=valid_payload) |
| assert resp.status_code == 422 |
|
|
|
|
| def test_predict_rejects_unknown_contract_type(client, valid_payload): |
| valid_payload["NAME_CONTRACT_TYPE"] = "Bitcoin loans" |
| resp = client.post("/predict", json=valid_payload) |
| assert resp.status_code == 422 |
|
|