From 84288a19f18ef22112cfc204837dfa03a790ebd0 Mon Sep 17 00:00:00 2001 From: Imenbr Date: Thu, 5 Sep 2024 12:45:37 +0200 Subject: [PATCH] (fix) handle booking places for a past competition --- competitions.json | 5 + server.py | 60 ++++-- ...est_booking-places-in-past-competitions.py | 48 +++++ ...est_booking-places-in-past-competitions.py | 189 ++++++++++++++++++ 4 files changed, 280 insertions(+), 22 deletions(-) create mode 100644 tests/integration_tests/test_booking-places-in-past-competitions.py create mode 100644 tests/unit_tests/test_booking-places-in-past-competitions.py diff --git a/competitions.json b/competitions.json index 039fc61bd..a40f93442 100644 --- a/competitions.json +++ b/competitions.json @@ -9,6 +9,11 @@ "name": "Fall Classic", "date": "2020-10-22 13:30:00", "numberOfPlaces": "13" + }, + { + "name": "New", + "date": "2024-10-22 13:30:00", + "numberOfPlaces": "13" } ] } \ No newline at end of file diff --git a/server.py b/server.py index 188eec454..8b5b76e37 100644 --- a/server.py +++ b/server.py @@ -1,4 +1,5 @@ import json +from datetime import datetime from flask import Flask, render_template, request, redirect, flash, url_for app = Flask(__name__) @@ -29,9 +30,7 @@ def index(): def get_club_from_email(email): try: - club = [ - club for club in clubs if club["email"] == email - ][0] + club = [club for club in clubs if club["email"] == email][0] return club except IndexError: return None @@ -41,33 +40,48 @@ def get_club_from_email(email): def showSummary(): club = get_club_from_email(request.form["email"]) if club: - return render_template("welcome.html", club=club, - competitions=competitions) + return render_template( + "welcome.html", club=club, competitions=competitions + ) else: flash("Sorry, that email wasn't found.") return redirect(url_for("index")) +def validate_competition_date(competition): + competition_date = datetime.strptime( + competition["date"], "%Y-%m-%d %H:%M:%S" + ) + if competition_date < datetime.now(): + return "This competition is already over. You cannot book a place." + + @app.route("/book//") def book(competition, club): - foundClub = [c for c in clubs if c["name"] == club][0] - foundCompetition = [c for c in competitions if c["name"] == competition][0] - if foundClub and foundCompetition: - return render_template( - "booking.html", club=foundClub, competition=foundCompetition - ) - else: + foundClub = get_club_from_name(club) + foundCompetition = get_competition_from_name(competition) + if not foundClub or not foundCompetition: flash("Something went wrong-please try again") return render_template( "welcome.html", club=club, competitions=competitions ) + error_message = validate_competition_date(foundCompetition) + if error_message: + flash(error_message) + return render_template( + "welcome.html", club=foundClub, competitions=competitions + ) + return render_template( + "booking.html", club=foundClub, competition=foundCompetition + ) def get_competition_from_name(name): try: competition = [ - competition for competition in - competitions if competition["name"] == name + competition + for competition in competitions + if competition["name"] == name ][0] return competition except IndexError: @@ -76,9 +90,7 @@ def get_competition_from_name(name): def get_club_from_name(name): try: - club = [ - club for club in clubs if club["name"] == name - ][0] + club = [club for club in clubs if club["name"] == name][0] return club except IndexError: return None @@ -88,16 +100,19 @@ def check_places(places, club): if not places or int(places) < 1: return "Places required must be a positive integer" if int(places) > 12: - return ("Places required must be a positive integer " - "that does not exceed 12") + return ( + "Places required must be a positive integer " + "that does not exceed 12" + ) if int(places) > int(club["points"]): return "Places required exceed club's total points" def take_places(places, club, competition): try: - competition["numberOfPlaces"] = \ + competition["numberOfPlaces"] = ( int(competition["numberOfPlaces"]) - places + ) club["points"] = int(club["points"]) - places return True except Exception: @@ -119,8 +134,9 @@ def purchasePlaces(): if take_places(placesRequired, club, competition): flash("Great-booking complete!") - return render_template("welcome.html", club=club, - competitions=competitions) + return render_template( + "welcome.html", club=club, competitions=competitions + ) else: flash("Something went wrong-please try again") return redirect( diff --git a/tests/integration_tests/test_booking-places-in-past-competitions.py b/tests/integration_tests/test_booking-places-in-past-competitions.py new file mode 100644 index 000000000..93ec674d4 --- /dev/null +++ b/tests/integration_tests/test_booking-places-in-past-competitions.py @@ -0,0 +1,48 @@ +def test_book_valid_competition(client): + """ + Test booking a valid competition with a valid club + (future competition date). + """ + response = client.get("/book/Competition%201/Club%201") + + # Ensure the correct template is rendered (booking.html) + assert response.status_code == 200 + assert ( + b"Competition 1" in response.data + ) # Assuming the booking page has the word "Booking" + + +def test_book_past_competition(client): + """ + Test booking a competition with a past date. + """ + response = client.get("/book/Competition%202/Club%201") + + # Ensure the user is shown a message that the competition is in the past + assert response.status_code == 200 + assert ( + b"This competition is already over. You cannot book a place." + in response.data + ) + + +def test_book_invalid_competition(client): + """ + Test trying to book with an invalid competition name. + """ + response = client.get("/book/Invalid%20Competition/Club%201") + + # Ensure the correct message is shown when competition is invalid + assert response.status_code == 200 + assert b"Something went wrong-please try again" in response.data + + +def test_book_invalid_club(client): + """ + Test trying to book with an invalid club name. + """ + response = client.get("/book/Competition%201/Invalid%20Club") + + # Ensure the correct message is shown when club is invalid + assert response.status_code == 200 + assert b"Something went wrong-please try again" in response.data diff --git a/tests/unit_tests/test_booking-places-in-past-competitions.py b/tests/unit_tests/test_booking-places-in-past-competitions.py new file mode 100644 index 000000000..c31115ae9 --- /dev/null +++ b/tests/unit_tests/test_booking-places-in-past-competitions.py @@ -0,0 +1,189 @@ +from unittest.mock import patch +from datetime import datetime + +from server import validate_competition_date +from .utils import mock_competitions_list, mock_clubs_list + +mock_competition_past = { + "name": "Competition 2", + "date": "2020-03-27 10:00:00", + "numberOfPlaces": "15", +} +mock_future_competition = { + "name": "Future Competition", + "date": "2024-03-27 10:00:00", +} + + +# Test for a valid future competition date +@patch("server.datetime") +def test_validate_competition_date_future(mock_datetime): + # Mock datetime.now() to return a date before the competition date + mock_datetime.now.return_value = datetime(2023, 3, 27) + mock_datetime.strptime.side_effect = datetime.strptime + + # Call the function with a future competition + result = validate_competition_date(mock_future_competition) + + # Assert that no error message is returned + assert result is None + + +# Test for a past competition date +@patch("server.datetime") +def test_validate_competition_date_past(mock_datetime): + # Mock datetime.now() to return a date after the competition date + mock_datetime.now.return_value = datetime(2023, 3, 27) + mock_datetime.strptime.side_effect = datetime.strptime + + # Call the function with a past competition + result = validate_competition_date(mock_competition_past) + + # Assert that the correct error message is returned + assert ( + result == "This competition is already over. You cannot book a place." + ) + + +# Test for valid club and valid competition (future date) +@patch("server.render_template") +@patch("server.validate_competition_date") +@patch("server.get_club_from_name") +@patch("server.get_competition_from_name") +def test_book_valid_competition( + mock_get_competition, + mock_get_club, + mock_validate_date, + mock_render_template, + client, +): + # Mock valid club and competition + mock_get_competition.return_value = mock_competitions_list[0] + mock_get_club.return_value = mock_clubs_list[0] + mock_validate_date.return_value = None # No validation error + + # Simulate GET request to the route + response = client.get("/book/Competition%201/Club%201") + + # Assert that the necessary functions are called with the correct parameters + mock_get_competition.assert_called_once_with("Competition 1") + mock_get_club.assert_called_once_with("Club 1") + mock_validate_date.assert_called_once_with(mock_competitions_list[0]) + mock_render_template.assert_called_once_with( + "booking.html", + club=mock_clubs_list[0], + competition=mock_competitions_list[0], + ) + + # Check the response status code + assert response.status_code == 200 + + +# Test for valid club and past competition (competition already over) +@patch("server.competitions", mock_competitions_list) +@patch("server.render_template") +@patch("server.flash") +@patch("server.get_club_from_name") +@patch("server.get_competition_from_name") +@patch("server.validate_competition_date") +def test_book_past_competition( + mock_validate_date, + mock_get_competition, + mock_get_club, + mock_flash, + mock_render_template, + client, +): + # Mock valid club but past competition + mock_get_competition.return_value = mock_competition_past + mock_get_club.return_value = mock_clubs_list[0] + mock_validate_date.return_value = ( + "This competition is already over. You cannot book a place." + ) + + # Simulate GET request to the route + response = client.get("/book/Competition%202/Club%201") + + # Assert that the necessary functions are called with the correct parameters + mock_get_competition.assert_called_once_with("Competition 2") + mock_get_club.assert_called_once_with("Club 1") + mock_validate_date.assert_called_once_with(mock_competition_past) + mock_flash.assert_called_once_with( + "This competition is already over. You cannot book a place." + ) + mock_render_template.assert_called_once_with( + "welcome.html", + club=mock_clubs_list[0], + competitions=mock_competitions_list, + ) + + # Check the response status code + assert response.status_code == 200 + + +# Test for missing club or competition +@patch("server.competitions", mock_competitions_list) +@patch("server.render_template") +@patch("server.flash") +@patch("server.get_club_from_name") +@patch("server.get_competition_from_name") +def test_book_missing_club_or_competition( + mock_get_competition, + mock_get_club, + mock_flash, + mock_render_template, + client, +): + # Mock a case where the competition is missing + mock_get_competition.return_value = None # Competition not found + mock_get_club.return_value = mock_clubs_list[0] # Club found + + # Simulate GET request to the route + response = client.get("/book/Competition%201/Club%201") + + # Assert that the necessary functions are called with the correct parameters + mock_get_competition.assert_called_once_with("Competition 1") + mock_get_club.assert_called_once_with("Club 1") + mock_flash.assert_called_once_with("Something went wrong-please try again") + mock_render_template.assert_called_once_with( + "welcome.html", + club=mock_clubs_list[0]["name"], + competitions=mock_competitions_list, + ) + + # Check the response status code + assert response.status_code == 200 + + +# Test for invalid club +@patch("server.competitions", mock_competitions_list) +@patch("server.render_template") +@patch("server.flash") +@patch("server.get_club_from_name") +@patch("server.get_competition_from_name") +def test_book_invalid_club( + mock_get_competition, + mock_get_club, + mock_flash, + mock_render_template, + client, +): + # Mock a case where the club is invalid + mock_get_competition.return_value = mock_competitions_list[ + 0 + ] # Competition found + mock_get_club.return_value = None # Club not found + + # Simulate GET request to the route + response = client.get("/book/Competition%201/Invalid%20Club") + + # Assert that the necessary functions are called with the correct parameters + mock_get_competition.assert_called_once_with("Competition 1") + mock_get_club.assert_called_once_with("Invalid Club") + mock_flash.assert_called_once_with("Something went wrong-please try again") + mock_render_template.assert_called_once_with( + "welcome.html", club="Invalid Club", competitions=mock_competitions_list + ) + + # Check the response status code + assert response.status_code == 200