Merge pull request 'All methods are in the right class' (#21) from refactor-code-and-comments into main
Reviewed-on: https://jouf.fr/gitea/stanislas/resa-padel/pulls/21
This commit is contained in:
commit
cea772371e
12 changed files with 585 additions and 1729 deletions
27
gd.json
Normal file
27
gd.json
Normal file
|
@ -0,0 +1,27 @@
|
|||
[
|
||||
{
|
||||
"id": 3628098,
|
||||
"chargeId": null,
|
||||
"partners": [],
|
||||
"dateResa": "28\/03\/2024",
|
||||
"startTime": "13:30",
|
||||
"endTime": "15:00",
|
||||
"dayFr": "jeudi 28 mars 2024",
|
||||
"codeLiveXperience": null,
|
||||
"qrCodeSpartime": null,
|
||||
"sport": "Padel",
|
||||
"court": "court 11",
|
||||
"creaPartie": 0,
|
||||
"limitCreaPartie": "2024-03-28 11:30:00",
|
||||
"cancel": true,
|
||||
"bloquerRemplacementJoueur": 1,
|
||||
"canRemovePartners": false,
|
||||
"remainingPlaces": 3,
|
||||
"isCaptain": true,
|
||||
"dtStart": "2024-03-28T13:30:00+01:00",
|
||||
"garantieCb": null,
|
||||
"dureeValidCertif": null,
|
||||
"playerStatus": 3,
|
||||
"products": []
|
||||
}
|
||||
]
|
|
@ -39,24 +39,12 @@ async def cancel_booking(club: Club, user: User, booking_filter: BookingFilter)
|
|||
await service.cancel_booking(user, club, booking_filter)
|
||||
|
||||
|
||||
async def cancel_booking_id(club: Club, user: User, booking_id: int) -> None:
|
||||
"""
|
||||
Cancel a booking that matches the booking id
|
||||
|
||||
:param club: the club in which the booking was made
|
||||
:param user: the user who made the booking
|
||||
:param booking_id: the id of the booking to cancel
|
||||
"""
|
||||
service = GestionSportsServices()
|
||||
await service.cancel_booking_id(user, club, booking_id)
|
||||
|
||||
|
||||
async def get_tournaments(club: Club, user: User) -> list[Tournament]:
|
||||
"""
|
||||
Cancel a booking that matches the booking id
|
||||
Get the list of all current tournaments, their price, date and availability
|
||||
|
||||
:param club: the club in which the booking was made
|
||||
:param user: the user who made the booking
|
||||
:param club: the club in which the tournaments are
|
||||
:param user: a user of the club in order to retrieve the information
|
||||
"""
|
||||
service = GestionSportsServices()
|
||||
return await service.get_all_tournaments(user, club)
|
||||
|
@ -74,17 +62,23 @@ def main() -> tuple[Court, User] | list[Tournament] | None:
|
|||
club = config.get_club()
|
||||
users = config.get_users(club.id)
|
||||
booking_filter = config.get_booking_filter()
|
||||
LOGGER.info(
|
||||
f"Booking a court of {booking_filter.sport_name} at {booking_filter.date} "
|
||||
f"at club {club.name}"
|
||||
)
|
||||
court_booked, user = asyncio.run(book_court(club, users, booking_filter))
|
||||
if court_booked:
|
||||
LOGGER.info(
|
||||
"Court %s booked successfully at %s for user %s",
|
||||
court_booked,
|
||||
booking_filter.date,
|
||||
user,
|
||||
f"Court of {booking_filter.sport_name} {court_booked} was booked "
|
||||
f"successfully at {booking_filter.date} at club {club.name} "
|
||||
f"for user {user}"
|
||||
)
|
||||
return court_booked, user
|
||||
else:
|
||||
LOGGER.info("Booking did not work")
|
||||
LOGGER.info(
|
||||
f"No court of {booking_filter.sport_name} at {booking_filter.date} "
|
||||
f"at club {club.name} was booked"
|
||||
)
|
||||
|
||||
elif action == Action.CANCEL:
|
||||
user = config.get_user()
|
||||
|
|
|
@ -1,2 +1,6 @@
|
|||
class ArgumentMissing(Exception):
|
||||
class WrongResponseStatus(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class MissingProperty(Exception):
|
||||
pass
|
||||
|
|
|
@ -2,12 +2,11 @@ import asyncio
|
|||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from urllib.parse import urljoin
|
||||
|
||||
import config
|
||||
from aiohttp import ClientResponse, ClientSession
|
||||
from bs4 import BeautifulSoup
|
||||
from models import Booking, BookingFilter, Club, Court, Sport, User
|
||||
from exceptions import WrongResponseStatus
|
||||
from models import BookingFilter, Club, Sport, User
|
||||
from payload_builders import PayloadBuilder
|
||||
from pendulum import DateTime
|
||||
|
||||
|
@ -18,8 +17,8 @@ POST_HEADERS = config.get_post_headers("gestion-sports")
|
|||
|
||||
class GestionSportsConnector:
|
||||
"""
|
||||
The connector for the Gestion Sports platform.
|
||||
It handles all the requests to the website.
|
||||
The connector for the Gestion Sports platform handles all the HTTP requests to the
|
||||
Gestion sports website.
|
||||
"""
|
||||
|
||||
def __init__(self, club: Club):
|
||||
|
@ -35,184 +34,139 @@ class GestionSportsConnector:
|
|||
|
||||
self.club = club
|
||||
|
||||
def _get_url_path(self, name: str) -> str:
|
||||
"""
|
||||
Get the URL path for the service with the given name
|
||||
|
||||
:param name: the name of the service
|
||||
:return: the URL path
|
||||
"""
|
||||
self._check_url_path_exists(name)
|
||||
|
||||
return urljoin(
|
||||
self.club.booking_platform.url,
|
||||
self.club.booking_platform.urls.get(name).path,
|
||||
)
|
||||
|
||||
def _get_url_parameter(self, name: str) -> str:
|
||||
self._check_url_path_exists(name)
|
||||
|
||||
return self.club.booking_platform.urls.get(name).parameter
|
||||
|
||||
def _get_payload_template(self, name: str) -> Path:
|
||||
"""
|
||||
Get the path to the template file for the service with the given name
|
||||
|
||||
:param name: the name of the service
|
||||
:return: the path to the template file
|
||||
"""
|
||||
self._check_payload_template_exists(name)
|
||||
|
||||
return (
|
||||
config.get_resources_folder()
|
||||
/ self.club.booking_platform.urls.get(name).payload_template
|
||||
)
|
||||
|
||||
def _check_url_path_exists(self, name: str) -> None:
|
||||
"""
|
||||
Check that the URL path for the given service is defined
|
||||
|
||||
:param name: the name of the service
|
||||
"""
|
||||
if (
|
||||
self.club.booking_platform.urls is None
|
||||
or self.club.booking_platform.urls.get(name) is None
|
||||
or self.club.booking_platform.urls.get(name).path is None
|
||||
):
|
||||
raise ValueError(
|
||||
f"The booking platform internal URL path for page {name} of club "
|
||||
f"{self.club.name} are not set"
|
||||
)
|
||||
|
||||
def _check_payload_template_exists(self, name: str) -> None:
|
||||
"""
|
||||
Check that the payload template for the given service is defined
|
||||
|
||||
:param name: the name of the service
|
||||
"""
|
||||
if (
|
||||
self.club.booking_platform.urls is None
|
||||
or self.club.booking_platform.urls.get(name) is None
|
||||
or self.club.booking_platform.urls.get(name).path is None
|
||||
):
|
||||
raise ValueError(
|
||||
f"The booking platform internal URL path for page {name} of club "
|
||||
f"{self.club.name} are not set"
|
||||
)
|
||||
|
||||
@property
|
||||
def landing_url(self) -> str:
|
||||
"""
|
||||
Get the URL to the landing page of Gestion-Sports
|
||||
Get the URL to for landing to the website
|
||||
|
||||
:return: the URL to the landing page
|
||||
:return: the URL to landing
|
||||
"""
|
||||
return self._get_url_path("landing-page")
|
||||
return self.club.landing_url
|
||||
|
||||
@property
|
||||
def login_url(self) -> str:
|
||||
"""
|
||||
Get the URL to the connection login of Gestion-Sports
|
||||
Get the URL to for logging in the website
|
||||
|
||||
:return: the URL to the login page
|
||||
:return: the URL for logging in
|
||||
"""
|
||||
return self._get_url_path("login")
|
||||
return self.club.login_url
|
||||
|
||||
@property
|
||||
def login_template(self) -> Path:
|
||||
"""
|
||||
Get the payload template to send to log in the website
|
||||
Get the payload template for logging in the website
|
||||
|
||||
:return: the payload template for logging in
|
||||
:return: the payload template for logging
|
||||
"""
|
||||
return self._get_payload_template("login")
|
||||
return self.club.login_template
|
||||
|
||||
@property
|
||||
def booking_url(self) -> str:
|
||||
"""
|
||||
Get the URL to the booking page of Gestion-Sports
|
||||
Get the URL used to book a court
|
||||
|
||||
:return: the URL to the booking page
|
||||
:return: the URL to book a court
|
||||
"""
|
||||
return self._get_url_path("booking")
|
||||
return self.club.booking_url
|
||||
|
||||
@property
|
||||
def booking_template(self) -> Path:
|
||||
"""
|
||||
Get the payload template to send to book a court
|
||||
Get the payload template for booking a court
|
||||
|
||||
:return: the payload template for booking a court
|
||||
"""
|
||||
return self._get_payload_template("booking")
|
||||
return self.club.booking_template
|
||||
|
||||
@property
|
||||
def user_bookings_url(self) -> str:
|
||||
"""
|
||||
Get the URL where all the user's bookings are available
|
||||
Get the URL of the bookings related to a user that are not yet passed
|
||||
|
||||
:return: the URL to the user's bookings
|
||||
:return: the URL to get the bookings related to a user
|
||||
"""
|
||||
return self._get_url_path("user-bookings")
|
||||
return self.club.user_bookings_url
|
||||
|
||||
@property
|
||||
def user_bookings_template(self) -> Path:
|
||||
"""
|
||||
Get the payload template to send to get all the user's bookings that are
|
||||
available
|
||||
Get the payload template to get the bookings related to a user that are not yet
|
||||
passed
|
||||
|
||||
:return: the payload template for the user's bookings
|
||||
:return: the template for requesting the bookings related to a user
|
||||
"""
|
||||
return self._get_payload_template("user-bookings")
|
||||
return self.club.user_bookings_template
|
||||
|
||||
@property
|
||||
def booking_cancellation_url(self) -> str:
|
||||
def cancel_url(self) -> str:
|
||||
"""
|
||||
Get the URL where all the user's bookings are available
|
||||
Get the URL used to cancel a booking
|
||||
|
||||
:return: the URL to the user's bookings
|
||||
:return: the URL to cancel a booking
|
||||
"""
|
||||
return self._get_url_path("cancellation")
|
||||
return self.club.cancel_url
|
||||
|
||||
@property
|
||||
def booking_cancel_template(self) -> Path:
|
||||
def cancel_template(self) -> Path:
|
||||
"""
|
||||
Get the payload template to send to get all the user's bookings that are
|
||||
available
|
||||
Get the payload template for cancelling a booking
|
||||
|
||||
:return: the payload template for the user's bookings
|
||||
:return: the template for cancelling a booking
|
||||
"""
|
||||
return self._get_payload_template("cancellation")
|
||||
return self.club.cancel_template
|
||||
|
||||
@property
|
||||
def tournaments_sessions_url(self) -> str:
|
||||
return self._get_url_path("tournament-sessions")
|
||||
def sessions_url(self) -> str:
|
||||
"""
|
||||
Get the URL of the session containing all the tournaments
|
||||
|
||||
:return: the URL to get the session
|
||||
"""
|
||||
return self.club.sessions_url
|
||||
|
||||
@property
|
||||
def tournaments_sessions_template(self) -> Path:
|
||||
return self._get_payload_template("tournament-sessions")
|
||||
def sessions_template(self) -> Path:
|
||||
"""
|
||||
Get the payload template for requesting the session containing all the
|
||||
tournaments
|
||||
|
||||
:return: the template for requesting the session
|
||||
"""
|
||||
return self.club.sessions_template
|
||||
|
||||
@property
|
||||
def tournaments_list_url(self) -> str:
|
||||
return self._get_url_path("tournaments-list")
|
||||
def tournaments_url(self) -> str:
|
||||
"""
|
||||
Get the URL of all the tournaments list
|
||||
|
||||
:return: the URL to get the tournaments list
|
||||
"""
|
||||
return self.club.tournaments_url
|
||||
|
||||
@property
|
||||
def available_sports(self) -> dict[str, Sport]:
|
||||
def sports(self) -> dict[str, Sport]:
|
||||
"""
|
||||
Get a dictionary of all sports, the key is the sport name lowered case
|
||||
:return: the dictionary of all sports
|
||||
"""
|
||||
return {
|
||||
sport.name.lower(): sport for sport in self.club.booking_platform.sports
|
||||
}
|
||||
return self.club.sports
|
||||
|
||||
@staticmethod
|
||||
def check_response_status(response_status: int) -> None:
|
||||
if response_status != 200:
|
||||
raise WrongResponseStatus("GestionSports request failed")
|
||||
|
||||
async def land(self, session: ClientSession) -> ClientResponse:
|
||||
"""
|
||||
Perform the request to the landing page in order to get the cookie PHPSESSIONID
|
||||
|
||||
:param session: the client session shared among all connections
|
||||
:return: the response from the landing page
|
||||
"""
|
||||
LOGGER.info("Connecting to GestionSports API at %s", self.login_url)
|
||||
async with session.get(self.landing_url) as response:
|
||||
self.check_response_status(response.status)
|
||||
await response.text()
|
||||
return response
|
||||
|
||||
|
@ -220,6 +174,8 @@ class GestionSportsConnector:
|
|||
"""
|
||||
Perform the request to the log in the user
|
||||
|
||||
:param session: the client session shared among all connections
|
||||
:param user: the user to log in
|
||||
:return: the response from the login
|
||||
"""
|
||||
LOGGER.info("Logging in to GestionSports API at %s", self.login_url)
|
||||
|
@ -228,11 +184,12 @@ class GestionSportsConnector:
|
|||
async with session.post(
|
||||
self.login_url, data=payload, headers=POST_HEADERS, allow_redirects=False
|
||||
) as response:
|
||||
self.check_response_status(response.status)
|
||||
resp_text = await response.text()
|
||||
LOGGER.debug("Connexion request response:\n%s", resp_text)
|
||||
return response
|
||||
|
||||
async def book_any_court(
|
||||
async def send_all_booking_requests(
|
||||
self, session: ClientSession, booking_filter: BookingFilter
|
||||
) -> list[tuple[int, dict]]:
|
||||
"""
|
||||
|
@ -241,7 +198,7 @@ class GestionSportsConnector:
|
|||
The gestion-sports backend does not allow several bookings at the same time
|
||||
so there is no need to make each request one after the other
|
||||
|
||||
:param session: the session to use
|
||||
:param session: the client session shared among all connections
|
||||
:param booking_filter: the booking conditions to meet
|
||||
:return: the booked court, or None if no court was booked
|
||||
"""
|
||||
|
@ -249,7 +206,7 @@ class GestionSportsConnector:
|
|||
"Booking any available court from GestionSports API at %s", self.booking_url
|
||||
)
|
||||
|
||||
sport = self.available_sports.get(booking_filter.sport_name)
|
||||
sport = self.sports.get(booking_filter.sport_name)
|
||||
|
||||
bookings = await asyncio.gather(
|
||||
*[
|
||||
|
@ -274,7 +231,7 @@ class GestionSportsConnector:
|
|||
"""
|
||||
Book a single court that meets the conditions from the booking filter
|
||||
|
||||
:param session: the HTTP session that contains the user information and cookies
|
||||
:param session: the client session shared among all connections
|
||||
:param date: the booking date
|
||||
:param court_id: the id of the court to book
|
||||
:param sport_id: the id of the sport
|
||||
|
@ -293,168 +250,96 @@ class GestionSportsConnector:
|
|||
async with session.post(
|
||||
self.booking_url, data=payload, headers=POST_HEADERS
|
||||
) as response:
|
||||
assert response.status == 200
|
||||
self.check_response_status(response.status)
|
||||
resp_json = json.loads(await response.text())
|
||||
|
||||
LOGGER.debug("Response from booking request:\n'%s'", resp_json)
|
||||
return court_id, resp_json
|
||||
|
||||
def get_booked_court(
|
||||
self, bookings: list[tuple[int, dict]], sport_name: str
|
||||
) -> Court | None:
|
||||
async def send_hash_request(self, session: ClientSession) -> ClientResponse:
|
||||
"""
|
||||
Parse the booking list and return the court that was booked
|
||||
Get the hash value used in some other requests
|
||||
|
||||
:param bookings: a list of bookings
|
||||
:param sport_name: the sport name
|
||||
:return: the id of the booked court if any, None otherwise
|
||||
"""
|
||||
for court_id, response in bookings:
|
||||
if self.is_booking_response_status_ok(response):
|
||||
LOGGER.debug("Court %d is booked", court_id)
|
||||
court_booked = self.find_court(court_id, sport_name)
|
||||
LOGGER.info("Court '%s' is booked", court_booked.name)
|
||||
return court_booked
|
||||
LOGGER.debug("No booked court found")
|
||||
return None
|
||||
|
||||
def find_court(self, court_id: int, sport_name: str) -> Court:
|
||||
"""
|
||||
Get all the court information based on the court id and the sport name
|
||||
|
||||
:param court_id: the court id
|
||||
:param sport_name: the sport name
|
||||
:return: the court that has the given id and sport name
|
||||
"""
|
||||
sport = self.available_sports.get(sport_name.lower())
|
||||
for court in sport.courts:
|
||||
if court.id == court_id:
|
||||
return court
|
||||
|
||||
@staticmethod
|
||||
def is_booking_response_status_ok(response: dict) -> bool:
|
||||
"""
|
||||
Check if the booking response is OK
|
||||
|
||||
:param response: the response as a string
|
||||
:return: true if the status is ok, false otherwise
|
||||
"""
|
||||
return response["status"] == "ok"
|
||||
|
||||
async def get_ongoing_bookings(self, session: ClientSession) -> list[Booking]:
|
||||
"""
|
||||
Get the list of all ongoing bookings of a user.
|
||||
The steps to perform this are to get the user's bookings page and get a hidden
|
||||
property in the HTML to get a hash that will be used in the payload of the
|
||||
POST request (sic) to get the user's bookings.
|
||||
Gestion sports is really a mess!!
|
||||
|
||||
:return: the list of all ongoing bookings of a user
|
||||
"""
|
||||
hash_value = await self.send_hash_request(session)
|
||||
LOGGER.debug(f"Hash value: {hash_value}")
|
||||
payload = PayloadBuilder.build(self.user_bookings_template, hash=hash_value)
|
||||
LOGGER.debug(f"Payload to get ongoing bookings: {payload}")
|
||||
return await self.send_user_bookings_request(session, payload)
|
||||
|
||||
async def send_hash_request(self, session: ClientSession) -> str:
|
||||
"""
|
||||
Get the hash value used in the request to get the user's bookings
|
||||
|
||||
:param session: the session in which the user logged in
|
||||
:param session: the client session shared among all connections
|
||||
:return: the value of the hash
|
||||
"""
|
||||
async with session.get(self.user_bookings_url) as response:
|
||||
self.check_response_status(response.status)
|
||||
html = await response.text()
|
||||
LOGGER.debug("Get bookings response: %s\n", html)
|
||||
return self.get_hash_input(html)
|
||||
|
||||
@staticmethod
|
||||
def get_hash_input(html_doc: str) -> str:
|
||||
"""
|
||||
There is a secret hash generated by Gestion sports that is reused when trying to get
|
||||
users bookings. This hash is stored in a hidden input with name "mesresas-hash"
|
||||
|
||||
:param html_doc: the html document when getting the page mes-resas.html
|
||||
:return: the value of the hash in the page
|
||||
"""
|
||||
soup = BeautifulSoup(html_doc, "html.parser")
|
||||
inputs = soup.find_all("input")
|
||||
for input_tag in inputs:
|
||||
if input_tag.get("name") == "mesresas-hash":
|
||||
return input_tag.get("value").strip()
|
||||
return response
|
||||
|
||||
async def send_user_bookings_request(
|
||||
self, session: ClientSession, payload: str
|
||||
) -> list[Booking]:
|
||||
self, session: ClientSession, hash_value: str
|
||||
) -> ClientResponse:
|
||||
"""
|
||||
Perform the HTTP request to get all bookings
|
||||
Send a request to the platform to get all bookings of a user
|
||||
|
||||
:param session: the session in which the user logged in
|
||||
:param payload: the HTTP payload for the request
|
||||
:param session: the client session shared among all connections
|
||||
:param hash_value: the hash value to put in the payload
|
||||
:return: a dictionary containing all the bookings
|
||||
"""
|
||||
|
||||
payload = PayloadBuilder.build(self.user_bookings_template, hash=hash_value)
|
||||
async with session.post(
|
||||
self.user_bookings_url, data=payload, headers=POST_HEADERS
|
||||
) as response:
|
||||
resp = await response.text()
|
||||
LOGGER.debug("ongoing bookings response: %s\n", resp)
|
||||
return [Booking(**booking) for booking in json.loads(resp)]
|
||||
self.check_response_status(response.status)
|
||||
await response.text()
|
||||
return response
|
||||
|
||||
async def cancel_booking_id(
|
||||
self, session: ClientSession, booking_id: int
|
||||
async def send_cancellation_request(
|
||||
self, session: ClientSession, booking_id: int, hash_value: str
|
||||
) -> ClientResponse:
|
||||
"""
|
||||
Send the HTTP request to cancel the booking
|
||||
|
||||
:param session: the HTTP session that contains the user information and cookies
|
||||
:param session: the client session shared among all connections
|
||||
:param booking_id: the id of the booking to cancel
|
||||
:return: the response from the client
|
||||
"""
|
||||
hash_value = await self.send_hash_request(session)
|
||||
|
||||
payload = PayloadBuilder.build(
|
||||
self.booking_cancel_template,
|
||||
self.cancel_template,
|
||||
booking_id=booking_id,
|
||||
hash=hash_value,
|
||||
)
|
||||
|
||||
async with session.post(
|
||||
self.booking_cancellation_url, data=payload, headers=POST_HEADERS
|
||||
self.cancel_url, data=payload, headers=POST_HEADERS
|
||||
) as response:
|
||||
self.check_response_status(response.status)
|
||||
await response.text()
|
||||
return response
|
||||
|
||||
async def cancel_booking(
|
||||
self, session: ClientSession, booking_filter: BookingFilter
|
||||
) -> ClientResponse | None:
|
||||
async def send_session_request(self, session: ClientSession) -> ClientResponse:
|
||||
"""
|
||||
Cancel the booking that meets some conditions
|
||||
Send a request to the platform to get the session id
|
||||
|
||||
:param session: the session
|
||||
:param booking_filter: the conditions the booking to cancel should meet
|
||||
:param session: the client session shared among all connections
|
||||
:return: a client response containing HTML which has the session id
|
||||
"""
|
||||
bookings = await self.get_ongoing_bookings(session)
|
||||
|
||||
for booking in bookings:
|
||||
if booking.matches(booking_filter):
|
||||
return await self.cancel_booking_id(session, booking.id)
|
||||
|
||||
async def send_tournaments_sessions_request(
|
||||
self, session: ClientSession
|
||||
) -> ClientResponse:
|
||||
payload = self.tournaments_sessions_template.read_text()
|
||||
payload = self.sessions_template.read_text()
|
||||
|
||||
async with session.post(
|
||||
self.tournaments_sessions_url, data=payload, headers=POST_HEADERS
|
||||
self.sessions_url, data=payload, headers=POST_HEADERS
|
||||
) as response:
|
||||
self.check_response_status(response.status)
|
||||
LOGGER.debug("tournament sessions: \n%s", await response.text())
|
||||
return response
|
||||
|
||||
async def send_tournaments_request(
|
||||
self, session: ClientSession, tournement_session_id: str
|
||||
self, session: ClientSession, session_id: str
|
||||
) -> ClientResponse:
|
||||
final_url = self.tournaments_list_url + tournement_session_id
|
||||
"""
|
||||
Send a request to the platform to get the next tournaments
|
||||
|
||||
:param session: the client session shared among all connections
|
||||
:param session_id: the tournaments are grouped in a session
|
||||
:return: a client response containing the list of all the nex tournaments
|
||||
"""
|
||||
final_url = self.tournaments_url + session_id
|
||||
LOGGER.debug("Getting tournaments list at %s", final_url)
|
||||
async with session.get(final_url) as response:
|
||||
self.check_response_status(response.status)
|
||||
LOGGER.debug("tournaments: %s\n", await response.text())
|
||||
return response
|
||||
|
|
|
@ -6,16 +6,24 @@ import pendulum
|
|||
from aiohttp import ClientSession
|
||||
from bs4 import BeautifulSoup
|
||||
from gestion_sport_connector import GestionSportsConnector
|
||||
from models import BookingFilter, BookingOpening, Club, Court, Tournament, User
|
||||
from models import (
|
||||
Booking,
|
||||
BookingFilter,
|
||||
BookingOpening,
|
||||
Club,
|
||||
Court,
|
||||
Sport,
|
||||
Tournament,
|
||||
User,
|
||||
)
|
||||
from pendulum import DateTime
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GestionSportsServices:
|
||||
@staticmethod
|
||||
async def book(
|
||||
club: Club, user: User, booking_filter: BookingFilter
|
||||
self, club: Club, user: User, booking_filter: BookingFilter
|
||||
) -> Court | None:
|
||||
"""
|
||||
Perform a request for each court at the same time to increase the chances to get
|
||||
|
@ -45,37 +53,76 @@ class GestionSportsServices:
|
|||
booking_filter, booking_opening
|
||||
)
|
||||
|
||||
bookings = await connector.book_any_court(session, booking_filter)
|
||||
bookings = await connector.send_all_booking_requests(
|
||||
session, booking_filter
|
||||
)
|
||||
|
||||
LOGGER.debug("Booking results:\n'%s'", bookings)
|
||||
return connector.get_booked_court(bookings, booking_filter.sport_name)
|
||||
|
||||
sport = club.sports.get(booking_filter.sport_name)
|
||||
|
||||
return self.get_booked_court(bookings, sport)
|
||||
|
||||
def get_booked_court(
|
||||
self, bookings: list[tuple[int, dict]], sport: Sport
|
||||
) -> Court | None:
|
||||
"""
|
||||
Parse the booking list and return the court that was booked
|
||||
|
||||
:param bookings: a list of bookings
|
||||
:param sport: the sport of the club and all the courts it has
|
||||
:return: the id of the booked court if any, None otherwise
|
||||
"""
|
||||
for court_id, response in bookings:
|
||||
if self.is_booking_response_status_ok(response):
|
||||
LOGGER.debug("Court %d is booked", court_id)
|
||||
court_booked = self.find_court(court_id, sport)
|
||||
LOGGER.info("Court '%s' is booked", court_booked.name)
|
||||
return court_booked
|
||||
LOGGER.debug("No booked court found")
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
async def has_user_available_slots(user: User, club: Club) -> bool:
|
||||
def is_booking_response_status_ok(response: dict) -> bool:
|
||||
"""
|
||||
Check if the booking response is OK
|
||||
|
||||
:param response: the response as a string
|
||||
:return: true if the status is ok, false otherwise
|
||||
"""
|
||||
return response["status"] == "ok"
|
||||
|
||||
@staticmethod
|
||||
def find_court(court_id: int, sport: Sport) -> Court:
|
||||
"""
|
||||
Get all the court information based on the court id and the sport name
|
||||
|
||||
:param court_id: the court id
|
||||
:param sport: the sport
|
||||
:return: the court that has the given id and sport name
|
||||
"""
|
||||
for court in sport.courts:
|
||||
if court.id == court_id:
|
||||
return court
|
||||
|
||||
async def has_user_available_slots(self, user: User, club: Club) -> bool:
|
||||
"""
|
||||
Checks if a user has available booking slot.
|
||||
If a user already has an ongoing booking, it is considered as no slot is
|
||||
available
|
||||
|
||||
:param user: The user to check the booking availability
|
||||
:param club: The club of the user
|
||||
:return: True if the user has no ongoing booking, False otherwise
|
||||
"""
|
||||
connector = GestionSportsConnector(club)
|
||||
async with ClientSession() as session:
|
||||
await connector.land(session)
|
||||
await connector.login(session, user)
|
||||
bookings = await connector.get_ongoing_bookings(session)
|
||||
bookings = await self.get_ongoing_bookings(session, connector)
|
||||
|
||||
return bool(bookings)
|
||||
|
||||
@staticmethod
|
||||
async def cancel_booking(user: User, club: Club, booking_filter: BookingFilter):
|
||||
connector = GestionSportsConnector(club)
|
||||
async with ClientSession() as session:
|
||||
await connector.land(session)
|
||||
await connector.login(session, user)
|
||||
await connector.cancel_booking(session, booking_filter)
|
||||
|
||||
@staticmethod
|
||||
async def cancel_booking_id(user: User, club: Club, booking_id: int):
|
||||
connector = GestionSportsConnector(club)
|
||||
async with ClientSession() as session:
|
||||
await connector.land(session)
|
||||
await connector.login(session, user)
|
||||
await connector.cancel_booking_id(session, booking_id)
|
||||
|
||||
@staticmethod
|
||||
def wait_until_booking_time(
|
||||
booking_filter: BookingFilter, booking_opening: BookingOpening
|
||||
|
@ -128,6 +175,71 @@ class GestionSportsServices:
|
|||
|
||||
return booking_date.at(booking_hour, booking_minute)
|
||||
|
||||
async def cancel_booking(
|
||||
self, user: User, club: Club, booking_filter: BookingFilter
|
||||
):
|
||||
connector = GestionSportsConnector(club)
|
||||
async with ClientSession() as session:
|
||||
await connector.land(session)
|
||||
await connector.login(session, user)
|
||||
|
||||
bookings = await self.get_ongoing_bookings(session, connector)
|
||||
for booking in bookings:
|
||||
if booking.matches(booking_filter):
|
||||
return await self.cancel_booking_id(session, connector, booking.id)
|
||||
|
||||
async def get_ongoing_bookings(
|
||||
self, session: ClientSession, connector: GestionSportsConnector
|
||||
) -> list[Booking]:
|
||||
"""
|
||||
Get the list of all ongoing bookings of a user.
|
||||
The steps to perform this are to get the user's bookings page and get a hidden
|
||||
property in the HTML to get a hash that will be used in the payload of the
|
||||
POST request (sic) to get the user's bookings.
|
||||
Gestion sports is really a mess!!
|
||||
|
||||
:param session: the client session shared among all connections
|
||||
:param connector: the connector used to send the requests
|
||||
:return: the list of all ongoing bookings of a user
|
||||
"""
|
||||
response = await connector.send_hash_request(session)
|
||||
hash_value = self.get_hash_input(await response.text())
|
||||
LOGGER.debug(f"Hash value: {hash_value}")
|
||||
response = await connector.send_user_bookings_request(session, hash_value)
|
||||
return [Booking(**booking) for booking in json.loads(await response.text())]
|
||||
|
||||
@staticmethod
|
||||
def get_hash_input(html_doc: str) -> str:
|
||||
"""
|
||||
There is a secret hash generated by Gestion sports that is reused when trying to get
|
||||
users bookings. This hash is stored in a hidden input with name "mesresas-hash"
|
||||
|
||||
:param html_doc: the html document when getting the page mes-resas.html
|
||||
:return: the value of the hash in the page
|
||||
"""
|
||||
soup = BeautifulSoup(html_doc, "html.parser")
|
||||
inputs = soup.find_all("input")
|
||||
for input_tag in inputs:
|
||||
if input_tag.get("name") == "mesresas-hash":
|
||||
return input_tag.get("value").strip()
|
||||
|
||||
async def cancel_booking_id(
|
||||
self, session: ClientSession, connector: GestionSportsConnector, booking_id: int
|
||||
) -> None:
|
||||
"""
|
||||
Send the HTTP request to cancel the booking
|
||||
|
||||
:param session: the client session shared among all connections
|
||||
:param connector: the connector used to send the requests
|
||||
:param booking_id: the id of the booking to cancel
|
||||
:return: the response from the client
|
||||
"""
|
||||
response = await connector.send_hash_request(session)
|
||||
hash_value = self.get_hash_input(await response.text())
|
||||
LOGGER.debug(f"Hash value: {hash_value}")
|
||||
|
||||
await connector.send_cancellation_request(session, booking_id, hash_value)
|
||||
|
||||
@staticmethod
|
||||
async def get_all_tournaments(user: User, club: Club) -> list[Tournament]:
|
||||
connector = GestionSportsConnector(club)
|
||||
|
@ -135,7 +247,7 @@ class GestionSportsServices:
|
|||
await connector.land(session)
|
||||
await connector.login(session, user)
|
||||
|
||||
session_html = await connector.send_tournaments_sessions_request(session)
|
||||
session_html = await connector.send_session_request(session)
|
||||
tournaments_id = GestionSportsServices.retrieve_tournament_session(
|
||||
await session_html.text()
|
||||
)
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from urllib.parse import urljoin
|
||||
|
||||
import config
|
||||
import pendulum
|
||||
from exceptions import MissingProperty
|
||||
from pendulum import Date, Time
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||
from pydantic_extra_types.pendulum_dt import DateTime
|
||||
|
@ -84,6 +88,166 @@ class BookingPlatform(BaseModel):
|
|||
sports: list[Sport]
|
||||
urls: dict[str, Url]
|
||||
|
||||
def get_url_path(self, name: str) -> str:
|
||||
"""
|
||||
Get the URL path for the service with the given name
|
||||
|
||||
:param name: the name of the service
|
||||
:return: the URL path
|
||||
"""
|
||||
self.check_url_path_exists(name)
|
||||
|
||||
return urljoin(self.url, self.urls.get(name).path)
|
||||
|
||||
def get_payload_template(self, name: str) -> Path:
|
||||
"""
|
||||
Get the path to the template file for the service with the given name
|
||||
|
||||
:param name: the name of the service
|
||||
:return: the path to the template file
|
||||
"""
|
||||
self.check_payload_template_exists(name)
|
||||
|
||||
return config.get_resources_folder() / self.urls.get(name).payload_template
|
||||
|
||||
def get_url_parameter(self, name: str) -> str:
|
||||
self.check_url_path_exists(name)
|
||||
|
||||
return self.urls.get(name).parameter
|
||||
|
||||
def check_url_path_exists(self, name: str) -> None:
|
||||
"""
|
||||
Check that the URL path for the given service is defined
|
||||
|
||||
:param name: the name of the service
|
||||
"""
|
||||
if (
|
||||
self.urls is None
|
||||
or self.urls.get(name) is None
|
||||
or self.urls.get(name).path is None
|
||||
):
|
||||
raise MissingProperty(
|
||||
f"The booking platform internal URL path for page {name} are not set"
|
||||
)
|
||||
|
||||
def check_payload_template_exists(self, name: str) -> None:
|
||||
"""
|
||||
Check that the payload template for the given service is defined
|
||||
|
||||
:param name: the name of the service
|
||||
"""
|
||||
if (
|
||||
self.urls is None
|
||||
or self.urls.get(name) is None
|
||||
or self.urls.get(name).path is None
|
||||
):
|
||||
raise ValueError(
|
||||
f"The booking platform internal URL path for page {name} are not set"
|
||||
)
|
||||
|
||||
@property
|
||||
def landing_url(self) -> str:
|
||||
"""
|
||||
Get the URL to the landing page of Gestion-Sports
|
||||
|
||||
:return: the URL to the landing page
|
||||
"""
|
||||
return self.get_url_path("landing-page")
|
||||
|
||||
@property
|
||||
def login_url(self) -> str:
|
||||
"""
|
||||
Get the URL to the connection login of Gestion-Sports
|
||||
|
||||
:return: the URL to the login page
|
||||
"""
|
||||
return self.get_url_path("login")
|
||||
|
||||
@property
|
||||
def login_template(self) -> Path:
|
||||
"""
|
||||
Get the payload template to send to log in the website
|
||||
|
||||
:return: the payload template for logging in
|
||||
"""
|
||||
return self.get_payload_template("login")
|
||||
|
||||
@property
|
||||
def booking_url(self) -> str:
|
||||
"""
|
||||
Get the URL to the booking page of Gestion-Sports
|
||||
|
||||
:return: the URL to the booking page
|
||||
"""
|
||||
return self.get_url_path("booking")
|
||||
|
||||
@property
|
||||
def booking_template(self) -> Path:
|
||||
"""
|
||||
Get the payload template to send to book a court
|
||||
|
||||
:return: the payload template for booking a court
|
||||
"""
|
||||
return self.get_payload_template("booking")
|
||||
|
||||
@property
|
||||
def user_bookings_url(self) -> str:
|
||||
"""
|
||||
Get the URL where all the user's bookings are available
|
||||
|
||||
:return: the URL to the user's bookings
|
||||
"""
|
||||
return self.get_url_path("user-bookings")
|
||||
|
||||
@property
|
||||
def user_bookings_template(self) -> Path:
|
||||
"""
|
||||
Get the payload template to send to get all the user's bookings that are
|
||||
available
|
||||
|
||||
:return: the payload template for the user's bookings
|
||||
"""
|
||||
return self.get_payload_template("user-bookings")
|
||||
|
||||
@property
|
||||
def booking_cancellation_url(self) -> str:
|
||||
"""
|
||||
Get the URL where all the user's bookings are available
|
||||
|
||||
:return: the URL to the user's bookings
|
||||
"""
|
||||
return self.get_url_path("cancellation")
|
||||
|
||||
@property
|
||||
def booking_cancel_template(self) -> Path:
|
||||
"""
|
||||
Get the payload template to send to get all the user's bookings that are
|
||||
available
|
||||
|
||||
:return: the payload template for the user's bookings
|
||||
"""
|
||||
return self.get_payload_template("cancellation")
|
||||
|
||||
@property
|
||||
def tournaments_sessions_url(self) -> str:
|
||||
return self.get_url_path("tournament-sessions")
|
||||
|
||||
@property
|
||||
def tournaments_sessions_template(self) -> Path:
|
||||
return self.get_payload_template("tournament-sessions")
|
||||
|
||||
@property
|
||||
def tournaments_list_url(self) -> str:
|
||||
return self.get_url_path("tournaments-list")
|
||||
|
||||
@property
|
||||
def available_sports(self) -> dict[str, Sport]:
|
||||
"""
|
||||
Get a dictionary of all sports, the key is the sport name lowered case
|
||||
:return: the dictionary of all sports
|
||||
"""
|
||||
return {sport.name.lower(): sport for sport in self.sports}
|
||||
|
||||
|
||||
class Club(BaseModel):
|
||||
id: str
|
||||
|
@ -91,6 +255,109 @@ class Club(BaseModel):
|
|||
url: str
|
||||
booking_platform: BookingPlatform = Field(alias="bookingPlatform")
|
||||
|
||||
@property
|
||||
def landing_url(self) -> str:
|
||||
"""
|
||||
Get the URL to the landing page of Gestion-Sports
|
||||
|
||||
:return: the URL to the landing page
|
||||
"""
|
||||
return self.booking_platform.landing_url
|
||||
|
||||
@property
|
||||
def login_url(self) -> str:
|
||||
"""
|
||||
Get the URL to the connection login of Gestion-Sports
|
||||
|
||||
:return: the URL to the login page
|
||||
"""
|
||||
return self.booking_platform.login_url
|
||||
|
||||
@property
|
||||
def login_template(self) -> Path:
|
||||
"""
|
||||
Get the payload template to send to log in the website
|
||||
|
||||
:return: the payload template for logging in
|
||||
"""
|
||||
return self.booking_platform.login_template
|
||||
|
||||
@property
|
||||
def booking_url(self) -> str:
|
||||
"""
|
||||
Get the URL to the booking page of Gestion-Sports
|
||||
|
||||
:return: the URL to the booking page
|
||||
"""
|
||||
return self.booking_platform.booking_url
|
||||
|
||||
@property
|
||||
def booking_template(self) -> Path:
|
||||
"""
|
||||
Get the payload template to send to book a court
|
||||
|
||||
:return: the payload template for booking a court
|
||||
"""
|
||||
return self.booking_platform.booking_template
|
||||
|
||||
@property
|
||||
def user_bookings_url(self) -> str:
|
||||
"""
|
||||
Get the URL where all the user's bookings are available
|
||||
|
||||
:return: the URL to the user's bookings
|
||||
"""
|
||||
return self.booking_platform.user_bookings_url
|
||||
|
||||
@property
|
||||
def user_bookings_template(self) -> Path:
|
||||
"""
|
||||
Get the payload template to send to get all the user's bookings that are
|
||||
available
|
||||
|
||||
:return: the payload template for the user's bookings
|
||||
"""
|
||||
return self.booking_platform.user_bookings_template
|
||||
|
||||
@property
|
||||
def cancel_url(self) -> str:
|
||||
"""
|
||||
Get the URL where all the user's bookings are available
|
||||
|
||||
:return: the URL to the user's bookings
|
||||
"""
|
||||
return self.booking_platform.booking_cancellation_url
|
||||
|
||||
@property
|
||||
def cancel_template(self) -> Path:
|
||||
"""
|
||||
Get the payload template to send to get all the user's bookings that are
|
||||
available
|
||||
|
||||
:return: the payload template for the user's bookings
|
||||
"""
|
||||
return self.booking_platform.booking_cancel_template
|
||||
|
||||
@property
|
||||
def sessions_url(self) -> str:
|
||||
return self.booking_platform.tournaments_sessions_url
|
||||
|
||||
@property
|
||||
def sessions_template(self) -> Path:
|
||||
return self.booking_platform.tournaments_sessions_template
|
||||
|
||||
@property
|
||||
def tournaments_url(self) -> str:
|
||||
return self.booking_platform.tournaments_list_url
|
||||
|
||||
@property
|
||||
def sports(self) -> dict[str, Sport]:
|
||||
"""
|
||||
Get a dictionary of all sports, the key is the sport name lowered case
|
||||
:return: the dictionary of all sports
|
||||
"""
|
||||
return self.booking_platform.available_sports
|
||||
|
||||
|
||||
class PlatformDefinition(BaseModel):
|
||||
id: str
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -33,7 +33,7 @@ def test_cancellation(club, user, booking_filter):
|
|||
"CLUB_ID": "tpc",
|
||||
"ACTION": "book",
|
||||
"SPORT_NAME": "Padel",
|
||||
"DATE_TIME": "2024-03-21T13:30:00+01:00",
|
||||
"DATE_TIME": "2024-03-28T13:30:00+01:00",
|
||||
},
|
||||
clear=True,
|
||||
)
|
||||
|
@ -49,7 +49,7 @@ def test_main_booking():
|
|||
"CLUB_ID": "tpc",
|
||||
"ACTION": "cancel",
|
||||
"SPORT_NAME": "Padel",
|
||||
"DATE_TIME": "2024-03-21T13:30:00+01:00",
|
||||
"DATE_TIME": "2024-03-28T13:30:00+01:00",
|
||||
"LOGIN": "padel.testing@jouf.fr",
|
||||
"PASSWORD": "ridicule",
|
||||
},
|
||||
|
|
|
@ -33,11 +33,11 @@ def test_urls(connector):
|
|||
== "https://toulousepadelclub.gestion-sports.com/membre/mesresas.html"
|
||||
)
|
||||
assert (
|
||||
connector.booking_cancellation_url
|
||||
connector.cancel_url
|
||||
== "https://toulousepadelclub.gestion-sports.com/membre/mesresas.html"
|
||||
)
|
||||
assert (
|
||||
connector.tournaments_sessions_url
|
||||
connector.sessions_url
|
||||
== "https://toulousepadelclub.gestion-sports.com/membre/index.php"
|
||||
)
|
||||
|
||||
|
@ -56,11 +56,11 @@ def test_urls_payload_templates(connector):
|
|||
== resources_folder / "user-bookings-payload.txt"
|
||||
)
|
||||
assert (
|
||||
connector.booking_cancel_template
|
||||
connector.cancel_template
|
||||
== resources_folder / "booking-cancellation-payload.txt"
|
||||
)
|
||||
assert (
|
||||
connector.tournaments_sessions_template
|
||||
connector.sessions_template
|
||||
== resources_folder / "tournament-sessions-payload.txt"
|
||||
)
|
||||
|
||||
|
@ -192,7 +192,7 @@ async def test_cancel_booking_id(connector, user):
|
|||
await connector.land(session)
|
||||
await connector.login(session, user)
|
||||
|
||||
await connector.cancel_booking_id(session, 666)
|
||||
await connector.send_cancellation_request(session, 666)
|
||||
|
||||
assert len(await connector.get_ongoing_bookings(session)) == 0
|
||||
|
||||
|
@ -217,7 +217,7 @@ async def test_tournament_sessions(connector, user):
|
|||
async with ClientSession() as session:
|
||||
await connector.land(session)
|
||||
await connector.login(session, user)
|
||||
response = await connector.send_tournaments_sessions_request(session)
|
||||
response = await connector.send_session_request(session)
|
||||
|
||||
assert response.status == 200
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ def set_tournaments_sessions_response(
|
|||
aioresponses, connector: GestionSportsConnector, tournaments_sessions_response
|
||||
):
|
||||
aioresponses.post(
|
||||
connector.tournaments_sessions_url,
|
||||
connector.sessions_url,
|
||||
status=200,
|
||||
body=tournaments_sessions_response,
|
||||
)
|
||||
|
@ -73,7 +73,7 @@ def set_tournaments_list_response(
|
|||
tournament_id,
|
||||
tournaments_list_response,
|
||||
):
|
||||
url = f"{connector.tournaments_list_url}{tournament_id}"
|
||||
url = f"{connector.tournaments_url}{tournament_id}"
|
||||
aioresponses.get(url, status=200, body=tournaments_list_response)
|
||||
|
||||
|
||||
|
@ -84,7 +84,7 @@ def set_full_user_bookings_responses(aioresponses, connector, responses):
|
|||
|
||||
|
||||
def set_cancellation_response(aioresponses, connector, response):
|
||||
aioresponses.post(connector.booking_cancellation_url, status=200, payload=response)
|
||||
aioresponses.post(connector.cancel_url, status=200, payload=response)
|
||||
|
||||
|
||||
def set_full_cancellation_by_id_responses(aioresponses, connector, responses):
|
||||
|
|
|
@ -16,9 +16,9 @@ def test_urls(connector, club):
|
|||
assert connector.login_url == f"{base_url}/login.html"
|
||||
assert connector.booking_url == f"{base_url}/booking.html"
|
||||
assert connector.user_bookings_url == f"{base_url}/user_bookings.html"
|
||||
assert connector.booking_cancellation_url == f"{base_url}/cancel.html"
|
||||
assert connector.tournaments_sessions_url == f"{base_url}/tournaments_sessions.php"
|
||||
assert connector.tournaments_list_url == f"{base_url}/tournaments_list.html?event="
|
||||
assert connector.cancel_url == f"{base_url}/cancel.html"
|
||||
assert connector.sessions_url == f"{base_url}/tournaments_sessions.php"
|
||||
assert connector.tournaments_url == f"{base_url}/tournaments_list.html?event="
|
||||
|
||||
|
||||
@patch("config.get_resources_folder")
|
||||
|
@ -41,11 +41,11 @@ def test_urls_payload_templates(mock_resources, club):
|
|||
== path_to_resources / "gestion-sports/user-bookings-payload.txt"
|
||||
)
|
||||
assert (
|
||||
connector.booking_cancel_template
|
||||
connector.cancel_template
|
||||
== path_to_resources / "gestion-sports/booking-cancellation-payload.txt"
|
||||
)
|
||||
assert (
|
||||
connector.tournaments_sessions_template
|
||||
connector.sessions_template
|
||||
== path_to_resources / "gestion-sports/tournament-sessions-payload.txt"
|
||||
)
|
||||
|
||||
|
@ -90,29 +90,6 @@ async def test_login_failure(aioresponses, connector, user, login_failure_respon
|
|||
assert await response.json() == login_failure_response
|
||||
|
||||
|
||||
def test_get_booked_court(connector, booked_courts_response):
|
||||
booked_court = connector.get_booked_court(booked_courts_response, "Sport1")
|
||||
assert booked_court.number == 3
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_ongoing_bookings(
|
||||
aioresponses,
|
||||
connector,
|
||||
user,
|
||||
user_bookings_get_response,
|
||||
user_bookings_list,
|
||||
):
|
||||
responses.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_cancellation_request(
|
||||
aioresponses, connector, user_bookings_get_response, cancellation_response
|
||||
|
@ -121,31 +98,11 @@ async def test_cancellation_request(
|
|||
responses.set_cancellation_response(aioresponses, connector, cancellation_response)
|
||||
|
||||
async with ClientSession() as session:
|
||||
response = await connector.cancel_booking_id(session, 123)
|
||||
response = await connector.send_cancellation_request(session, 123, "hash")
|
||||
|
||||
assert await response.json() == cancellation_response
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_cancel_booking_success(
|
||||
aioresponses,
|
||||
connector,
|
||||
user,
|
||||
cancellation_success_booking_filter,
|
||||
cancellation_success_from_start,
|
||||
):
|
||||
responses.set_full_cancellation_responses(
|
||||
aioresponses, connector, cancellation_success_from_start
|
||||
)
|
||||
|
||||
async with ClientSession() as session:
|
||||
response = await connector.cancel_booking(
|
||||
session, cancellation_success_booking_filter
|
||||
)
|
||||
|
||||
assert await response.json() == cancellation_success_from_start[4]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_tournament_sessions(
|
||||
aioresponses, connector, user, tournament_sessions_json
|
||||
|
@ -154,7 +111,7 @@ async def test_tournament_sessions(
|
|||
aioresponses, connector, tournament_sessions_json
|
||||
)
|
||||
async with ClientSession() as session:
|
||||
response = await connector.send_tournaments_sessions_request(session)
|
||||
response = await connector.send_session_request(session)
|
||||
|
||||
assert response.status == 200
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ from tests.unit_tests import responses
|
|||
@pytest.mark.asyncio
|
||||
async def test_booking_success(
|
||||
aioresponses,
|
||||
gs_services,
|
||||
connector,
|
||||
club,
|
||||
user,
|
||||
|
@ -21,7 +22,7 @@ async def test_booking_success(
|
|||
aioresponses, connector, booking_success_from_start
|
||||
)
|
||||
|
||||
court_booked = await GestionSportsServices.book(club, user, booking_filter)
|
||||
court_booked = await gs_services.book(club, user, booking_filter)
|
||||
|
||||
assert court_booked.id == 2
|
||||
|
||||
|
@ -45,6 +46,11 @@ async def test_booking_failure(
|
|||
assert court_booked is None
|
||||
|
||||
|
||||
def test_get_booked_court(gs_services, booked_courts_response, sport1):
|
||||
booked_court = gs_services.get_booked_court(booked_courts_response, sport1)
|
||||
assert booked_court.number == 3
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_user_has_available_booking_slots(
|
||||
aioresponses,
|
||||
|
@ -98,22 +104,6 @@ async def test_cancel_booking(
|
|||
await gs_services.cancel_booking(user, club, booking_filter)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_cancel_booking_id(
|
||||
aioresponses,
|
||||
gs_services,
|
||||
connector,
|
||||
user,
|
||||
club,
|
||||
cancellation_success_from_start,
|
||||
):
|
||||
responses.set_full_cancellation_responses(
|
||||
aioresponses, connector, cancellation_success_from_start
|
||||
)
|
||||
|
||||
await gs_services.cancel_booking_id(user, club, 65464)
|
||||
|
||||
|
||||
@patch("pendulum.now")
|
||||
def test_wait_until_booking_time(mock_now, club, user):
|
||||
booking_filter = BookingFilter(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue