Added a lot of unit tests
This commit is contained in:
parent
0938fb98b7
commit
16d4a0724c
32 changed files with 4268 additions and 497 deletions
|
@ -80,7 +80,7 @@ class GestionSportsConnector(Connector):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"Gestion Sports connector was instantiated with a club not handled"
|
"Gestion Sports connector was instantiated with a club not handled"
|
||||||
" by gestions sports. Club id is {} instead of gestion-sports".format(
|
" 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")
|
return self._get_url_path("cancellation")
|
||||||
|
|
||||||
@property
|
@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
|
Get the payload template to send to get all the user's bookings that are
|
||||||
available
|
available
|
||||||
|
@ -519,8 +519,9 @@ class GestionSportsConnector(Connector):
|
||||||
:return: the response from the client
|
:return: the response from the client
|
||||||
"""
|
"""
|
||||||
hash_value = await self.send_hash_request(session)
|
hash_value = await self.send_hash_request(session)
|
||||||
|
|
||||||
payload = PayloadBuilder.build(
|
payload = PayloadBuilder.build(
|
||||||
self.booking_cancellation_template,
|
self.booking_cancel_template,
|
||||||
booking_id=booking_id,
|
booking_id=booking_id,
|
||||||
hash=hash_value,
|
hash=hash_value,
|
||||||
)
|
)
|
||||||
|
@ -531,7 +532,9 @@ class GestionSportsConnector(Connector):
|
||||||
await response.text()
|
await response.text()
|
||||||
return response
|
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
|
Cancel the booking that meets some conditions
|
||||||
|
|
||||||
|
@ -545,54 +548,5 @@ class GestionSportsConnector(Connector):
|
||||||
bookings = await self.get_ongoing_bookings(session)
|
bookings = await self.get_ongoing_bookings(session)
|
||||||
|
|
||||||
for booking in bookings:
|
for booking in bookings:
|
||||||
if self.is_booking_matching_filter(booking, booking_filter):
|
if booking.matches(booking_filter):
|
||||||
await self.send_cancellation_request(session, booking.id)
|
return 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()
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ from enum import Enum
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import pendulum
|
import pendulum
|
||||||
|
from pendulum import Date, Time
|
||||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||||
from pydantic_extra_types.pendulum_dt import DateTime
|
from pydantic_extra_types.pendulum_dt import DateTime
|
||||||
|
|
||||||
|
@ -165,6 +166,46 @@ class Booking(BaseModel):
|
||||||
def to_lower_case(cls, d: str) -> str:
|
def to_lower_case(cls, d: str) -> str:
|
||||||
return d.lower()
|
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):
|
class Action(Enum):
|
||||||
BOOK = "book"
|
BOOK = "book"
|
||||||
|
|
92
tests/data/configuration/clubs.yaml
Normal file
92
tests/data/configuration/clubs.yaml
Normal file
|
@ -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
|
|
@ -0,0 +1 @@
|
||||||
|
ajax=removeResa&hash={{ hash }}&id={{ booking_id }}
|
|
@ -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
|
|
@ -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
|
12
tests/data/configuration/gestion-sports/post-headers.json
Normal file
12
tests/data/configuration/gestion-sports/post-headers.json
Normal file
|
@ -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"
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
ajax=loadResa&hash={{ hash }}
|
19
tests/data/configuration/platforms.yaml
Normal file
19
tests/data/configuration/platforms.yaml
Normal file
|
@ -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
|
13
tests/data/configuration/users.yaml
Normal file
13
tests/data/configuration/users.yaml
Normal file
|
@ -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
|
4
tests/data/responses/booking_failure.json
Normal file
4
tests/data/responses/booking_failure.json
Normal file
|
@ -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!"
|
||||||
|
}
|
5
tests/data/responses/booking_success.json
Normal file
5
tests/data/responses/booking_success.json
Normal file
|
@ -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
|
||||||
|
}
|
4
tests/data/responses/cancellation_response.json
Normal file
4
tests/data/responses/cancellation_response.json
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"status": "ok",
|
||||||
|
"message": "La r\u00e9servation a bien \u00e9t\u00e9 annul\u00e9e !"
|
||||||
|
}
|
2033
tests/data/responses/landing_response.html
Normal file
2033
tests/data/responses/landing_response.html
Normal file
File diff suppressed because it is too large
Load diff
5
tests/data/responses/login_failure.json
Normal file
5
tests/data/responses/login_failure.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"status": "ko",
|
||||||
|
"msg": "L'email ou le mot de passe saisi est incorrect.",
|
||||||
|
"data": false
|
||||||
|
}
|
9
tests/data/responses/login_success.json
Normal file
9
tests/data/responses/login_success.json
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"status": "ok",
|
||||||
|
"msg": "",
|
||||||
|
"data": {
|
||||||
|
"needChoice": false,
|
||||||
|
"redirectUrl": "\/membre",
|
||||||
|
"id_club": 88
|
||||||
|
}
|
||||||
|
}
|
1363
tests/data/responses/user_bookings_get.html
Normal file
1363
tests/data/responses/user_bookings_get.html
Normal file
File diff suppressed because one or more lines are too long
52
tests/data/responses/user_bookings_post.json
Normal file
52
tests/data/responses/user_bookings_post.json
Normal file
|
@ -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": []
|
||||||
|
}
|
||||||
|
]
|
|
@ -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
|
|
|
@ -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"
|
|
|
@ -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
|
|
|
@ -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)
|
|
|
@ -14,7 +14,7 @@ from resa_padel import booking
|
||||||
{"CLUB_ID": "tpc"},
|
{"CLUB_ID": "tpc"},
|
||||||
clear=True,
|
clear=True,
|
||||||
)
|
)
|
||||||
def test_real_booking():
|
def test_booking():
|
||||||
club = config.get_club()
|
club = config.get_club()
|
||||||
user = User(login="padel.testing@jouf.fr", password="ridicule")
|
user = User(login="padel.testing@jouf.fr", password="ridicule")
|
||||||
booking_filter = BookingFilter(
|
booking_filter = BookingFilter(
|
||||||
|
@ -32,7 +32,7 @@ def test_real_booking():
|
||||||
{"CLUB_ID": "tpc"},
|
{"CLUB_ID": "tpc"},
|
||||||
clear=True,
|
clear=True,
|
||||||
)
|
)
|
||||||
def test_real_cancellation():
|
def test_cancellation():
|
||||||
club = config.get_club()
|
club = config.get_club()
|
||||||
user = User(login="padel.testing@jouf.fr", password="ridicule")
|
user = User(login="padel.testing@jouf.fr", password="ridicule")
|
||||||
asyncio.run(booking.cancel_booking_id(club, user, 3605033))
|
asyncio.run(booking.cancel_booking_id(club, user, 3605033))
|
|
@ -60,7 +60,7 @@ def test_urls_payload_templates():
|
||||||
== resources_folder / "user-bookings-payload.txt"
|
== resources_folder / "user-bookings-payload.txt"
|
||||||
)
|
)
|
||||||
assert (
|
assert (
|
||||||
connector.booking_cancellation_template
|
connector.booking_cancel_template
|
||||||
== resources_folder / "booking-cancellation-payload.txt"
|
== resources_folder / "booking-cancellation-payload.txt"
|
||||||
)
|
)
|
||||||
|
|
0
tests/unit_tests/__init__.py
Normal file
0
tests/unit_tests/__init__.py
Normal file
284
tests/unit_tests/conftest.py
Normal file
284
tests/unit_tests/conftest.py
Normal file
|
@ -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")
|
||||||
|
)
|
0
tests/unit_tests/test_booking.py
Normal file
0
tests/unit_tests/test_booking.py
Normal file
0
tests/unit_tests/test_cancellation.py
Normal file
0
tests/unit_tests/test_cancellation.py
Normal file
315
tests/unit_tests/test_gestion_sports_connector.py
Normal file
315
tests/unit_tests/test_gestion_sports_connector.py
Normal file
|
@ -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]
|
|
@ -1,59 +1,8 @@
|
||||||
from urllib.parse import urljoin
|
|
||||||
|
|
||||||
import pendulum
|
import pendulum
|
||||||
from models import BookingFilter, Club
|
from models import BookingFilter, Club
|
||||||
from pendulum import DateTime
|
from pendulum import DateTime
|
||||||
|
|
||||||
from tests.fixtures import (
|
from tests.fixtures import a_booking_filter
|
||||||
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,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def retrieve_booking_datetime(
|
def retrieve_booking_datetime(
|
||||||
|
@ -74,48 +23,3 @@ def retrieve_booking_datetime(
|
||||||
return date_to_book.subtract(days=booking_opening.days_before).at(
|
return date_to_book.subtract(days=booking_opening.days_before).at(
|
||||||
booking_hour, booking_minute
|
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)
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue