Skip to content

Commit

Permalink
[Test] Firsts moked tests (#22)
Browse files Browse the repository at this point in the history
* test: write tests for search formation + onisep api call

* test: write tests for a auth user

* feat: add types

* test: Write first service test

* Fix: CI errors
  • Loading branch information
Angel-Dijoux authored Jan 1, 2024
1 parent 1310f1b commit f8945ae
Show file tree
Hide file tree
Showing 9 changed files with 263 additions and 29 deletions.
42 changes: 42 additions & 0 deletions src/blueprints/tests/services/test_search_formations_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from unittest.mock import patch
import pytest
from src.business_logic.formation.scrap.tests.unit.test_search_onisep_formations import (
MOKED_RESEARCH,
)
from src.business_logic.formation.scrap.types import FormationIsFavorite
from src.models.formation import Formation


@pytest.fixture
def mock_search_formations():
with patch(
"src.business_logic.formation.scrap.search_formation.get_raw_data"
) as mock_get_raw_data:
yield mock_get_raw_data


def test_no_authenticated_search_formation_api_should_return_formations(
client, mock_search_formations
):
# Arrange
mock_search_formations.return_value = MOKED_RESEARCH

# When
response = client.post(
"/api/v1/formations/search", json={"query": "STH", "limit": 1}
)

result = response.json
print(result)

# Then
formation_favorite = result["formations"][0]
formation_favorite_instance = FormationIsFavorite(**formation_favorite)

formation = formation_favorite["formation"]
formation_instance = Formation(**formation)

assert result["total"] == 5754
assert isinstance(formation_favorite_instance, FormationIsFavorite)
assert formation_favorite["is_favorite"] is False
assert isinstance(formation_instance, Formation)
15 changes: 5 additions & 10 deletions src/business_logic/formation/scrap/get_main_formation.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,13 @@
format_formations,
)
from src.business_logic.formation.scrap.types import FormationsWithTotal
from src.business_logic.formation.scrap.utils.get_onisep_data import get_onisep_data


def _get_raw_main_formations(limit: int = 10, offset: int = None) -> dict:
params = f"/search?&size={limit}"
if offset:
params += f"&from={offset}"
return get_onisep_data(params)
from src.business_logic.formation.scrap.utils.get_onisep_data import (
get_raw_data,
)


def get_main_formations(limit: int = 10, offset: int = None) -> FormationsWithTotal:
data = _get_raw_main_formations(limit, offset)
data = get_raw_data(limit, offset)

formated_formations = format_formations(data["results"])

Expand All @@ -24,7 +19,7 @@ def get_main_formations(limit: int = 10, offset: int = None) -> FormationsWithTo
def auth_get_main_formations(
user_id: int, limit: int = 10, offset: int = None
) -> FormationsWithTotal:
data = _get_raw_main_formations(limit, offset)
data = get_raw_data(limit, offset)

formated_formations = format_formation_with_is_favorite(user_id, data["results"])

Expand Down
15 changes: 5 additions & 10 deletions src/business_logic/formation/scrap/search_formation.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,15 @@
format_formations,
)
from src.business_logic.formation.scrap.types import FormationsWithTotal
from src.business_logic.formation.scrap.utils.get_onisep_data import get_onisep_data


def _get_raw_search_data(query: str, limit: int, offset: int = None) -> dict:
params = f"/search?q={query}&size={limit}"
if offset:
params += f"&from={offset}"
return get_onisep_data(params)
from src.business_logic.formation.scrap.utils.get_onisep_data import (
get_raw_data,
)


def search_formations(
query: str, limit: int, offset: int = None
) -> FormationsWithTotal:
data = _get_raw_search_data(query, limit, offset)
data = get_raw_data(query, limit, offset)

formated_formations = format_formations(data["results"])

Expand All @@ -26,7 +21,7 @@ def search_formations(
def auth_search_formations(
user_id: int, query: str, limit: int, offset: int = None
) -> FormationsWithTotal:
data = _get_raw_search_data(query, limit, offset)
data = get_raw_data(query, limit, offset)
formated_formations = format_formation_with_is_favorite(user_id, data["results"])

return FormationsWithTotal(data["total"], formated_formations)
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from unittest.mock import MagicMock, patch

import pytest
from src.business_logic.formation.exceptions import NoOnisepAPIException
from src.business_logic.formation.scrap.utils.get_onisep_data import get_raw_data

from src.constants.http_status_codes import (
HTTP_200_OK,
HTTP_401_UNAUTHORIZED,
HTTP_500_INTERNAL_SERVER_ERROR,
)


@pytest.fixture
def mock_onisep_request():
with patch(
"src.business_logic.formation.scrap.utils.get_onisep_data.requests.get"
) as mock_requests_get:
yield mock_requests_get


def test_get_onisep_data_successful(mock_onisep_request):
# Arrange
mock_response = MagicMock()
mock_response.status_code = HTTP_200_OK
mock_response.json.return_value = {"total": "5173"}
mock_onisep_request.return_value = mock_response
# Act
result = get_raw_data("SHR")
# Assert
assert result == {"total": "5173"}


def test_get_onisep_data_retry_after_unauthorized(mock_onisep_request):
# Arrange
unauthorized_response = MagicMock()
unauthorized_response.status_code = HTTP_401_UNAUTHORIZED
authorized_response = MagicMock()
authorized_response.status_code = HTTP_200_OK
authorized_response.json.return_value = {"total": "5173"}

# Set up the responses for the two requests
mock_onisep_request.side_effect = [unauthorized_response, authorized_response]

# Act
result = get_raw_data("SHR")

# Assert
assert result == {"total": "5173"}


def test_get_onisep_data_raises_exception(mock_onisep_request):
# Arrange
mock_response = MagicMock()
mock_response.status_code = HTTP_500_INTERNAL_SERVER_ERROR
mock_onisep_request.return_value = mock_response

# Act and Assert
with pytest.raises(NoOnisepAPIException):
get_raw_data("SHR")
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
from unittest.mock import patch

import pytest
from src.business_logic.formation.scrap.search_formation import (
auth_search_formations,
search_formations,
)
from src.business_logic.formation.scrap.types import (
FormationsWithTotal,
)
from src.business_logic.formation.scrap.utils.format_formations import (
format_formation_with_is_favorite,
format_formations,
)
from src.models.user import User
from src.models.user_favori import UserFavori
from src.tests.factories.factories import (
UserFactory,
UserFavorisFactory,
)


MOKED_RESEARCH = {
"total": 5754,
"size": 1,
"results": [
{
"code_nsf": "334",
"sigle_type_formation": "",
"libelle_type_formation": "baccalauréat technologique",
"libelle_formation_principal": "bac techno STHR Sciences et technologies de l'hôtellerie et de la restauration",
"sigle_formation": "STHR",
"duree": "1 an",
"niveau_de_sortie_indicatif": "Bac ou équivalent",
"code_rncp": "",
"niveau_de_certification": "4",
"libelle_niveau_de_certification": "niveau 4 (bac ou équivalent)",
"tutelle": "Ministère chargé de l'Éducation nationale et de la Jeunesse",
"url_et_id_onisep": "http://www.onisep.fr/http/redirection/formation/slug/FOR.494",
"domainesous-domaine": "hôtellerie-restauration, tourisme/hôtellerie | hôtellerie-restauration, tourisme/restauration",
}
],
}


@pytest.fixture
def mock_search_formations():
with patch(
"src.business_logic.formation.scrap.search_formation.get_raw_data"
) as mock_get_raw_data:
yield mock_get_raw_data


def test_search_formations_should_return_formations_without_favorite(
mock_search_formations,
):
# Arrange
mock_search_formations.return_value = MOKED_RESEARCH

# Act
formations = search_formations("STHR", 1)

# Assert
moked_formation = MOKED_RESEARCH["results"]
waited_formations = format_formations(moked_formation)
waited_result = FormationsWithTotal(
total=MOKED_RESEARCH["total"], formations=waited_formations
)

assert formations == waited_result
mock_search_formations.assert_called_once_with("STHR", 1, None)


def test_authenticated_search_formations_should_return_formations_with_favorite(
mock_search_formations, db_session
):
# Arrange
mock_search_formations.return_value = MOKED_RESEARCH

# Given
user: User = UserFactory()
db_session.add(user)
db_session.flush()

user_favori: UserFavori = UserFavorisFactory(user_id=user.id)
db_session.add(user_favori)
db_session.commit()

# Act
formations = auth_search_formations(user.id, "STHR", 1)

# Assert
moked_formation = MOKED_RESEARCH["results"]
waited_formations = format_formation_with_is_favorite(user.id, moked_formation)
waited_result = FormationsWithTotal(
total=MOKED_RESEARCH["total"], formations=waited_formations
)

assert formations == waited_result
mock_search_formations.assert_called_once_with("STHR", 1, None)
22 changes: 21 additions & 1 deletion src/business_logic/formation/scrap/utils/get_onisep_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
from src.business_logic.formation import HEADERS, ONISEP_URL, HeaderKey
from src.business_logic.formation.exceptions import NoOnisepAPIException
from src.business_logic.formation.scrap.utils.get_onisep_token import get_token
from src.constants.http_status_codes import HTTP_200_OK, HTTP_401_UNAUTHORIZED
from src.constants.http_status_codes import (
HTTP_200_OK,
HTTP_401_UNAUTHORIZED,
)


DATASET = "5fa591127f501"
Expand All @@ -23,3 +26,20 @@ def get_onisep_data(params: str) -> dict:
raise NoOnisepAPIException(
f"\n status: {response.status_code} \n message : Onisep API is down. \n dataset : {DATASET} \n headers : {HEADERS} "
)


def get_raw_data(
query: str = None,
limit: int = 10,
offset: int = None,
is_main_formations: bool = True,
) -> dict:
if is_main_formations:
params = f"/search?&size={limit}"
else:
params = f"/search?q={query}&size={limit}"

if offset:
params += f"&from={offset}"

return get_onisep_data(params)
3 changes: 2 additions & 1 deletion src/business_logic/formation/scrap/utils/get_onisep_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import requests

from src.business_logic.formation.exceptions import NoOnisepAPIException
from src.constants.http_status_codes import HTTP_200_OK


URL = "https://api.opendata.onisep.fr/api/1.0/login"
Expand All @@ -20,7 +21,7 @@ def _get_form_data() -> dict[str, str]:

def get_token() -> BearerToken:
response = requests.post(URL, data=_get_form_data())
if response.status_code == 200:
if response.status_code == HTTP_200_OK:
data = response.json()
return f"Bearer {data.get('token')}"
raise NoOnisepAPIException(
Expand Down
17 changes: 10 additions & 7 deletions src/tests/factories/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,9 @@ class UserFactory(factory.Factory):
class Meta:
model = models.User

username = factory.Faker("uuidv4")
email = factory.Faker("uuidv4")


class UserFavorisFactory(factory.Factory):
class Meta:
model = models.UserFavori
username = factory.Faker("uuid4")
email = factory.Faker("uuid4")
password = factory.Faker("uuid4")


class FormationFactory(factory.Factory):
Expand All @@ -29,3 +25,10 @@ class Meta:
domain = "hôtellerie-restauration, tourisme/hôtellerie | hôtellerie-restauration, tourisme/restauration"
niveau_de_sortie = "Bac ou équivalent"
duree = "1 an"


class UserFavorisFactory(factory.Factory):
class Meta:
model = models.UserFavori

formation = factory.SubFactory(FormationFactory)
18 changes: 18 additions & 0 deletions src/tests/fixtures/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@
from sqlalchemy.orm import Session
from flask.testing import FlaskClient
from flask_sqlalchemy import SQLAlchemy
from flask_jwt_extended import (
create_access_token,
)

from src import create_app
from src import db as _db
from src.business_logic.formation.scrap.utils.get_onisep_token import BearerToken
from src.models.user import User


@pytest.fixture(autouse=True, scope="session")
Expand Down Expand Up @@ -34,3 +39,16 @@ def db_session(db: SQLAlchemy) -> Session:
def client(app: Flask, db: SQLAlchemy) -> Generator[FlaskClient, None, None]:
with app.test_client() as client:
yield client


@pytest.fixture(scope="module")
def authenticated_user(db_session: Session) -> tuple[User, dict[str, BearerToken]]:
from src.tests.factories import UserFactory

user: User = UserFactory()
db_session.add(user)
db_session.commit()

headers = {"Authorization": f"Bearer {create_access_token(identity=user.id)}"}

yield user, headers

0 comments on commit f8945ae

Please sign in to comment.