From 16d4a0724c2c162acd95693e6e384e6a92c8786f Mon Sep 17 00:00:00 2001 From: stanislas Date: Mon, 18 Mar 2024 23:46:01 +0100 Subject: [PATCH] Added a lot of unit tests --- resa_padel/connectors.py | 64 +- resa_padel/models.py | 41 + tests/data/configuration/clubs.yaml | 92 + .../booking-cancellation-payload.txt | 1 + .../gestion-sports/booking-payload.txt | 1 + .../gestion-sports/login-payload.txt | 1 + .../gestion-sports/post-headers.json | 12 + .../gestion-sports/user-bookings-payload.txt | 1 + tests/data/configuration/platforms.yaml | 19 + tests/data/configuration/users.yaml | 13 + tests/data/responses/booking_failure.json | 4 + tests/data/responses/booking_success.json | 5 + .../data/responses/cancellation_response.json | 4 + tests/data/responses/landing_response.html | 2033 +++++++++++++++++ tests/data/responses/login_failure.json | 5 + tests/data/responses/login_success.json | 9 + tests/data/responses/user_bookings_get.html | 1363 +++++++++++ tests/data/responses/user_bookings_post.json | 52 + .../test_gestion_sports_connector.py | 185 -- .../test_gestion_sports_html_parser.py | 8 - .../test_gestion_sports_payload_builder.py | 62 - .../test_gestion_sports_platform.py | 87 - .../__init__.py | 0 tests/{ => integration_tests}/test_booking.py | 4 +- .../test_connectors.py | 2 +- tests/unit_tests/__init__.py | 0 tests/unit_tests/conftest.py | 284 +++ tests/unit_tests/test_booking.py | 0 tests/unit_tests/test_cancellation.py | 0 tests/{ => unit_tests}/test_config.py | 0 .../test_gestion_sports_connector.py | 315 +++ tests/utils.py | 98 +- 32 files changed, 4268 insertions(+), 497 deletions(-) create mode 100644 tests/data/configuration/clubs.yaml create mode 100644 tests/data/configuration/gestion-sports/booking-cancellation-payload.txt create mode 100644 tests/data/configuration/gestion-sports/booking-payload.txt create mode 100644 tests/data/configuration/gestion-sports/login-payload.txt create mode 100644 tests/data/configuration/gestion-sports/post-headers.json create mode 100644 tests/data/configuration/gestion-sports/user-bookings-payload.txt create mode 100644 tests/data/configuration/platforms.yaml create mode 100644 tests/data/configuration/users.yaml create mode 100644 tests/data/responses/booking_failure.json create mode 100644 tests/data/responses/booking_success.json create mode 100644 tests/data/responses/cancellation_response.json create mode 100644 tests/data/responses/landing_response.html create mode 100644 tests/data/responses/login_failure.json create mode 100644 tests/data/responses/login_success.json create mode 100644 tests/data/responses/user_bookings_get.html create mode 100644 tests/data/responses/user_bookings_post.json delete mode 100644 tests/gestion_sports/test_gestion_sports_connector.py delete mode 100644 tests/gestion_sports/test_gestion_sports_html_parser.py delete mode 100644 tests/gestion_sports/test_gestion_sports_payload_builder.py delete mode 100644 tests/gestion_sports/test_gestion_sports_platform.py rename tests/{gestion_sports => integration_tests}/__init__.py (100%) rename tests/{ => integration_tests}/test_booking.py (96%) rename tests/{ => integration_tests}/test_connectors.py (99%) create mode 100644 tests/unit_tests/__init__.py create mode 100644 tests/unit_tests/conftest.py create mode 100644 tests/unit_tests/test_booking.py create mode 100644 tests/unit_tests/test_cancellation.py rename tests/{ => unit_tests}/test_config.py (100%) create mode 100644 tests/unit_tests/test_gestion_sports_connector.py diff --git a/resa_padel/connectors.py b/resa_padel/connectors.py index c36149e..25c6c90 100644 --- a/resa_padel/connectors.py +++ b/resa_padel/connectors.py @@ -80,7 +80,7 @@ class GestionSportsConnector(Connector): raise ValueError( "Gestion Sports connector was instantiated with a club not handled" " by gestions sports. Club id is {} instead of gestion-sports".format( - club.club_id + club.id ) ) @@ -220,7 +220,7 @@ class GestionSportsConnector(Connector): return self._get_url_path("cancellation") @property - def booking_cancellation_template(self) -> Path: + def booking_cancel_template(self) -> Path: """ Get the payload template to send to get all the user's bookings that are available @@ -519,8 +519,9 @@ class GestionSportsConnector(Connector): :return: the response from the client """ hash_value = await self.send_hash_request(session) + payload = PayloadBuilder.build( - self.booking_cancellation_template, + self.booking_cancel_template, booking_id=booking_id, hash=hash_value, ) @@ -531,7 +532,9 @@ class GestionSportsConnector(Connector): await response.text() return response - async def cancel_booking(self, user: User, booking_filter: BookingFilter) -> None: + async def cancel_booking( + self, user: User, booking_filter: BookingFilter + ) -> ClientResponse | None: """ Cancel the booking that meets some conditions @@ -545,54 +548,5 @@ class GestionSportsConnector(Connector): bookings = await self.get_ongoing_bookings(session) for booking in bookings: - if self.is_booking_matching_filter(booking, booking_filter): - await self.send_cancellation_request(session, booking.id) - - def is_booking_matching_filter( - self, booking: Booking, booking_filter: BookingFilter - ) -> bool: - """ - Check if the booking matches the booking filter - - :param booking: the booking to be checked - :param booking_filter: the conditions the booking should meet - :return: true if the booking matches the conditions, false otherwise - """ - return ( - self._is_same_sport(booking, booking_filter) - and self._is_date_matching(booking, booking_filter) - and self._is_time_matching(booking, booking_filter) - ) - - @staticmethod - def _is_same_sport(booking: Booking, booking_filter: BookingFilter) -> bool: - """ - Check if the booking and the booking filter are about the same sport - - :param booking: the booking to be checked - :param booking_filter: the conditions the booking should meet - :return: true if the booking sport matches the filter sport, false otherwise - """ - return booking.sport == booking_filter.sport_name - - @staticmethod - def _is_date_matching(booking: Booking, booking_filter: BookingFilter) -> bool: - """ - Check if the booking and the booking filter are at the same date - - :param booking: the booking to be checked - :param booking_filter: the conditions the booking should meet - :return: true if the booking date matches the filter date, false otherwise - """ - return booking.booking_date.date() == booking_filter.date.date() - - @staticmethod - def _is_time_matching(booking: Booking, booking_filter: BookingFilter) -> bool: - """ - Check if the booking and the booking filter are at the same time - - :param booking: the booking to be checked - :param booking_filter: the conditions the booking should meet - :return: true if the booking time matches the filter time, false otherwise - """ - return booking.start_time.time() == booking_filter.date.time() + if booking.matches(booking_filter): + return await self.send_cancellation_request(session, booking.id) diff --git a/resa_padel/models.py b/resa_padel/models.py index 6045abd..d13b375 100644 --- a/resa_padel/models.py +++ b/resa_padel/models.py @@ -2,6 +2,7 @@ from enum import Enum from typing import Optional import pendulum +from pendulum import Date, Time from pydantic import BaseModel, ConfigDict, Field, field_validator from pydantic_extra_types.pendulum_dt import DateTime @@ -165,6 +166,46 @@ class Booking(BaseModel): def to_lower_case(cls, d: str) -> str: return d.lower() + def matches(self, booking_filter: BookingFilter) -> bool: + """ + Check if the booking matches the booking filter + + :param booking_filter: the conditions the booking should meet + :return: true if the booking matches the conditions, false otherwise + """ + return ( + self.is_same_sport(booking_filter.sport_name) + and self.is_same_date(booking_filter.date.date()) + and self.is_same_time(booking_filter.date.time()) + ) + + def is_same_sport(self, sport: str) -> bool: + """ + Check if the booking and the booking filter are about the same sport + + :param sport: the sport to test + :return: true if the sport matches booking sport, false otherwise + """ + return self.sport == sport + + def is_same_date(self, date: Date) -> bool: + """ + Check if the booking filter has the same date as the booking + + :param date: the date to test + :return: true if the date matches the booking date, false otherwise + """ + return self.booking_date.date() == date + + def is_same_time(self, time: Time) -> bool: + """ + Check if the booking filter has the same time as the booking + + :param time: the time to test + :return: true if the time matches the booking time, false otherwise + """ + return self.start_time.time() == time + class Action(Enum): BOOK = "book" diff --git a/tests/data/configuration/clubs.yaml b/tests/data/configuration/clubs.yaml new file mode 100644 index 0000000..649f291 --- /dev/null +++ b/tests/data/configuration/clubs.yaml @@ -0,0 +1,92 @@ +clubs: + - name: Super Club + url: https://www.super-club.com + id: sc + bookingPlatform: + id: gestion-sports + clubId: 54 + url: https://superclub.flapi.fr + hoursBeforeCancellation: 10 + bookingOpening: + daysBefore: 10 + time: 22:37 + totalBookings: + peakHours: 3 + offPeakHours: unlimited + sports: + - name: Sport1 + id: 22 + duration: 55 + price: 78 + players: 4 + courts: + - name: Court 1 + number: 1 + id: 54 + isIndoor: True + - name: Court 2 + number: 2 + id: 67 + isIndoor: False + - name: Court 3 + number: 3 + id: 26 + isIndoor: True + - name: Sport2 + id: 25 + duration: 22 + price: 3 + players: 2 + courts: + - name: Court 1 + id: 99 + number: 1 + isIndoor: True + - name: Court 2 + number: 2 + id: 101 + isIndoor: False + + - name: Club Pourri + url: https://www.clubpourri.fr + id: cp + bookingPlatform: + id: gestion-sports + clubId: 1111 + url: https://clubpourri.flapi.fr + hoursBeforeCancellation: 24 + bookingOpening: + daysBefore: 2 + time: 02:54 + totalBookings: + peakHours: 4 + offPeakHours: unlimited + sports: + - name: Sport1 + id: 465 + duration: 44 + price: 98 + players: 4 + courts: + - name: Court 7 + number: 15 + id: 987 + isIndoor: True + - name: prout prout + number: 555 + id: 747 + isIndoor: False + - name: Sport3 + id: 321 + duration: 11 + price: 18 + players: 2 + courts: + - name: Court 1 + id: 613 + isIndoor: True + number: 1 + - name: Court 2 + id: 614 + isIndoor: True + number: 2 diff --git a/tests/data/configuration/gestion-sports/booking-cancellation-payload.txt b/tests/data/configuration/gestion-sports/booking-cancellation-payload.txt new file mode 100644 index 0000000..2083999 --- /dev/null +++ b/tests/data/configuration/gestion-sports/booking-cancellation-payload.txt @@ -0,0 +1 @@ +ajax=removeResa&hash={{ hash }}&id={{ booking_id }} diff --git a/tests/data/configuration/gestion-sports/booking-payload.txt b/tests/data/configuration/gestion-sports/booking-payload.txt new file mode 100644 index 0000000..c416a11 --- /dev/null +++ b/tests/data/configuration/gestion-sports/booking-payload.txt @@ -0,0 +1 @@ +ajax=addResa&date={{ date.date().strftime("%d/%m/%Y") }}&hour={{ date.time().strftime("%H:%M") }}&duration=90&partners=null|null|null&paiement=facultatif&idSport={{ sport_id }}&creaPartie=false&idCourt={{ court_id }}&pay=false&token=undefined&totalPrice=48&saveCard=0&foodNumber=0 diff --git a/tests/data/configuration/gestion-sports/login-payload.txt b/tests/data/configuration/gestion-sports/login-payload.txt new file mode 100644 index 0000000..7692aed --- /dev/null +++ b/tests/data/configuration/gestion-sports/login-payload.txt @@ -0,0 +1 @@ +ajax=connexionUser&id_club={{ club.booking_platform.club_id }}&email={{ user.login }}&form_ajax=1&pass={{ user.password }}&compte=user&playeridonesignal=0&identifiant=identifiant&externCo=true diff --git a/tests/data/configuration/gestion-sports/post-headers.json b/tests/data/configuration/gestion-sports/post-headers.json new file mode 100644 index 0000000..8adccc1 --- /dev/null +++ b/tests/data/configuration/gestion-sports/post-headers.json @@ -0,0 +1,12 @@ +{ + "Connection": "keep-alive", + "Accept-Language": "en-US,en;q=0.5", + "Accept-Encoding": "gzip, deflate, br", + "DNT": "1", + "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", + "Accept": "application/json, text/javascript, */*; q=0.01", + "X-Requested-With": "XMLHttpRequest", + "Sec-Fetch-Dest": "empty", + "Sec-Fetch-Mode": "cors", + "Sec-Fetch-Site": "same-origin" +} diff --git a/tests/data/configuration/gestion-sports/user-bookings-payload.txt b/tests/data/configuration/gestion-sports/user-bookings-payload.txt new file mode 100644 index 0000000..bc971e6 --- /dev/null +++ b/tests/data/configuration/gestion-sports/user-bookings-payload.txt @@ -0,0 +1 @@ +ajax=loadResa&hash={{ hash }} diff --git a/tests/data/configuration/platforms.yaml b/tests/data/configuration/platforms.yaml new file mode 100644 index 0000000..f3cdc8e --- /dev/null +++ b/tests/data/configuration/platforms.yaml @@ -0,0 +1,19 @@ +platforms: + - name: flapi + url: https://flapi.fr/ + id: gestion-sports + urls: + - name: landing-page + path: /landing.html + - name: login + path: /login.html + payloadTemplate: gestion-sports/login-payload.txt + - name: booking + path: /booking.html + payloadTemplate: gestion-sports/booking-payload.txt + - name: user-bookings + path: /user_bookings.html + payloadTemplate: gestion-sports/user-bookings-payload.txt + - name: cancellation + path: /cancel.html + payloadTemplate: gestion-sports/booking-cancellation-payload.txt diff --git a/tests/data/configuration/users.yaml b/tests/data/configuration/users.yaml new file mode 100644 index 0000000..1d53d16 --- /dev/null +++ b/tests/data/configuration/users.yaml @@ -0,0 +1,13 @@ +clubs: + - id: tpc + users: + - login: padel.testing@jouf.fr + password: ridicule + - login: mateochal31@gmail.com + password: pleanyakis + - id: padeltolosa + users: + - login: padel.testing@jouf.fr + password: ridicule + - login: mateochal31@gmail.com + password: pleanyakis diff --git a/tests/data/responses/booking_failure.json b/tests/data/responses/booking_failure.json new file mode 100644 index 0000000..406f970 --- /dev/null +++ b/tests/data/responses/booking_failure.json @@ -0,0 +1,4 @@ +{ + "status": "error", + "message": "D\u00e9sol\u00e9 mais vous avez 1 r\u00e9servation en cours au Padel en heures pleines et le r\u00e9glement n'autorise qu'une r\u00e9servation en heures pleines \u00e0 la fois au Padel!" +} diff --git a/tests/data/responses/booking_success.json b/tests/data/responses/booking_success.json new file mode 100644 index 0000000..267ebee --- /dev/null +++ b/tests/data/responses/booking_success.json @@ -0,0 +1,5 @@ +{ + "status": "ok", + "message": "Merci, votre r\u00e9servation s'est bien effectu\u00e9e, vous allez recevoir un email avec le r\u00e9capitulatif de votre r\u00e9servation, pensez \u00e0 le conserver.", + "id_resa": 3609529 +} diff --git a/tests/data/responses/cancellation_response.json b/tests/data/responses/cancellation_response.json new file mode 100644 index 0000000..401ac46 --- /dev/null +++ b/tests/data/responses/cancellation_response.json @@ -0,0 +1,4 @@ +{ + "status": "ok", + "message": "La r\u00e9servation a bien \u00e9t\u00e9 annul\u00e9e !" +} diff --git a/tests/data/responses/landing_response.html b/tests/data/responses/landing_response.html new file mode 100644 index 0000000..71dab94 --- /dev/null +++ b/tests/data/responses/landing_response.html @@ -0,0 +1,2033 @@ + + + + Gestion-sports + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+ + + +
+ +
+ + + + + + + + + + +

Retour

+
+ + + +
+
+ +
+ + + +

Retour

+

Votre compte est déjà connu de nos services. + Grâce à la technologie GS Connect, vous serez automatiquement enregistré auprès de Toulouse Padel Club + avec les mêmes identifiants de connexion que vous utilisez dans notre réseau

+ +
+ + + + + +
+ Mot de passe oublié ? +
+ +
+ + + + + +
+ +
+

Sur quel espace souhaitez-vous vous connecter ?

+
+ + + +
+ + +
+ + + + +
+ +

Retour

+ +

Renseignez votre email ci-dessous et vous recevrez un mail avec un nouveau mot de passe.

+ +
+ + + +
+ +
+ + +
+
+ +
+

Retour

+ + +
+ + + +
+ +
+ + + +
+
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + + + +
+ +
+ + + +
+ +
+ + + +
+ +

Votre mot de passe doit + contenir minimum 6 caractères.

+
+ + + + + +
+ + +
+ + + +
+ +

Champs obligatoires.

+ +
+ + +
+ +
+ + +
+
+ +
+ +
+
+
+
+ + + +
+ + +
+ + + +
+ +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/data/responses/login_failure.json b/tests/data/responses/login_failure.json new file mode 100644 index 0000000..731ed87 --- /dev/null +++ b/tests/data/responses/login_failure.json @@ -0,0 +1,5 @@ +{ + "status": "ko", + "msg": "L'email ou le mot de passe saisi est incorrect.", + "data": false +} diff --git a/tests/data/responses/login_success.json b/tests/data/responses/login_success.json new file mode 100644 index 0000000..3a07b66 --- /dev/null +++ b/tests/data/responses/login_success.json @@ -0,0 +1,9 @@ +{ + "status": "ok", + "msg": "", + "data": { + "needChoice": false, + "redirectUrl": "\/membre", + "id_club": 88 + } +} diff --git a/tests/data/responses/user_bookings_get.html b/tests/data/responses/user_bookings_get.html new file mode 100644 index 0000000..fc473f0 --- /dev/null +++ b/tests/data/responses/user_bookings_get.html @@ -0,0 +1,1363 @@ + + + + + + Mes réservations + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+
+ + + Retour + + + + + +
+ +
+ Mes réservations
+ +
+ + + +
+
+
+ + +
+
+
Mes événements
+
+
+
+
+
+ +
+
Mes réservations
+ +
+
+
+
+
Suivi de vos réservations par sport
+
+ + Heure pleines + Heure creuses +
+
+ Padel + +
+
1 /1
+
1 /1
+
+
+ +
+
illimitées
+
illimitées
+
+
+
+
+ Squash + +
+
0 /2
+
0 /2
+
+
+ +
+
illimitées
+
illimitées
+
+
+
+
+ Electrostimulation + +
+
illimitées
+
illimitées
+
+
+ +
+
illimitées
+
illimitées
+
+
+
+
+
+ + +
+ + + + + + + +
+ + +
+ + + + + + + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + Accueil +
+
+ + +
+ + Actualités +
+
+ + +
+
+ + Réserver +
+
+ +
+ + +
+ + Compte +
+
+ + +
+ + Menu +
+
+ +
+ + + + + + + + + + + + + + + + + + +
+
+
+
+ image + Le Mas + à l'instant +
+ + + +
+
+
+

Titre de la notif

+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod + tempor incididunt ut labore et dolore magna aliqua. +
+
+
+
+
+ + + + + + + + + + + +
+
+ +
+ Votre inscription à la partie de Tennis a bien été enregistrée !
+ Rendez-vous le 14 Janvier de 16h30 à 17h30. +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/data/responses/user_bookings_post.json b/tests/data/responses/user_bookings_post.json new file mode 100644 index 0000000..e6c5218 --- /dev/null +++ b/tests/data/responses/user_bookings_post.json @@ -0,0 +1,52 @@ +[ + { + "id": 111, + "chargeId": null, + "partners": [], + "dateResa": "21\/03\/2024", + "startTime": "13:30", + "endTime": "15:00", + "dayFr": "jeudi 21 mars 2024", + "codeLiveXperience": null, + "qrCodeSpartime": null, + "sport": "Sport1", + "court": "court 13", + "creaPartie": 0, + "limitCreaPartie": "2024-03-21 11:30:00", + "cancel": true, + "bloquerRemplacementJoueur": 1, + "canRemovePartners": false, + "remainingPlaces": 3, + "isCaptain": true, + "dtStart": "2024-03-21T13:30:00+01:00", + "garantieCb": null, + "dureeValidCertif": null, + "playerStatus": 3, + "products": [] + }, + { + "id": 360, + "chargeId": null, + "partners": [], + "dateResa": "18\/11\/2025", + "startTime": "09:00", + "endTime": "10:30", + "dayFr": "vendredi 18 novembre 2025", + "codeLiveXperience": null, + "qrCodeSpartime": null, + "sport": "Sport1", + "court": "court 13", + "creaPartie": 0, + "limitCreaPartie": "2025-11-18 07:00:00", + "cancel": true, + "bloquerRemplacementJoueur": 1, + "canRemovePartners": false, + "remainingPlaces": 3, + "isCaptain": true, + "dtStart": "2025-11-18T07:00:00+01:00", + "garantieCb": null, + "dureeValidCertif": null, + "playerStatus": 3, + "products": [] + } +] diff --git a/tests/gestion_sports/test_gestion_sports_connector.py b/tests/gestion_sports/test_gestion_sports_connector.py deleted file mode 100644 index c279263..0000000 --- a/tests/gestion_sports/test_gestion_sports_connector.py +++ /dev/null @@ -1,185 +0,0 @@ -import pytest -from aiohttp import ClientSession -from models import BookingFilter, Club, User -from yarl import URL - -from resa_padel.gestion_sports.gestion_sports_connector import GestionSportsConnector -from tests.fixtures import ( - a_booking_failure_response, - a_booking_filter, - a_booking_success_response, - a_club, - a_user, -) - -tpc_url = "https://toulousepadelclub.gestion-sports.com" -TPC_COURTS = [ - None, - 596, - 597, - 598, - 599, - 600, - 601, - 602, - 603, - 604, - 605, - 606, - 607, - 608, - 609, - 610, - 611, -] - - -@pytest.mark.asyncio -async def test_should_reach_landing_page_to_gestion_sports_website() -> None: - """ - Test that landing page is reached - """ - async with ClientSession() as session: - cookies = session.cookie_jar.filter_cookies(URL(tpc_url)) - assert cookies.get("PHPSESSID") is None - gs_connector = GestionSportsConnector(session, tpc_url) - - response = await gs_connector.land() - - assert response.status == 200 - assert response.request_info.method == "GET" - assert response.content_type == "text/html" - assert response.request_info.url == URL(tpc_url + "/connexion.php") - assert response.charset == "UTF-8" - - cookies = session.cookie_jar.filter_cookies(URL(tpc_url)) - assert cookies.get("PHPSESSID") is not None - - -@pytest.mark.asyncio -async def test_should_login_to_gestion_sports_website( - a_user: User, a_club: Club -) -> None: - """ - Test that a user can log in after reaching the landing page - - :param a_user: the user that wants to book a court - :param a_club: the club information - """ - async with ClientSession() as session: - gs_connector = GestionSportsConnector(session, tpc_url) - await gs_connector.land() - - response = await gs_connector.login(a_user, a_club) - - assert response.status == 200 - assert response.request_info.url == URL(tpc_url + "/connexion.php") - assert response.request_info.method == "POST" - - cookies = session.cookie_jar.filter_cookies(URL(tpc_url)) - assert cookies.get("COOK_ID_CLUB").value is not None - assert cookies.get("COOK_ID_USER").value is not None - assert cookies.get("PHPSESSID") is not None - - -@pytest.mark.asyncio -@pytest.mark.slow -async def test_booking_url_should_be_reachable( - a_user: User, a_booking_filter: BookingFilter, a_club: Club -) -> None: - """ - Test that a user can log in the booking platform and book a court - - :param a_user: the user that wants to book a court - :param a_booking_filter: the booking information - :param a_club: the club information - """ - async with ClientSession() as session: - gs_connector = GestionSportsConnector(session, tpc_url) - await gs_connector.land() - await gs_connector.login(a_user, a_club) - - court_booked = await gs_connector.book(a_booking_filter, a_club) - # At 18:00 no chance to get a booking, any day of the week - assert court_booked in TPC_COURTS - - -@pytest.mark.asyncio -async def test_should_book_a_court_from_gestion_sports( - aioresponses, - a_booking_filter: BookingFilter, - a_club: Club, - a_booking_success_response: str, - a_booking_failure_response: str, -) -> None: - """ - Test that user can reach the landing page, then log in to the platform - and eventually book a court - - :param aioresponses: the http response mock - :param a_booking_filter: the booking information - :param a_club: the club information - :param a_booking_success_response: the success response mock - :param a_booking_failure_response: the failure response mock - """ - booking_url = URL(tpc_url + "/membre/reservation.html?") - - # first booking request will fail - aioresponses.post(booking_url, status=200, body=a_booking_failure_response) - # first booking request will succeed - aioresponses.post(booking_url, status=200, body=a_booking_success_response) - # first booking request will fail - aioresponses.post(booking_url, status=200, body=a_booking_failure_response) - - async with ClientSession() as session: - gs_connector = GestionSportsConnector(session, tpc_url) - court_booked = await gs_connector.book(a_booking_filter, a_club) - - # the second element of the list is the booked court - assert court_booked == a_club.courts_ids[1] - - -def test_response_status_should_be_ok(a_booking_success_response: str) -> None: - """ - Test internal method to verify that the success response received by booking - a gestion-sports court is still a JSON with a field 'status' set to 'ok' - - :param a_booking_success_response: the success response mock - """ - is_booked = GestionSportsConnector.is_booking_response_status_ok( - a_booking_success_response - ) - assert is_booked - - -def test_response_status_should_be_not_ok(a_booking_failure_response: str) -> None: - """ - Test internal method to verify that the failure response received by booking - a gestion-sports court is still a JSON with a field 'status' set to 'error' - - :param a_booking_failure_response: the failure response mock - """ - is_booked = GestionSportsConnector.is_booking_response_status_ok( - a_booking_failure_response - ) - assert not is_booked - - -@pytest.mark.asyncio -@pytest.mark.slow -async def test_get_user_ongoing_bookings(a_user: User, a_club: Club) -> None: - """ - Test that the user has 2 ongoing bookings - - :param a_user: - :param a_club: - :return: - """ - async with ClientSession() as session: - gs_connector = GestionSportsConnector(session, tpc_url) - await gs_connector.land() - await gs_connector.login(a_user, a_club) - - bookings = await gs_connector.get_ongoing_bookings() - - assert len(bookings) == 0 diff --git a/tests/gestion_sports/test_gestion_sports_html_parser.py b/tests/gestion_sports/test_gestion_sports_html_parser.py deleted file mode 100644 index 314e3e3..0000000 --- a/tests/gestion_sports/test_gestion_sports_html_parser.py +++ /dev/null @@ -1,8 +0,0 @@ -from gestion_sports import gestion_sports_html_parser as parser - -from tests.fixtures import mes_resas_html - - -def test_html_parser(mes_resas_html): - hash_value = parser.get_hash_input(mes_resas_html) - assert hash_value == "ef4403f4c44fa91060a92476aae011a2184323ec" diff --git a/tests/gestion_sports/test_gestion_sports_payload_builder.py b/tests/gestion_sports/test_gestion_sports_payload_builder.py deleted file mode 100644 index b494c55..0000000 --- a/tests/gestion_sports/test_gestion_sports_payload_builder.py +++ /dev/null @@ -1,62 +0,0 @@ -from payload_builders import ( - GestionSportsBookingPayloadBuilder, - GestionSportsLoginPayloadBuilder, - GestionSportsUsersBookingsPayloadBuilder, -) - -from tests.fixtures import a_booking_filter, a_club, a_user - - -def test_login_payload_should_be_built(a_user, a_club): - """ - Test that the login payload is filled with the right template - and filled accordingly - - :param a_user: the user information fixture - :param a_club: the club information fixture - """ - payload_builder = GestionSportsLoginPayloadBuilder() - login_payload = payload_builder.user(a_user).club(a_club).build() - - expected_payload = ( - f"ajax=connexionUser&id_club={a_club.id}&email={a_user.login}&form_ajax=1" - f"&pass={a_user.password}&compte=user&playeridonesignal=0" - f"&identifiant=identifiant&externCo=true" - ) - - assert login_payload == expected_payload - - -def test_booking_payload_should_be_built(a_booking_filter): - """ - Test that the booking payload is filled with the right template - and filled accordingly - - :param a_booking_filter: the booking information fixture - """ - booking_builder = GestionSportsBookingPayloadBuilder() - booking_payload = ( - booking_builder.booking_filter(a_booking_filter).court_id(4).build() - ) - - expected_date = a_booking_filter.date.date().strftime("%d/%m/%Y") - expected_time = a_booking_filter.date.time().strftime("%H:%M") - expected_payload = ( - f"ajax=addResa&date={expected_date}" - f"&hour={expected_time}&duration=90&partners=null|null|null" - f"&paiement=facultatif&idSport={a_booking_filter.sport_id}" - f"&creaPartie=false&idCourt=4&pay=false&token=undefined&totalPrice=44" - f"&saveCard=0&foodNumber=0" - ) - - assert booking_payload == expected_payload - - -def test_users_bookings_payload_should_be_built(): - builder = GestionSportsUsersBookingsPayloadBuilder() - builder.hash("super_hash") - expected_payload = "ajax=loadResa&hash=super_hash" - - actual_payload = builder.build() - - assert actual_payload == expected_payload diff --git a/tests/gestion_sports/test_gestion_sports_platform.py b/tests/gestion_sports/test_gestion_sports_platform.py deleted file mode 100644 index 7332bc5..0000000 --- a/tests/gestion_sports/test_gestion_sports_platform.py +++ /dev/null @@ -1,87 +0,0 @@ -from unittest.mock import patch - -import pendulum -import pytest -from aioresponses import aioresponses -from gestion_sports.gestion_sports_platform import ( - GestionSportsPlatform, - wait_until_booking_time, -) -from models import BookingFilter, Club, User - -from tests import fixtures, utils -from tests.fixtures import ( - a_booking_failure_response, - a_booking_filter, - a_booking_success_response, - a_club, - a_user, - mes_resas_html, -) - - -@pytest.mark.asyncio -@patch("pendulum.now") -async def test_booking( - mock_now, - a_booking_success_response: str, - a_booking_failure_response: str, - a_user: User, - a_club: Club, - a_booking_filter: BookingFilter, - mes_resas_html: str, -): - """ - Test a single court booking without reading the conf from environment variables - - :param mock_now: the pendulum.now() mock - :param a_booking_success_response: the success json response - :param a_booking_failure_response: the failure json response - :param a_user: a test user - :param a_club:a test club - :param a_booking_filter: a test booking filter - """ - booking_datetime = utils.retrieve_booking_datetime(a_booking_filter, a_club) - mock_now.side_effect = [booking_datetime] - - # mock connection to the booking platform - with aioresponses() as aio_mock: - utils.mock_rest_api_from_connection_to_booking( - aio_mock, - fixtures.url, - a_booking_failure_response, - a_booking_success_response, - mes_resas_html, - ) - - async with GestionSportsPlatform(a_club) as gs_operations: - court_booked = await gs_operations.book(a_user, a_booking_filter) - assert court_booked == a_club.courts_ids[1] - - -@patch("pendulum.now") -def test_wait_until_booking_time( - mock_now, a_club: Club, a_booking_filter: BookingFilter -): - """ - Test the function that waits until the booking can be performed - - :param mock_now: the pendulum.now() mock - :param a_club: a club - :param a_booking_filter: a booking filter - """ - booking_datetime = utils.retrieve_booking_datetime(a_booking_filter, a_club) - - seconds = [ - booking_datetime.subtract(seconds=3), - booking_datetime.subtract(seconds=2), - booking_datetime.subtract(seconds=1), - booking_datetime, - booking_datetime.add(microseconds=1), - booking_datetime.add(microseconds=2), - ] - mock_now.side_effect = seconds - - wait_until_booking_time(a_club, a_booking_filter) - - assert pendulum.now() == booking_datetime.add(microseconds=1) diff --git a/tests/gestion_sports/__init__.py b/tests/integration_tests/__init__.py similarity index 100% rename from tests/gestion_sports/__init__.py rename to tests/integration_tests/__init__.py diff --git a/tests/test_booking.py b/tests/integration_tests/test_booking.py similarity index 96% rename from tests/test_booking.py rename to tests/integration_tests/test_booking.py index 4de9767..7d738ed 100644 --- a/tests/test_booking.py +++ b/tests/integration_tests/test_booking.py @@ -14,7 +14,7 @@ from resa_padel import booking {"CLUB_ID": "tpc"}, clear=True, ) -def test_real_booking(): +def test_booking(): club = config.get_club() user = User(login="padel.testing@jouf.fr", password="ridicule") booking_filter = BookingFilter( @@ -32,7 +32,7 @@ def test_real_booking(): {"CLUB_ID": "tpc"}, clear=True, ) -def test_real_cancellation(): +def test_cancellation(): club = config.get_club() user = User(login="padel.testing@jouf.fr", password="ridicule") asyncio.run(booking.cancel_booking_id(club, user, 3605033)) diff --git a/tests/test_connectors.py b/tests/integration_tests/test_connectors.py similarity index 99% rename from tests/test_connectors.py rename to tests/integration_tests/test_connectors.py index 9f503ed..6c262bc 100644 --- a/tests/test_connectors.py +++ b/tests/integration_tests/test_connectors.py @@ -60,7 +60,7 @@ def test_urls_payload_templates(): == resources_folder / "user-bookings-payload.txt" ) assert ( - connector.booking_cancellation_template + connector.booking_cancel_template == resources_folder / "booking-cancellation-payload.txt" ) diff --git a/tests/unit_tests/__init__.py b/tests/unit_tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit_tests/conftest.py b/tests/unit_tests/conftest.py new file mode 100644 index 0000000..e301c8a --- /dev/null +++ b/tests/unit_tests/conftest.py @@ -0,0 +1,284 @@ +import json +from pathlib import Path + +import pendulum +import pytest +from connectors import GestionSportsConnector +from models import ( + BookingFilter, + BookingOpening, + BookingPlatform, + Club, + Court, + Sport, + TotalBookings, + Url, + User, +) + +TEST_FOLDER = Path(__file__).parent.parent +DATA_FOLDER = TEST_FOLDER / "data" +RESPONSES_FOLDER = DATA_FOLDER / "responses" + +court11 = Court(id="1", name="Court 1", number=1, isIndoor=True) +court12 = Court(id="2", name="Court 2", number=2, isIndoor=False) +court13 = Court(id="3", name="Court 3", number=3, isIndoor=True) +court14 = Court(id="4", name="Court 4", number=4, isIndoor=True) + +sport1 = Sport( + name="Sport1", + id=8, + duration=99, + price=54, + players=3, + courts=[court11, court12, court13, court14], +) + +court21 = Court(id="1", name="Court 1", number=1, isIndoor=False) +court22 = Court(id="2", name="Court 2", number=2, isIndoor=True) +court23 = Court(id="3", name="Court 3", number=3, isIndoor=True) +court24 = Court(id="4", name="Court 4", number=4, isIndoor=True) + +sport2 = Sport( + name="Sport 2", + id=10, + duration=44, + price=23, + players=1, + courts=[court21, court22, court23, court24], +) + +landing_url = Url( + name="landing-page", + path="landing.html", +) + +login_url = Url( + name="login", + path="login.html", + payloadTemplate="gestion-sports/login-payload.txt", +) + +booking_url = Url( + name="booking", + path="booking.html", + payloadTemplate="gestion-sports/booking-payload.txt", +) + +user_bookings_url = Url( + name="user-bookings", + path="user_bookings.html", + payloadTemplate="gestion-sports/user-bookings-payload.txt", +) + +cancellation_url = Url( + name="cancellation", + path="cancel.html", + payloadTemplate="gestion-sports/booking-cancellation-payload.txt", +) + +booking_opening = BookingOpening(daysBefore=10, time="03:27") + +total_bookings = TotalBookings(peakHours=3, offPeakHours="unlimited") + +booking_platform = BookingPlatform( + id="gestion-sports", + clubId=21, + url="https://ptf1.com", + hoursBeforeCancellation=7, + bookingOpening=booking_opening, + totalBookings=total_bookings, + sports=[sport1, sport2], + urls={ + "landing-page": landing_url, + "login": login_url, + "booking": booking_url, + "user-bookings": user_bookings_url, + "cancellation": cancellation_url, + }, +) + +club = Club( + id="super_club", + name="Super Club", + url="https://superclub.com", + bookingPlatform=booking_platform, +) + + +@pytest.fixture +def a_club() -> Club: + return club + + +@pytest.fixture +def connector() -> GestionSportsConnector: + return GestionSportsConnector(club) + + +@pytest.fixture +def user() -> User: + return User(login="padel.testing@jouf.fr", password="ridicule") + + +@pytest.fixture +def booking_filter() -> BookingFilter: + return BookingFilter( + sport_name="Sport1", date=pendulum.parse("2024-03-21T13:30:00Z") + ) + + +@pytest.fixture +def landing_response() -> str: + landing_response_file = RESPONSES_FOLDER / "landing_response.html" + return landing_response_file.read_text(encoding="utf-8") + + +@pytest.fixture +def login_success_response() -> dict: + login_success_file = RESPONSES_FOLDER / "login_success.json" + return json.loads(login_success_file.read_text(encoding="utf-8")) + + +@pytest.fixture +def login_failure_response() -> dict: + login_failure_file = RESPONSES_FOLDER / "login_failure.json" + return json.loads(login_failure_file.read_text(encoding="utf-8")) + + +@pytest.fixture +def booking_success_response() -> dict: + booking_success_file = RESPONSES_FOLDER / "booking_success.json" + return json.loads(booking_success_file.read_text(encoding="utf-8")) + + +@pytest.fixture +def booking_failure_response() -> dict: + booking_failure_file = RESPONSES_FOLDER / "booking_failure.json" + return json.loads(booking_failure_file.read_text(encoding="utf-8")) + + +@pytest.fixture +def booking_success_from_start( + landing_response, + login_success_response, + booking_success_response, + booking_failure_response, +): + return [ + landing_response, + login_success_response, + booking_failure_response, + booking_success_response, + booking_failure_response, + booking_failure_response, + ] + + +@pytest.fixture +def booking_failure_from_start( + landing_response, + login_success_response, + booking_success_response, + booking_failure_response, +): + return [ + landing_response, + login_success_response, + booking_failure_response, + booking_failure_response, + booking_failure_response, + booking_failure_response, + ] + + +@pytest.fixture +def user_bookings_get_response() -> str: + user_bookings_file = RESPONSES_FOLDER / "user_bookings_get.html" + return user_bookings_file.read_text(encoding="utf-8") + + +@pytest.fixture +def user_bookings_list() -> list: + user_bookings_file = RESPONSES_FOLDER / "user_bookings_post.json" + return json.loads(user_bookings_file.read_text(encoding="utf-8")) + + +@pytest.fixture +def user_has_ongoing_bookings_from_start( + landing_response, + login_success_response, + user_bookings_get_response, + user_bookings_list, +) -> list: + return [ + landing_response, + login_success_response, + user_bookings_get_response, + user_bookings_list, + ] + + +@pytest.fixture +def user_bookings_empty_list() -> list: + return [] + + +@pytest.fixture +def user_has_no_ongoing_bookings_from_start( + landing_response, + login_success_response, + user_bookings_get_response, + user_bookings_empty_list, +) -> list: + return [ + landing_response, + login_success_response, + user_bookings_get_response, + user_bookings_empty_list, + ] + + +@pytest.fixture +def cancellation_response() -> list: + cancellation_response_file = RESPONSES_FOLDER / "cancellation_response.json" + return json.loads(cancellation_response_file.read_text(encoding="utf-8")) + + +@pytest.fixture +def cancellation_by_id_from_start( + landing_response, + login_success_response, + user_bookings_get_response, + cancellation_response, +): + return [ + landing_response, + login_success_response, + user_bookings_get_response, + cancellation_response, + ] + + +@pytest.fixture +def cancellation_success_from_start( + landing_response, + login_success_response, + user_bookings_get_response, + user_bookings_list, + cancellation_response, +): + return [ + landing_response, + login_success_response, + user_bookings_get_response, + user_bookings_list, + cancellation_response, + ] + + +@pytest.fixture +def cancellation_success_booking_filter() -> BookingFilter: + return BookingFilter( + sport_name="Sport1", date=pendulum.parse("2024-03-21T13:30:00Z") + ) diff --git a/tests/unit_tests/test_booking.py b/tests/unit_tests/test_booking.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit_tests/test_cancellation.py b/tests/unit_tests/test_cancellation.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_config.py b/tests/unit_tests/test_config.py similarity index 100% rename from tests/test_config.py rename to tests/unit_tests/test_config.py diff --git a/tests/unit_tests/test_gestion_sports_connector.py b/tests/unit_tests/test_gestion_sports_connector.py new file mode 100644 index 0000000..7ba5b52 --- /dev/null +++ b/tests/unit_tests/test_gestion_sports_connector.py @@ -0,0 +1,315 @@ +from pathlib import Path +from unittest.mock import patch + +import pytest +from aiohttp import ClientSession +from connectors import GestionSportsConnector + + +def make_landing_request_success(aioresponses, connector, landing_response): + aioresponses.get( + connector.landing_url, + status=200, + headers={"Set-Cookie": "PHPSESSID=987512"}, + body=landing_response, + ) + + +def make_login_request_fail(aioresponses, connector, login_failure_response): + aioresponses.post( + connector.login_url, + status=200, + payload=login_failure_response, + ) + + +def make_login_request_success(aioresponses, connector, login_success_response): + aioresponses.post( + connector.login_url, + status=200, + headers={"Set-Cookie": "COOK_COMPTE=e2be1;" "COOK_ID_CLUB=22;COOK_ID_USER=666"}, + payload=login_success_response, + ) + + +def set_booking_request(aioresponses, connector, booking_response): + aioresponses.post(connector.booking_url, status=200, payload=booking_response) + + +def set_full_booking_requests_responses(aioresponses, connector, responses_list): + make_landing_request_success(aioresponses, connector, responses_list[0]) + make_login_request_success(aioresponses, connector, responses_list[1]) + for response in responses_list[2:]: + set_booking_request(aioresponses, connector, response) + + +def set_ongoing_bookings_response( + aioresponses, connector, user_bookings_get_response, user_bookings_post_response +): + set_hash_response(aioresponses, connector, user_bookings_get_response) + set_bookings_response(aioresponses, connector, user_bookings_post_response) + + +def set_hash_response(aioresponses, connector, user_bookings_get_response): + aioresponses.get( + connector.user_bookings_url, status=200, body=user_bookings_get_response + ) + + +def set_bookings_response(aioresponses, connector, user_bookings_post_response): + aioresponses.post( + connector.user_bookings_url, status=200, payload=user_bookings_post_response + ) + + +def set_full_user_bookings_responses(aioresponses, connector, responses): + make_landing_request_success(aioresponses, connector, responses[0]) + make_login_request_success(aioresponses, connector, responses[1]) + set_ongoing_bookings_response(aioresponses, connector, *responses[2:]) + + +def set_cancellation_response(aioresponses, connector, response): + aioresponses.post(connector.booking_cancellation_url, status=200, payload=response) + + +def set_full_cancellation_by_id_responses(aioresponses, connector, responses): + make_landing_request_success(aioresponses, connector, responses[0]) + make_login_request_success(aioresponses, connector, responses[1]) + set_hash_response(aioresponses, connector, responses[2]) + set_cancellation_response(aioresponses, connector, responses[3]) + + +def set_full_cancellation_responses(aioresponses, connector, responses): + make_landing_request_success(aioresponses, connector, responses[0]) + make_login_request_success(aioresponses, connector, responses[1]) + + # the request to get the hash is made twice + set_hash_response(aioresponses, connector, responses[2]) + set_hash_response(aioresponses, connector, responses[2]) + + set_bookings_response(aioresponses, connector, responses[3]) + set_cancellation_response(aioresponses, connector, responses[4]) + + +def test_urls(a_club): + connector = GestionSportsConnector(a_club) + base_url = a_club.booking_platform.url + relative_urls = a_club.booking_platform.urls + + relative_landing_url = relative_urls.get("landing-page").path + assert connector.landing_url == f"{base_url}/{relative_landing_url}" + + relative_login_url = relative_urls.get("login").path + assert connector.login_url == f"{base_url}/{relative_login_url}" + + relative_booking_url = relative_urls.get("booking").path + assert connector.booking_url == f"{base_url}/{relative_booking_url}" + + relative_user_bookings_url = relative_urls.get("user-bookings").path + assert connector.user_bookings_url == f"{base_url}/{relative_user_bookings_url}" + + relative_cancel_url = relative_urls.get("cancellation").path + assert connector.booking_cancellation_url == f"{base_url}/{relative_cancel_url}" + + +@patch("config.get_resources_folder") +def test_urls_payload_templates(mock_resources, a_club): + path_to_resources = Path("some/path/to/resource") + mock_resources.return_value = path_to_resources + + connector = GestionSportsConnector(a_club) + relative_urls = a_club.booking_platform.urls + + login_payload = relative_urls.get("login").payload_template + assert connector.login_template == path_to_resources / login_payload + + booking_payload = relative_urls.get("booking").payload_template + assert connector.booking_template == path_to_resources / booking_payload + + user_bookings_payload = relative_urls.get("user-bookings").payload_template + assert connector.user_bookings_template == path_to_resources / user_bookings_payload + + cancel_payload = relative_urls.get("cancellation").payload_template + assert connector.booking_cancel_template == path_to_resources / cancel_payload + + +@pytest.mark.asyncio +async def test_landing_page(aioresponses, connector, landing_response): + make_landing_request_success(aioresponses, connector, landing_response) + + async with ClientSession() as session: + response = await connector.land(session) + + assert response.status == 200 + assert response.cookies.get("PHPSESSID").value == "987512" + assert await response.text() == landing_response + + +@pytest.mark.asyncio +async def test_login_success(aioresponses, connector, user, login_success_response): + make_login_request_success(aioresponses, connector, login_success_response) + + async with ClientSession() as session: + response = await connector.login(session, user) + + assert response.status == 200 + assert response.cookies.get("COOK_COMPTE").value == "e2be1" + assert response.cookies.get("COOK_ID_CLUB").value == "22" + assert response.cookies.get("COOK_ID_USER").value == "666" + assert await response.json() == login_success_response + + +@pytest.mark.asyncio +async def test_login_failure(aioresponses, connector, user, login_failure_response): + make_login_request_fail(aioresponses, connector, login_failure_response) + + async with ClientSession() as session: + response = await connector.login(session, user) + + assert response.status == 200 + assert len(response.cookies) == 0 + assert await response.json() == login_failure_response + + +@pytest.mark.asyncio +async def test_booking_success( + aioresponses, + connector, + user, + booking_filter, + booking_success_from_start, +): + set_full_booking_requests_responses( + aioresponses, connector, booking_success_from_start + ) + + court_booked = await connector.book(user, booking_filter) + + assert court_booked.id == 2 + + +@pytest.mark.asyncio +async def test_booking_failure( + aioresponses, + connector, + user, + booking_filter, + booking_failure_from_start, +): + set_full_booking_requests_responses( + aioresponses, connector, booking_failure_from_start + ) + + court_booked = await connector.book(user, booking_filter) + + assert court_booked is None + + +@pytest.mark.asyncio +async def test_get_ongoing_bookings( + aioresponses, + connector, + user, + user_bookings_get_response, + user_bookings_list, +): + set_ongoing_bookings_response( + aioresponses, connector, user_bookings_get_response, user_bookings_list + ) + + async with ClientSession() as session: + bookings = await connector.get_ongoing_bookings(session) + + assert len(bookings) == 2 + + +@pytest.mark.asyncio +async def test_get_ongoing_bookings( + aioresponses, + connector, + user, + user_bookings_get_response, + user_bookings_list, +): + set_ongoing_bookings_response( + aioresponses, connector, user_bookings_get_response, user_bookings_list + ) + + async with ClientSession() as session: + bookings = await connector.get_ongoing_bookings(session) + + assert len(bookings) == 2 + + +@pytest.mark.asyncio +async def test_has_user_ongoing_bookings( + aioresponses, + connector, + user, + user_has_ongoing_bookings_from_start, +): + set_full_user_bookings_responses( + aioresponses, connector, user_has_ongoing_bookings_from_start + ) + + has_bookings = await connector.has_user_ongoing_booking(user) + + assert has_bookings + + +@pytest.mark.asyncio +async def test_has_user_ongoing_bookings( + aioresponses, + connector, + user, + user_has_no_ongoing_bookings_from_start, +): + set_full_user_bookings_responses( + aioresponses, connector, user_has_no_ongoing_bookings_from_start + ) + has_bookings = await connector.has_user_ongoing_booking(user) + + assert not has_bookings + + +@pytest.mark.asyncio +async def test_cancellation_request( + aioresponses, connector, user_bookings_get_response, cancellation_response +): + set_hash_response(aioresponses, connector, user_bookings_get_response) + set_cancellation_response(aioresponses, connector, cancellation_response) + + async with ClientSession() as session: + response = await connector.send_cancellation_request(session, 123) + + assert await response.json() == cancellation_response + + +@pytest.mark.asyncio +async def test_cancel_booking_id( + aioresponses, connector, user, cancellation_by_id_from_start +): + set_full_cancellation_by_id_responses( + aioresponses, connector, cancellation_by_id_from_start + ) + + response = await connector.cancel_booking_id(user, 132) + + assert await response.json() == cancellation_by_id_from_start[3] + + +@pytest.mark.asyncio +async def test_cancel_booking_success( + aioresponses, + connector, + user, + cancellation_success_booking_filter, + cancellation_success_from_start, +): + set_full_cancellation_responses( + aioresponses, connector, cancellation_success_from_start + ) + + response = await connector.cancel_booking(user, cancellation_success_booking_filter) + + assert await response.json() == cancellation_success_from_start[4] diff --git a/tests/utils.py b/tests/utils.py index c05ff6b..7d769c0 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,59 +1,8 @@ -from urllib.parse import urljoin - import pendulum from models import BookingFilter, Club from pendulum import DateTime -from tests.fixtures import ( - a_booking_failure_response, - a_booking_filter, - a_booking_success_response, - mes_resas_html, -) - - -def mock_successful_connection(aio_mock, url): - """ - Mock a call to the connection endpoint - - :param aio_mock: the aioresponses mock object - :param url: the URL of the connection endpoint - """ - aio_mock.get( - url, - status=200, - headers={"Set-Cookie": f"connection_called=True; Domain={url}"}, - ) - - -def mock_successful_login(aio_mock, url): - """ - Mock a call to the login endpoint - - :param aio_mock: the aioresponses mock object - :param url: the URL of the login endpoint - """ - aio_mock.post( - url, - status=200, - headers={"Set-Cookie": f"login_called=True; Domain={url}"}, - ) - - -def mock_booking(aio_mock, url, response): - """ - Mock a call to the booking endpoint - - :param aio_mock: the aioresponses mock object - :param url: the URL of the booking endpoint - :param response: the response from the booking endpoint - """ - aio_mock.post( - url, - status=200, - headers={"Set-Cookie": f"booking_called=True; Domain={url}"}, - body=response, - ) +from tests.fixtures import a_booking_filter def retrieve_booking_datetime( @@ -74,48 +23,3 @@ def retrieve_booking_datetime( return date_to_book.subtract(days=booking_opening.days_before).at( booking_hour, booking_minute ) - - -def mock_get_users_booking(aio_mock, url: str, booking_response: str): - return aio_mock.get(url, body=booking_response) - - -def mock_post_users_booking(aio_mock, url: str): - return aio_mock.post(url, payload=[]) - - -def mock_rest_api_from_connection_to_booking( - aio_mock, - url: str, - a_booking_failure_response: str, - a_booking_success_response: str, - mes_resas_html: str, -): - """ - Mock a REST API from a club. - It mocks the calls to the connexion to the website, a call to log in the user - and 2 calls to the booking endpoint - - :param aio_mock: the pendulum.now() mock - :param url: the API root URL - :param a_booking_success_response: the success json response - :param a_booking_failure_response: the failure json response - :param mes_resas_html: the html response for getting the bookings - :return: - """ - connexion_url = urljoin(url, "/connexion.php?") - mock_successful_connection(aio_mock, connexion_url) - mock_successful_connection(aio_mock, connexion_url) - - login_url = urljoin(url, "/connexion.php?") - mock_successful_login(aio_mock, login_url) - mock_successful_login(aio_mock, login_url) - - users_bookings_url = urljoin(url, "/membre/mesresas.html") - mock_get_users_booking(aio_mock, users_bookings_url, mes_resas_html) - mock_post_users_booking(aio_mock, users_bookings_url) - - booking_url = urljoin(url, "/membre/reservation.html?") - mock_booking(aio_mock, booking_url, a_booking_failure_response) - mock_booking(aio_mock, booking_url, a_booking_success_response) - mock_booking(aio_mock, booking_url, a_booking_failure_response)