From ccd019eb4cc5d18509864bc3ba944aad6ff5f878 Mon Sep 17 00:00:00 2001 From: stanislas Date: Sun, 18 Feb 2024 09:16:11 +0100 Subject: [PATCH 1/3] Added docstrings --- resa_padel/booking.py | 32 ++++++- resa_padel/config.py | 27 ++++++ .../gestion_sports_connector.py | 43 ++++++++- .../test_gestion_sports_connector.py | 93 +++++++++++++++---- 4 files changed, 174 insertions(+), 21 deletions(-) diff --git a/resa_padel/booking.py b/resa_padel/booking.py index 6943170..66a94cc 100644 --- a/resa_padel/booking.py +++ b/resa_padel/booking.py @@ -14,6 +14,14 @@ LOGGER = logging.getLogger(__name__) def wait_until_booking_time(club: Club, booking_filter: BookingFilter): + """ + Wait until the booking is open. + The booking filter contains the date and time of the booking. + The club has the information about when the booking is open for that date. + + :param club: the club where to book a court + :param booking_filter: the booking information + """ LOGGER.info("Waiting booking time") booking_datetime = build_booking_datetime(booking_filter, club) now = pendulum.now() @@ -23,6 +31,15 @@ def wait_until_booking_time(club: Club, booking_filter: BookingFilter): def build_booking_datetime(booking_filter: BookingFilter, club: Club) -> DateTime: + """ + Build the date and time when the booking is open for a given match date. + The booking filter contains the date and time of the booking. + The club has the information about when the booking is open for that date. + + :param booking_filter: the booking information + :param club: the club where to book a court + :return: the date and time when the booking is open + """ date_to_book = booking_filter.date booking_date = date_to_book.subtract(days=club.booking_open_days_before) @@ -33,15 +50,28 @@ def build_booking_datetime(booking_filter: BookingFilter, club: Club) -> DateTim async def book(club: Club, user: User, booking_filter: BookingFilter) -> int | None: + """ + Book a court for a user to a club following a booking filter + + :param club: the club where to book a court + :param user: the user information + :param booking_filter: the information related to the booking + :return: the id of the booked court, or None if no court was booked + """ async with ClientSession() as session: platform = GestionSportsConnector(session, club.url) - await platform.connect() + await platform.land() await platform.login(user, club) wait_until_booking_time(club, booking_filter) return await platform.book(booking_filter, club) def main() -> int | None: + """ + Main function used to book a court + + :return: the id of the booked court, or None if no court was booked + """ user = config.get_user() booking_filter = config.get_booking_filter() club = config.get_club() diff --git a/resa_padel/config.py b/resa_padel/config.py index bfd17d7..3c162b0 100644 --- a/resa_padel/config.py +++ b/resa_padel/config.py @@ -13,6 +13,12 @@ load_dotenv() def get_club() -> Club: + """ + Read the environment variables related to the current club + and build the Club object + + :return: the club + """ club_url = os.environ.get("CLUB_URL") court_ids_tmp = os.environ.get("COURT_IDS") or "" court_ids = ( @@ -34,6 +40,12 @@ def get_club() -> Club: def get_booking_filter() -> BookingFilter: + """ + Read the environment variables related to the current booking filter + and build the BookingFilter object + + :return: the club + """ sport_id_tmp = os.environ.get("SPORT_ID") sport_id = int(sport_id_tmp) if sport_id_tmp else None date_time_tmp = os.environ.get("DATE_TIME") @@ -42,12 +54,24 @@ def get_booking_filter() -> BookingFilter: def get_user() -> User: + """ + Read the environment variables related to the current user + and build the User object + + :return: the club + """ login = os.environ.get("LOGIN") password = os.environ.get("PASSWORD") return User(login=login, password=password) def get_post_headers(platform_id: str) -> dict: + """ + Get the headers for the POST endpoint related to a specific booking platform + + :param platform_id: the platform to which the headers apply + :return: the headers as a dictionary + """ root_path = Path(__file__).parent headers_file = Path(root_path, "resources", platform_id, "post-headers.json") with headers_file.open(mode="r", encoding="utf-8") as f: @@ -57,6 +81,9 @@ def get_post_headers(platform_id: str) -> dict: def init_log_config(): + """ + Read the logging.yaml file to initialize the logging configuration + """ root_dir = os.path.realpath(os.path.dirname(__file__)) logging_file = root_dir + "/logging.yaml" diff --git a/resa_padel/gestion_sports/gestion_sports_connector.py b/resa_padel/gestion_sports/gestion_sports_connector.py index cb9a42c..2a61090 100644 --- a/resa_padel/gestion_sports/gestion_sports_connector.py +++ b/resa_padel/gestion_sports/gestion_sports_connector.py @@ -18,6 +18,9 @@ POST_HEADERS = config.get_post_headers("gestion-sports") class GestionSportsConnector: + """ + Handle the specific booking requests to Gestion-Sports + """ def __init__(self, session: ClientSession, url: str): LOGGER.info("Initializing connection to GestionSports API") @@ -26,24 +29,49 @@ class GestionSportsConnector: self.payload_builder = GestionSportsPayloadBuilder() @property - def connection_url(self) -> str: + def landing_url(self) -> str: + """ + Get the URL to the landing page of Gestion-Sports + + :return: the URL to the landing page + """ return urljoin(self.url, "/connexion.php?") @property def login_url(self) -> str: + """ + Get the URL to the connection login of Gestion-Sports + + :return: the URL to the login page + """ return urljoin(self.url, "/connexion.php?") @property def booking_url(self) -> str: + """ + Get the URL to the booking page of Gestion-Sports + + :return: the URL to the booking page + """ return urljoin(self.url, "/membre/reservation.html?") - async def connect(self) -> ClientResponse: + async def land(self) -> ClientResponse: + """ + Perform the request to the landing page in order to get the cookie PHPSESSIONID + + :return: the response from the landing page + """ LOGGER.info("Connecting to GestionSports API") - async with self.session.get(self.connection_url) as response: + async with self.session.get(self.landing_url) as response: await response.text() return response async def login(self, user: User, club: Club) -> ClientResponse: + """ + Perform the request to the log in the user + + :return: the response from the login + """ payload = ( self.payload_builder.login(user.login) .password(user.password) @@ -58,6 +86,15 @@ class GestionSportsConnector: return response async def book(self, booking_filter: BookingFilter, club: Club) -> int | None: + """ + Perform a request for each court at the same time to increase the chances to get a booking. + 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 booking_filter: the booking information + :param club: the club where to book the court + :return: the booked court, or None if no court was booked + """ # use asyncio to request a booking on every court # the gestion-sports backend is able to book only one court for a user bookings = await asyncio.gather( diff --git a/tests/gestion_sports/test_gestion_sports_connector.py b/tests/gestion_sports/test_gestion_sports_connector.py index 02047a9..52d6edd 100644 --- a/tests/gestion_sports/test_gestion_sports_connector.py +++ b/tests/gestion_sports/test_gestion_sports_connector.py @@ -2,6 +2,7 @@ import pytest from aiohttp import ClientSession from yarl import URL +from models import BookingFilter, Club, User from resa_padel.gestion_sports.gestion_sports_connector import GestionSportsConnector from tests.fixtures import ( a_booking_failure_response, @@ -16,13 +17,16 @@ tpc_url = "https://toulousepadelclub.gestion-sports.com" @pytest.mark.asyncio -async def test_should_connect_to_gestion_sports_website(): +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.connect() + response = await gs_connector.land() assert response.status == 200 assert response.request_info.method == "GET" @@ -35,10 +39,18 @@ async def test_should_connect_to_gestion_sports_website(): @pytest.mark.asyncio -async def test_should_login_to_gestion_sports_website(a_user, a_club): +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.connect() + await gs_connector.land() response = await gs_connector.login(a_user, a_club) @@ -53,10 +65,19 @@ async def test_should_login_to_gestion_sports_website(a_user, a_club): @pytest.mark.asyncio -async def test_booking_url_should_be_reachable(a_user, a_booking_filter, a_club): +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.connect() + await gs_connector.land() await gs_connector.login(a_user, a_club) court_booked = await gs_connector.book(a_booking_filter, a_club) @@ -67,11 +88,21 @@ async def test_booking_url_should_be_reachable(a_user, a_booking_filter, a_club) @pytest.mark.asyncio async def test_should_book_a_court_from_gestion_sports( aioresponses, - a_booking_filter, - a_club, - a_booking_success_response, - a_booking_failure_response, -): + 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 @@ -89,20 +120,40 @@ async def test_should_book_a_court_from_gestion_sports( assert court_booked == a_club.courts_ids[1] -def test_response_status_should_be_ok(a_booking_success_response): +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_response_status_ok(a_booking_success_response) assert is_booked -def test_response_status_should_be_not_ok(a_booking_failure_response): +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_response_status_ok(a_booking_failure_response) assert not is_booked @pytest.mark.asyncio async def test_court_should_not_be_booked( - aioresponses, a_booking_payload, a_booking_failure_response -): + aioresponses, a_booking_payload: str, a_booking_failure_response: str +) -> None: + """ + Test that no court is booked when there is a failure response + from the booking request + + :param aioresponses: the http requests mock + :param a_booking_payload: the payload that is sent for booking + :param a_booking_failure_response: the failure response mock + """ async with ClientSession() as session: tpc_connector = GestionSportsConnector(session, tpc_url) aioresponses.post( @@ -114,8 +165,16 @@ async def test_court_should_not_be_booked( @pytest.mark.asyncio async def test_court_should_be_booked( - aioresponses, a_booking_payload, a_booking_success_response -): + aioresponses, a_booking_payload: str, a_booking_success_response: str +) -> None: + """ + Test that a court is booked when there is a success response + from the booking request + + :param aioresponses: the http requests mock + :param a_booking_payload: the payload that is sent for booking + :param a_booking_success_response: the success response mock + """ async with ClientSession() as session: tpc_connector = GestionSportsConnector(session, tpc_url) From badced0a301e0e0691f01153be3507affdcf750f Mon Sep 17 00:00:00 2001 From: stanislas Date: Tue, 20 Feb 2024 10:23:29 +0100 Subject: [PATCH 2/3] Payloads are now built with Jinja templates --- poetry.lock | 88 ++++++++++++++++++- pyproject.toml | 3 +- resa_padel/config.py | 4 + .../gestion_sports/gestion_sports_config.py | 7 ++ .../gestion_sports_connector.py | 35 ++++---- .../gestion_sports_payload_builder.py | 69 --------------- resa_padel/gestion_sports/payload_builders.py | 59 +++++++++++++ .../gestion-sports/booking-payload.txt | 1 + .../gestion-sports/login-payload.txt | 1 + tests/fixtures.py | 12 +-- .../test_gestion_sports_connector.py | 44 ---------- .../test_gestion_sports_payload_builder.py | 74 +++++++++------- 12 files changed, 222 insertions(+), 175 deletions(-) create mode 100644 resa_padel/gestion_sports/gestion_sports_config.py delete mode 100644 resa_padel/gestion_sports/gestion_sports_payload_builder.py create mode 100644 resa_padel/gestion_sports/payload_builders.py create mode 100644 resa_padel/resources/gestion-sports/booking-payload.txt create mode 100644 resa_padel/resources/gestion-sports/login-payload.txt diff --git a/poetry.lock b/poetry.lock index 15769ae..3e7a2a1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -383,6 +383,92 @@ files = [ [package.extras] colors = ["colorama (>=0.4.6)"] +[[package]] +name = "jinja2" +version = "3.1.3" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, + {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "markupsafe" +version = "2.1.5" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, +] + [[package]] name = "multidict" version = "6.0.5" @@ -1151,4 +1237,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "c85317b4b8334d5d25f97ac426cfa6cd1f63c3c3d0726e313c97cf1de43c985a" +content-hash = "eb2292ededdcd551249bd05bdc2fd3d38dfeeba495cb3ea8dd7ba5dfd9250980" diff --git a/pyproject.toml b/pyproject.toml index c278400..94f0cd2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,7 @@ pendulum = "^3.0.0" pydantic = "^2.6.1" pydantic-extra-types = "^2.5.0" python-dotenv = "^1.0.1" +jinja2 = "^3.1.3" [tool.poetry.group.dev.dependencies] black = "^24.1.1" @@ -30,7 +31,7 @@ line-length = 88 [tool.pytest.ini_options] pythonpath = [ - ".", "resa_padel", "rr" + "resa_padel" ] [build-system] diff --git a/resa_padel/config.py b/resa_padel/config.py index 3c162b0..17381a8 100644 --- a/resa_padel/config.py +++ b/resa_padel/config.py @@ -90,3 +90,7 @@ def init_log_config(): with open(logging_file, "r") as f: logging_config = yaml.safe_load(f.read()) logging.config.dictConfig(logging_config) + + +ROOT_PATH = Path(__file__).parent.resolve() +RESOURCES_DIR = Path(ROOT_PATH, "resources") diff --git a/resa_padel/gestion_sports/gestion_sports_config.py b/resa_padel/gestion_sports/gestion_sports_config.py new file mode 100644 index 0000000..4f342c2 --- /dev/null +++ b/resa_padel/gestion_sports/gestion_sports_config.py @@ -0,0 +1,7 @@ +from pathlib import Path + +import config + +RESOURCES_DIR = Path(config.RESOURCES_DIR, "gestion-sports") +BOOKING_TEMPLATE = Path(RESOURCES_DIR, "booking-payload.txt") +LOGIN_TEMPLATE = Path(RESOURCES_DIR, "login-payload.txt") diff --git a/resa_padel/gestion_sports/gestion_sports_connector.py b/resa_padel/gestion_sports/gestion_sports_connector.py index 2a61090..c4ddf47 100644 --- a/resa_padel/gestion_sports/gestion_sports_connector.py +++ b/resa_padel/gestion_sports/gestion_sports_connector.py @@ -6,7 +6,10 @@ from urllib.parse import urljoin from aiohttp import ClientResponse, ClientSession import config -from gestion_sports.gestion_sports_payload_builder import GestionSportsPayloadBuilder +from gestion_sports.payload_builders import ( + GestionSportsLoginPayloadBuilder, + GestionSportsBookingPayloadBuilder, +) from models import BookingFilter, Club, User DATE_FORMAT = "%d/%m/%Y" @@ -26,7 +29,6 @@ class GestionSportsConnector: LOGGER.info("Initializing connection to GestionSports API") self.url = url self.session = session - self.payload_builder = GestionSportsPayloadBuilder() @property def landing_url(self) -> str: @@ -72,12 +74,8 @@ class GestionSportsConnector: :return: the response from the login """ - payload = ( - self.payload_builder.login(user.login) - .password(user.password) - .club_id(club.id) - .build_login_payload() - ) + payload_builder = GestionSportsLoginPayloadBuilder() + payload = payload_builder.user(user).club(club).build() async with self.session.post( self.login_url, data=payload, headers=POST_HEADERS @@ -105,10 +103,11 @@ class GestionSportsConnector: return_exceptions=True, ) - return await self.get_booked_court(bookings) + return self.get_booked_court(bookings) @staticmethod - async def get_booked_court(bookings): + def get_booked_court(bookings): + LOGGER.info(bookings) for court, is_booked in bookings: if is_booked: return court @@ -117,23 +116,23 @@ class GestionSportsConnector: async def book_one_court( self, booking_filter: BookingFilter, court_id: int ) -> tuple[int, bool]: + payload_builder = GestionSportsBookingPayloadBuilder() payload = ( - self.payload_builder.date(booking_filter.date.date().strftime(DATE_FORMAT)) - .time(booking_filter.date.time().strftime(TIME_FORMAT)) - .sport_id(booking_filter.sport_id) - .court_id(court_id) - .build_booking_payload() + payload_builder.booking_filter(booking_filter).court_id(court_id).build() ) - return court_id, await self.is_court_booked(payload) + LOGGER.info(payload) - async def is_court_booked(self, payload: str) -> bool: async with self.session.post( self.booking_url, data=payload, headers=POST_HEADERS ) as response: - return self.is_response_status_ok(await response.text()) + resp_json = await response.text() + + return court_id, self.is_response_status_ok(resp_json) @staticmethod def is_response_status_ok(response: str) -> bool: + + LOGGER.info(response) formatted_result = response.removeprefix('"').removesuffix('"') result_json = json.loads(formatted_result) return result_json["status"] == "ok" diff --git a/resa_padel/gestion_sports/gestion_sports_payload_builder.py b/resa_padel/gestion_sports/gestion_sports_payload_builder.py deleted file mode 100644 index b4511c5..0000000 --- a/resa_padel/gestion_sports/gestion_sports_payload_builder.py +++ /dev/null @@ -1,69 +0,0 @@ -from exceptions import ArgumentMissing - - -class GestionSportsPayloadBuilder: - def __init__(self): - self._login = None - self._password = None - self._club_id = None - self._date = None - self._time = None - self._sport_id = None - self._court_id = None - - def login(self, login: str): - self._login = login - return self - - def password(self, password: str): - self._password = password - return self - - def club_id(self, club_id: str): - self._club_id = club_id - return self - - def date(self, date: str): - self._date = date - return self - - def time(self, time: str): - self._time = time - return self - - def sport_id(self, sport_id: int): - self._sport_id = sport_id - return self - - def court_id(self, court_id: int): - self._court_id = court_id - return self - - def build_login_payload(self): - if self._login is None: - raise ArgumentMissing("Login not provided") - if self.password is None: - raise ArgumentMissing("Password not provided") - if self._club_id is None: - raise ArgumentMissing("Club ID not provided") - - return ( - f"ajax=connexionUser&id_club={self._club_id}&email={self._login}&form_ajax=1&pass={self._password}&compte" - f"=user&playeridonesignal=0&identifiant=identifiant&externCo=true" - ).encode("utf-8") - - def build_booking_payload(self): - if self._date is None: - raise ArgumentMissing("Date not provided") - if self._time is None: - raise ArgumentMissing("Time not provided") - if self._sport_id is None: - raise ArgumentMissing("Sport ID not provided") - if self._court_id is None: - raise ArgumentMissing("Court ID not provided") - - return ( - f"ajax=addResa&date={self._date}&hour={self._time}&duration=90&partners=null|null|null" - f"&paiement=facultatif&idSport={self._sport_id}&creaPartie=false&idCourt={self._court_id}" - f"&pay=false&token=undefined&totalPrice=44&saveCard=0&foodNumber=0" - ).encode("utf-8") diff --git a/resa_padel/gestion_sports/payload_builders.py b/resa_padel/gestion_sports/payload_builders.py new file mode 100644 index 0000000..4673a00 --- /dev/null +++ b/resa_padel/gestion_sports/payload_builders.py @@ -0,0 +1,59 @@ +from jinja2 import Environment, FileSystemLoader + +from exceptions import ArgumentMissing +from gestion_sports.gestion_sports_config import LOGIN_TEMPLATE, BOOKING_TEMPLATE +from models import User, Club, BookingFilter + + +class GestionSportsLoginPayloadBuilder: + def __init__(self): + self._user: User | None = None + self._club: Club | None = None + self._template = LOGIN_TEMPLATE + + def user(self, user: User): + self._user = user + return self + + def club(self, club: Club): + self._club = club + return self + + def build(self): + if self._user is None: + raise ArgumentMissing("No user was provided") + if self._club is None: + raise ArgumentMissing("No club was provided") + + environment = Environment(loader=FileSystemLoader(self._template.parent)) + template = environment.get_template(self._template.name) + + return template.render(club=self._club, user=self._user) + + +class GestionSportsBookingPayloadBuilder: + def __init__(self): + self._booking_filter: BookingFilter | None = None + self._court_id: int | None = None + self._template = BOOKING_TEMPLATE + + def booking_filter(self, booking_filter: BookingFilter): + self._booking_filter = booking_filter + return self + + def court_id(self, court_id: int): + self._court_id = court_id + return self + + def build(self): + if self._booking_filter is None: + raise ArgumentMissing("No booking filter was provided") + if self.court_id is None: + raise ArgumentMissing("No court id was provided") + + environment = Environment(loader=FileSystemLoader(self._template.parent)) + template = environment.get_template(self._template.name) + + return template.render( + court_id=self._court_id, booking_filter=self._booking_filter + ) diff --git a/resa_padel/resources/gestion-sports/booking-payload.txt b/resa_padel/resources/gestion-sports/booking-payload.txt new file mode 100644 index 0000000..f2f5fe3 --- /dev/null +++ b/resa_padel/resources/gestion-sports/booking-payload.txt @@ -0,0 +1 @@ +ajax=addResa&date={{ booking_filter.date.date().strftime("%d/%m/%Y") }}&hour={{ booking_filter.date.time().strftime("%H:%M") }}&duration=90&partners=null|null|null&paiement=facultatif&idSport={{ booking_filter.sport_id }}&creaPartie=false&idCourt={{ court_id }}&pay=false&token=undefined&totalPrice=44&saveCard=0&foodNumber=0 \ No newline at end of file diff --git a/resa_padel/resources/gestion-sports/login-payload.txt b/resa_padel/resources/gestion-sports/login-payload.txt new file mode 100644 index 0000000..5d64938 --- /dev/null +++ b/resa_padel/resources/gestion-sports/login-payload.txt @@ -0,0 +1 @@ +ajax=connexionUser&id_club={{ club.id }}&email={{ user.login }}&form_ajax=1&pass={{ user.password }}&compte=user&playeridonesignal=0&identifiant=identifiant&externCo=true \ No newline at end of file diff --git a/tests/fixtures.py b/tests/fixtures.py index b3ddac0..3c747ec 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -3,9 +3,7 @@ import json import pendulum import pytest -from resa_padel.gestion_sports.gestion_sports_payload_builder import ( - GestionSportsPayloadBuilder, -) +from gestion_sports.payload_builders import GestionSportsBookingPayloadBuilder from resa_padel.models import BookingFilter, Club, User user = User(login="padel.testing@jouf.fr", password="ridicule", club_id="123") @@ -38,12 +36,10 @@ booking_success_response = json.dumps( date_format = "%d/%m/%Y" time_format = "%H:%M" booking_payload = ( - GestionSportsPayloadBuilder() - .date(booking_date.date().strftime(date_format)) - .time(booking_date.time().strftime(time_format)) - .sport_id(sport_id) + GestionSportsBookingPayloadBuilder() + .booking_filter(booking_filter) .court_id(courts[0]) - .build_booking_payload() + .build() ) diff --git a/tests/gestion_sports/test_gestion_sports_connector.py b/tests/gestion_sports/test_gestion_sports_connector.py index 52d6edd..a32de8b 100644 --- a/tests/gestion_sports/test_gestion_sports_connector.py +++ b/tests/gestion_sports/test_gestion_sports_connector.py @@ -7,7 +7,6 @@ from resa_padel.gestion_sports.gestion_sports_connector import GestionSportsConn from tests.fixtures import ( a_booking_failure_response, a_booking_filter, - a_booking_payload, a_booking_success_response, a_club, a_user, @@ -140,46 +139,3 @@ def test_response_status_should_be_not_ok(a_booking_failure_response: str) -> No """ is_booked = GestionSportsConnector.is_response_status_ok(a_booking_failure_response) assert not is_booked - - -@pytest.mark.asyncio -async def test_court_should_not_be_booked( - aioresponses, a_booking_payload: str, a_booking_failure_response: str -) -> None: - """ - Test that no court is booked when there is a failure response - from the booking request - - :param aioresponses: the http requests mock - :param a_booking_payload: the payload that is sent for booking - :param a_booking_failure_response: the failure response mock - """ - async with ClientSession() as session: - tpc_connector = GestionSportsConnector(session, tpc_url) - aioresponses.post( - URL(tpc_connector.booking_url), status=200, body=a_booking_failure_response - ) - is_booked = await tpc_connector.is_court_booked(a_booking_payload) - assert not is_booked - - -@pytest.mark.asyncio -async def test_court_should_be_booked( - aioresponses, a_booking_payload: str, a_booking_success_response: str -) -> None: - """ - Test that a court is booked when there is a success response - from the booking request - - :param aioresponses: the http requests mock - :param a_booking_payload: the payload that is sent for booking - :param a_booking_success_response: the success response mock - """ - async with ClientSession() as session: - tpc_connector = GestionSportsConnector(session, tpc_url) - - aioresponses.post( - URL(tpc_connector.booking_url), status=200, body=a_booking_success_response - ) - is_booked = await tpc_connector.is_court_booked(a_booking_payload) - assert is_booked diff --git a/tests/gestion_sports/test_gestion_sports_payload_builder.py b/tests/gestion_sports/test_gestion_sports_payload_builder.py index cca8376..3e56949 100644 --- a/tests/gestion_sports/test_gestion_sports_payload_builder.py +++ b/tests/gestion_sports/test_gestion_sports_payload_builder.py @@ -1,44 +1,50 @@ -import pendulum - -from resa_padel.gestion_sports.gestion_sports_payload_builder import ( - GestionSportsPayloadBuilder, +from resa_padel.gestion_sports.payload_builders import ( + GestionSportsLoginPayloadBuilder, + GestionSportsBookingPayloadBuilder, ) +from tests.fixtures import a_user, a_club, a_booking_filter -def test_login_payload_should_be_built(): - payload_builder = GestionSportsPayloadBuilder() - login = "jacques" - password = "chirac" - club_id = "27" - login_payload = ( - payload_builder.login(login) - .password(password) - .club_id(club_id) - .build_login_payload() +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 == ( - f"ajax=connexionUser&id_club={club_id}&email={login}&form_ajax=1&pass={password}&compte" - f"=user&playeridonesignal=0&identifiant=identifiant&externCo=true" - ).encode("utf-8") + assert login_payload == expected_payload -def test_booking_payload_should_be_built(): - booking_builder = GestionSportsPayloadBuilder() - booking_date = pendulum.now("Europe/Paris") - sport_id = "27" - court_id = "55" +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.date(booking_date.date()) - .time(booking_date.time()) - .sport_id(sport_id) - .court_id(court_id) - .build_booking_payload() + booking_builder.booking_filter(a_booking_filter).court_id(4).build() ) - assert booking_payload == ( - f"ajax=addResa&date={booking_date.date()}&hour={booking_date.time()}&duration=90" - f"&partners=null|null|null&paiement=facultatif&idSport={sport_id}" - f"&creaPartie=false&idCourt={court_id}&pay=false&token=undefined&totalPrice=44&saveCard=0" - f"&foodNumber=0" - ).encode("utf-8") + 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 From f813e4b973304e3e282b40651cd210d297858cc3 Mon Sep 17 00:00:00 2001 From: stanislas Date: Tue, 20 Feb 2024 10:26:28 +0100 Subject: [PATCH 3/3] Isorted and blacked files --- resa_padel/booking.py | 5 ++--- resa_padel/config.py | 1 - resa_padel/gestion_sports/gestion_sports_connector.py | 5 ++--- resa_padel/gestion_sports/payload_builders.py | 7 +++---- resa_padel/models.py | 2 +- tests/fixtures.py | 2 +- tests/gestion_sports/test_gestion_sports_connector.py | 2 +- .../gestion_sports/test_gestion_sports_payload_builder.py | 4 ++-- tests/test_booking.py | 2 +- 9 files changed, 13 insertions(+), 17 deletions(-) diff --git a/resa_padel/booking.py b/resa_padel/booking.py index 66a94cc..144358d 100644 --- a/resa_padel/booking.py +++ b/resa_padel/booking.py @@ -2,13 +2,12 @@ import asyncio import logging import time +import config import pendulum from aiohttp import ClientSession -from pendulum import DateTime - -import config from gestion_sports.gestion_sports_connector import GestionSportsConnector from models import BookingFilter, Club, User +from pendulum import DateTime LOGGER = logging.getLogger(__name__) diff --git a/resa_padel/config.py b/resa_padel/config.py index 17381a8..ad15c18 100644 --- a/resa_padel/config.py +++ b/resa_padel/config.py @@ -6,7 +6,6 @@ from pathlib import Path import pendulum import yaml from dotenv import load_dotenv - from models import BookingFilter, Club, User load_dotenv() diff --git a/resa_padel/gestion_sports/gestion_sports_connector.py b/resa_padel/gestion_sports/gestion_sports_connector.py index c4ddf47..85d6eb2 100644 --- a/resa_padel/gestion_sports/gestion_sports_connector.py +++ b/resa_padel/gestion_sports/gestion_sports_connector.py @@ -3,12 +3,11 @@ import json import logging from urllib.parse import urljoin -from aiohttp import ClientResponse, ClientSession - import config +from aiohttp import ClientResponse, ClientSession from gestion_sports.payload_builders import ( - GestionSportsLoginPayloadBuilder, GestionSportsBookingPayloadBuilder, + GestionSportsLoginPayloadBuilder, ) from models import BookingFilter, Club, User diff --git a/resa_padel/gestion_sports/payload_builders.py b/resa_padel/gestion_sports/payload_builders.py index 4673a00..287febb 100644 --- a/resa_padel/gestion_sports/payload_builders.py +++ b/resa_padel/gestion_sports/payload_builders.py @@ -1,8 +1,7 @@ -from jinja2 import Environment, FileSystemLoader - from exceptions import ArgumentMissing -from gestion_sports.gestion_sports_config import LOGIN_TEMPLATE, BOOKING_TEMPLATE -from models import User, Club, BookingFilter +from gestion_sports.gestion_sports_config import BOOKING_TEMPLATE, LOGIN_TEMPLATE +from jinja2 import Environment, FileSystemLoader +from models import BookingFilter, Club, User class GestionSportsLoginPayloadBuilder: diff --git a/resa_padel/models.py b/resa_padel/models.py index 8971b8e..1857750 100644 --- a/resa_padel/models.py +++ b/resa_padel/models.py @@ -1,5 +1,5 @@ from pendulum import Time -from pydantic import BaseModel, Field, ConfigDict +from pydantic import BaseModel, ConfigDict, Field from pydantic_extra_types.pendulum_dt import DateTime diff --git a/tests/fixtures.py b/tests/fixtures.py index 3c747ec..220ee92 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -2,8 +2,8 @@ import json import pendulum import pytest - from gestion_sports.payload_builders import GestionSportsBookingPayloadBuilder + from resa_padel.models import BookingFilter, Club, User user = User(login="padel.testing@jouf.fr", password="ridicule", club_id="123") diff --git a/tests/gestion_sports/test_gestion_sports_connector.py b/tests/gestion_sports/test_gestion_sports_connector.py index a32de8b..c7731dd 100644 --- a/tests/gestion_sports/test_gestion_sports_connector.py +++ b/tests/gestion_sports/test_gestion_sports_connector.py @@ -1,8 +1,8 @@ import pytest from aiohttp import ClientSession +from models import BookingFilter, Club, User from yarl import URL -from models import BookingFilter, Club, User from resa_padel.gestion_sports.gestion_sports_connector import GestionSportsConnector from tests.fixtures import ( a_booking_failure_response, diff --git a/tests/gestion_sports/test_gestion_sports_payload_builder.py b/tests/gestion_sports/test_gestion_sports_payload_builder.py index 3e56949..0b917c3 100644 --- a/tests/gestion_sports/test_gestion_sports_payload_builder.py +++ b/tests/gestion_sports/test_gestion_sports_payload_builder.py @@ -1,8 +1,8 @@ from resa_padel.gestion_sports.payload_builders import ( - GestionSportsLoginPayloadBuilder, GestionSportsBookingPayloadBuilder, + GestionSportsLoginPayloadBuilder, ) -from tests.fixtures import a_user, a_club, a_booking_filter +from tests.fixtures import a_booking_filter, a_club, a_user def test_login_payload_should_be_built(a_user, a_club): diff --git a/tests/test_booking.py b/tests/test_booking.py index a518462..e2667fe 100644 --- a/tests/test_booking.py +++ b/tests/test_booking.py @@ -5,9 +5,9 @@ from urllib.parse import urljoin import pendulum from aioresponses import aioresponses +from models import BookingFilter, Club, User from pendulum import DateTime, Time -from models import BookingFilter, Club, User from resa_padel import booking from tests import fixtures from tests.fixtures import (