test_api.py 26.3 KB
Newer Older
David Foucher's avatar
David Foucher committed
1
import datetime
Yohan Boniface's avatar
Yohan Boniface committed
2
import json
Régis Behmo's avatar
Régis Behmo committed
3
import os
4
import urllib.parse
Régis Behmo's avatar
Régis Behmo committed
5 6
from http import HTTPStatus
from pathlib import Path
Yohan Boniface's avatar
Yohan Boniface committed
7 8

import pytest
9
from openapi_core import create_spec
10
from openapi_core.wrappers.mock import MockRequest, MockResponse
11
from openapi_core.shortcuts import ResponseValidator
Yohan Boniface's avatar
Yohan Boniface committed
12
from openapi_spec_validator import validate_spec
Yohan Boniface's avatar
Yohan Boniface committed
13

14

15
from trefle.config import FINANCEMENTS
David Foucher's avatar
David Foucher committed
16
from trefle.config import REMUNERATIONS
17
from trefle import VERSION
Yohan Boniface's avatar
Yohan Boniface committed
18 19 20 21

pytestmark = pytest.mark.asyncio


David Foucher's avatar
David Foucher committed
22 23 24 25 26
async def test_healthcheck(client):
    resp = await client.get("/healthcheck")
    assert resp.status == HTTPStatus.OK


Régis Behmo's avatar
Régis Behmo committed
27
async def test_schema(client):
28
    resp = await client.get("/schema")
29 30 31 32
    assert resp.status == HTTPStatus.OK
    validate_spec(json.loads(resp.body))


Régis Behmo's avatar
Régis Behmo committed
33
async def test_simulate_endpoint(client):
34
    resp = await client.get("/schema")
35
    spec = create_spec(json.loads(resp.body))
Yohan Boniface's avatar
Yohan Boniface committed
36

37 38 39 40 41 42 43 44 45 46 47 48 49
    resp = await client.post(
        "/financement",
        body={
            "beneficiaire.solde_cpf": 10,
            "beneficiaire.remuneration": 1400,
            "beneficiaire.droit_prive": True,
            "beneficiaire.contrat": "cdi",
            "formation.eligible_cpf": True,
            "formation.heures": 100,
            "beneficiaire.entreprise.commune": "2A004",
            "beneficiaire.entreprise.idcc": 2706,
        },
    )
Yohan Boniface's avatar
Yohan Boniface committed
50
    assert resp.status == HTTPStatus.OK
51 52
    assert "financements" in json.loads(resp.body)
    financements = json.loads(resp.body)["financements"]
53
    print(financements[0])
Régis Behmo's avatar
Régis Behmo committed
54
    assert financements
55 56
    assert financements[0].get("eligible")
    assert "Version" in resp.headers
Yohan Boniface's avatar
Yohan Boniface committed
57

58
    validator = ResponseValidator(spec)
59
    request = MockRequest("http://trefle.pole-emploi.fr", "post", "/financement")
60 61 62
    response = MockResponse(resp.body, resp.status.value)
    result = validator.validate(request, response)
    result.raise_for_errors()
Yohan Boniface's avatar
Yohan Boniface committed
63 64


David Foucher's avatar
David Foucher committed
65 66 67 68 69
async def test_remuneration_endpoint(client):
    resp = await client.get("/schema")
    spec = create_spec(json.loads(resp.body))

    resp = await client.post(
70
        "/remuneration-aide",
David Foucher's avatar
David Foucher committed
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
        body={
            "beneficiaire.age": 20,
            "formation.region": 27,
            "formation.codes_financeur": [2],
        },
    )
    assert resp.status == HTTPStatus.OK
    assert "remunerations" in json.loads(resp.body)
    remunerations = json.loads(resp.body)["remunerations"]
    assert remunerations
    print(remunerations[0])
    assert "remuneration" in remunerations[0]
    assert "Version" in resp.headers

    validator = ResponseValidator(spec)
    request = MockRequest("http://trefle.pole-emploi.fr", "post", "/remuneration")
    response = MockResponse(resp.body, resp.status.value)
    result = validator.validate(request, response)
    result.raise_for_errors()


92
async def test_simulate_endpoint_without_formation_prix_horaire(client):
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
    resp = await client.get("/schema")

    resp = await client.post(
        "/financement",
        body={
            "beneficiaire.solde_cpf": 10,
            "beneficiaire.remuneration": 1400,
            "beneficiaire.droit_prive": True,
            "beneficiaire.contrat": "cdi",
            "formation.eligible_cpf": True,
            "formation.heures": 100,
            "beneficiaire.entreprise.commune": "2A004",
            "beneficiaire.entreprise.idcc": 2706,
        },
    )
108
    assert resp.status == HTTPStatus.OK
109 110
    assert "financements" in json.loads(resp.body)
    financements = json.loads(resp.body)["financements"]
Régis Behmo's avatar
Régis Behmo committed
111
    assert financements
112 113
    assert financements[0]["prise_en_charge"] is None
    assert financements[0]["plafond_prise_en_charge"] > 0
114 115 116


async def test_simulate_endpoint_with_formation_prix_horaire(client):
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
    resp = await client.get("/schema")

    resp = await client.post(
        "/financement",
        body={
            "beneficiaire.solde_cpf": 10,
            "beneficiaire.remuneration": 1400,
            "beneficiaire.droit_prive": True,
            "beneficiaire.contrat": "cdi",
            "formation.eligible_cpf": True,
            "formation.heures": 100,
            "formation.prix_horaire": 25,
            "beneficiaire.entreprise.commune": "2A004",
            "beneficiaire.entreprise.idcc": 2706,
        },
    )
133
    assert resp.status == HTTPStatus.OK
134 135
    assert "financements" in json.loads(resp.body)
    financements = json.loads(resp.body)["financements"]
Régis Behmo's avatar
Régis Behmo committed
136
    assert financements
137 138
    assert financements[0]["plafond_prise_en_charge"] > 0
    assert financements[0]["prise_en_charge"] > 0
139 140


Régis Behmo's avatar
Régis Behmo committed
141
async def test_simulate_endpoint_filter_eligible(client):
142
    body = {
143 144 145 146 147 148 149 150
        "beneficiaire.solde_cpf": 10,
        "beneficiaire.remuneration": 1400,
        "beneficiaire.droit_prive": True,
        "beneficiaire.contrat": "cdi",
        "formation.eligible_cpf": True,
        "formation.heures": 100,
        "beneficiaire.entreprise.commune": "2A004",
        "beneficiaire.entreprise.idcc": 2706,
151 152
    }
    # Normal request.
153
    resp = await client.post("/financement", body=body)
154
    assert resp.status == HTTPStatus.OK
155
    financements = json.loads(resp.body)["financements"]
156
    assert len(financements) == len(FINANCEMENTS)
157
    # Filter eligible only
158
    resp = await client.post("/financement?eligible=true", body=body)
159
    assert resp.status == HTTPStatus.OK
160
    financements = json.loads(resp.body)["financements"]
161
    assert len(financements) == 3
162
    for financement in financements:
163
        assert financement["eligible"] is True
164
    # Filter non eligible only
165
    resp = await client.post("/financement?eligible=false", body=body)
166
    assert resp.status == HTTPStatus.OK
167
    financements = json.loads(resp.body)["financements"]
168
    assert len(financements) == len(FINANCEMENTS) - 3
169
    for financement in financements:
170
        assert financement["eligible"] is False
171 172


Yohan Boniface's avatar
Yohan Boniface committed
173 174
async def test_simulate_endpoint_filter_tags(client):
    body = {
175 176 177 178 179 180 181 182
        "beneficiaire.solde_cpf": 10,
        "beneficiaire.remuneration": 1400,
        "beneficiaire.droit_prive": True,
        "beneficiaire.contrat": "cdi",
        "formation.eligible_cpf": True,
        "formation.heures": 100,
        "beneficiaire.entreprise.commune": "2A004",
        "beneficiaire.entreprise.idcc": 2706,
Yohan Boniface's avatar
Yohan Boniface committed
183 184
    }
    # Normal request.
185
    resp = await client.post("/financement", body=body)
Yohan Boniface's avatar
Yohan Boniface committed
186
    assert resp.status == HTTPStatus.OK
187
    financements = json.loads(resp.body)["financements"]
188
    assert len(financements) == len(FINANCEMENTS)
189
    # Filter CPF only
190
    resp = await client.post("/financement?tags=CPF", body=body)
Yohan Boniface's avatar
Yohan Boniface committed
191
    assert resp.status == HTTPStatus.OK
192
    financements = json.loads(resp.body)["financements"]
Yohan Boniface's avatar
Yohan Boniface committed
193
    assert len(financements) == 3
Yohan Boniface's avatar
Yohan Boniface committed
194
    for financement in financements:
195
        assert "CPF" in financement["tags"]
Yohan Boniface's avatar
Yohan Boniface committed
196 197


198 199
async def test_simulate_endpoint_mix_filters(client):
    body = {
200 201 202 203 204 205 206 207
        "beneficiaire.solde_cpf": 10,
        "beneficiaire.remuneration": 1400,
        "beneficiaire.droit_prive": True,
        "beneficiaire.contrat": "cdi",
        "formation.eligible_cpf": True,
        "formation.heures": 100,
        "beneficiaire.entreprise.commune": "2A004",
        "beneficiaire.entreprise.idcc": 2706,
208 209
    }
    # Normal request.
210
    resp = await client.post("/financement", body=body)
211
    assert resp.status == HTTPStatus.OK
212
    financements = json.loads(resp.body)["financements"]
213
    assert len(financements) == len(FINANCEMENTS)
214
    # Filter CPF only
215 216 217
    resp = await client.post(
        "/financement?tags=hors%20temps%20de%20travail" "&eligible=1", body=body
    )
218
    assert resp.status == HTTPStatus.OK
219
    financements = json.loads(resp.body)["financements"]
220
    assert len(financements) == 1
221 222 223 224


async def test_simulate_hors_temps_de_travail(client):
    body = {
225 226 227 228 229 230 231 232
        "beneficiaire.solde_cpf": 10,
        "beneficiaire.remuneration": 1400,
        "beneficiaire.droit_prive": True,
        "beneficiaire.contrat": "cdi",
        "formation.eligible_cpf": True,
        "formation.heures": 100,
        "beneficiaire.entreprise.commune": "2A004",
        "beneficiaire.entreprise.idcc": 2706,
233 234
    }
    # Normal request.
235
    resp = await client.post("/financement", body=body)
236
    assert resp.status == HTTPStatus.OK
237
    financements = json.loads(resp.body)["financements"]
238
    assert len(financements) == len(FINANCEMENTS)
239
    # Filter eligible only
240 241 242
    resp = await client.post(
        "/financement?tags=hors%20temps%20de%20travail" "&eligible=1", body=body
    )
243
    assert resp.status == HTTPStatus.OK
244
    financements = json.loads(resp.body)["financements"]
245
    assert len(financements) == 1
246 247


Régis Behmo's avatar
Régis Behmo committed
248 249
async def test_simulate_triggers_log(client):
    body = {
250 251 252 253 254 255 256 257
        "beneficiaire.solde_cpf": 10,
        "beneficiaire.remuneration": 1400,
        "beneficiaire.droit_prive": True,
        "beneficiaire.contrat": "cdi",
        "formation.eligible_cpf": True,
        "formation.heures": 100,
        "beneficiaire.entreprise.commune": "2A004",
        "beneficiaire.entreprise.idcc": 2706,
Régis Behmo's avatar
Régis Behmo committed
258 259
    }

260
    log_path = Path(os.environ["TREFLE_LOG_DIR"]) / "trefle-simulate.log"
261 262
    log_path.write_text("")
    resp = await client.post("/financement", body=body)
David Foucher's avatar
David Foucher committed
263
    assert resp.status == 200
Régis Behmo's avatar
Régis Behmo committed
264 265 266
    lines = log_path.read_text().splitlines()
    assert len(lines) == 1
    log_data = json.loads(lines[0])
267 268 269 270
    assert log_data["financements"]
    assert "version" in log_data
    assert "date" in log_data
    assert not log_data["errors"]
Régis Behmo's avatar
Régis Behmo committed
271 272 273 274


async def test_simulate_error_triggers_log(client):
    body = {
275 276 277 278 279 280 281 282
        "beneficiaire.solde_cpf": 10,
        "beneficiaire.remuneration": 1400,
        "beneficiaire.droit_prive": True,
        "beneficiaire.contrat": "cdi",
        "formation.eligible_cpf": True,
        "formation.heures": 100,
        "beneficiaire.entreprise.commune": "invalide",
        "beneficiaire.entreprise.idcc": 2706,
Régis Behmo's avatar
Régis Behmo committed
283 284
    }

285
    log_path = Path(os.environ["TREFLE_LOG_DIR"]) / "trefle-simulate.log"
286 287
    log_path.write_text("")
    resp = await client.post("/financement", body=body)
288
    assert resp.status == 400
Régis Behmo's avatar
Régis Behmo committed
289 290 291
    lines = log_path.read_text().splitlines()
    assert len(lines) == 1
    log_data = json.loads(lines[0])
292 293 294 295
    assert not log_data["financements"]
    assert "version" in log_data
    assert "date" in log_data
    assert "beneficiaire.entreprise.commune" in log_data["errors"]
Régis Behmo's avatar
Régis Behmo committed
296 297


Régis Behmo's avatar
Régis Behmo committed
298
async def test_simulate_endpoint_with_wrong_method(client):
299
    resp = await client.get("/financement")
Yohan Boniface's avatar
Yohan Boniface committed
300
    assert resp.status == HTTPStatus.METHOD_NOT_ALLOWED
301
    assert "application/json" in resp.headers["Content-Type"]
Yohan Boniface's avatar
Yohan Boniface committed
302 303


Régis Behmo's avatar
Régis Behmo committed
304
async def test_simulate_endpoint_with_empty_data(client):
305
    resp = await client.post("/financement", body="")
Yohan Boniface's avatar
Yohan Boniface committed
306
    assert resp.status == HTTPStatus.BAD_REQUEST
307
    assert "application/json" in resp.headers["Content-Type"]
Yohan Boniface's avatar
Yohan Boniface committed
308 309


Yohan Boniface's avatar
Yohan Boniface committed
310
async def test_simulate_endpoint_with_empty_idcc(client):
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325
    resp = await client.get("/schema")

    resp = await client.post(
        "/financement",
        body={
            "beneficiaire.solde_cpf": 10,
            "beneficiaire.remuneration": 1400,
            "beneficiaire.droit_prive": True,
            "beneficiaire.contrat": "cdi",
            "formation.eligible_cpf": True,
            "formation.heures": 100,
            "beneficiaire.entreprise.commune": "2A004",
            "beneficiaire.entreprise.idcc": None,
        },
    )
326
    assert resp.status == HTTPStatus.BAD_REQUEST
Yohan Boniface's avatar
Yohan Boniface committed
327
    assert json.loads(resp.body) == {
328 329
        "beneficiaire.entreprise.idcc": "Ce champ est obligatoire"
    }
Yohan Boniface's avatar
Yohan Boniface committed
330 331 332


async def test_simulate_endpoint_with_invalid_idcc_format(client):
333 334 335 336 337 338 339 340 341 342 343 344 345 346 347
    resp = await client.get("/schema")

    resp = await client.post(
        "/financement",
        body={
            "beneficiaire.solde_cpf": 10,
            "beneficiaire.remuneration": 1400,
            "beneficiaire.droit_prive": True,
            "beneficiaire.contrat": "cdi",
            "formation.eligible_cpf": True,
            "formation.heures": 100,
            "beneficiaire.entreprise.commune": "2A004",
            "beneficiaire.entreprise.idcc": "foobar",
        },
    )
348
    assert resp.status == HTTPStatus.BAD_REQUEST
Yohan Boniface's avatar
Yohan Boniface committed
349
    assert json.loads(resp.body) == {
350 351
        "beneficiaire.entreprise.idcc": "Valeur d'IDCC inconnue: `foobar`"
    }
Yohan Boniface's avatar
Yohan Boniface committed
352 353 354


async def test_simulate_endpoint_with_unknown_idcc(client):
355 356 357 358 359 360 361 362 363 364 365 366 367 368 369
    resp = await client.get("/schema")

    resp = await client.post(
        "/financement",
        body={
            "beneficiaire.solde_cpf": 10,
            "beneficiaire.remuneration": 1400,
            "beneficiaire.droit_prive": True,
            "beneficiaire.contrat": "cdi",
            "formation.eligible_cpf": True,
            "formation.heures": 100,
            "beneficiaire.entreprise.commune": "2A004",
            "beneficiaire.entreprise.idcc": 1234567,
        },
    )
370
    assert resp.status == HTTPStatus.BAD_REQUEST
Yohan Boniface's avatar
Yohan Boniface committed
371
    assert json.loads(resp.body) == {
372 373
        "beneficiaire.entreprise.idcc": "Valeur d'IDCC inconnue: `1234567`"
    }
Yohan Boniface's avatar
Yohan Boniface committed
374 375


376
@pytest.mark.parametrize("naf", ["12345", "A1234A", "1234AA"])
Yohan Boniface's avatar
Yohan Boniface committed
377
async def test_simulate_endpoint_with_invalid_naf(client, naf):
378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393
    resp = await client.get("/schema")

    resp = await client.post(
        "/financement",
        body={
            "beneficiaire.solde_cpf": 10,
            "beneficiaire.remuneration": 1400,
            "beneficiaire.droit_prive": True,
            "beneficiaire.contrat": "cdi",
            "formation.eligible_cpf": True,
            "formation.heures": 100,
            "beneficiaire.entreprise.commune": "2A004",
            "beneficiaire.entreprise.naf": naf,
            "beneficiaire.entreprise.idcc": "1486",
        },
    )
394
    assert resp.status == HTTPStatus.BAD_REQUEST
Yohan Boniface's avatar
Yohan Boniface committed
395
    assert json.loads(resp.body) == {
396 397 398 399
        "beneficiaire.entreprise.naf": (