From 38209866c1a3b96a91121d16fc26b1506a653b2a Mon Sep 17 00:00:00 2001 From: rafaRemote Date: Wed, 22 Dec 2021 11:10:17 +0100 Subject: [PATCH] [#2] - user use more points than available - fix and tests - fix: ui: booking.html: html input attribute min set to 1 and max set to club["points"] - fix: server.py: purchase_places(): conditional structure implemented, checks redeemed points amount against competition places available - tests: class TestPurchase created: method: test_should_not_use_more_points_than_have - tests: class TestPurchase: method: test_should_deduct_points_from_club --- server.py | 46 +++++++++++++++++-------- templates/booking.html | 17 ++++++--- templates/index.html | 2 +- templates/welcome.html | 4 +-- tests/conftest.py | 19 +++++------ tests/unit/test_server.py | 72 ++++++++++++++++++++++++++++++++++++--- 6 files changed, 124 insertions(+), 36 deletions(-) diff --git a/server.py b/server.py index 7a2e3218e..cd0813583 100644 --- a/server.py +++ b/server.py @@ -17,25 +17,26 @@ def load_competitions(): app = Flask(__name__) app.secret_key = "something_special" +PLACE_COST = 1 competitions = load_competitions() clubs = load_clubs() -@app.route("/") +@app.route("/", strict_slashes=False) def index(): return render_template("index.html") -@app.route("/showSummary", methods=["POST"]) -def showSummary(): +@app.route("/show-summary", methods=["POST"], strict_slashes=False) +def show_summary(): try: club = [club for club in clubs if club["email"] == request.form["email"]][0] - except IndexError: + except (IndexError, TypeError): return page_not_found() return render_template("welcome.html", club=club, competitions=competitions) -@app.route("/book//") +@app.route("/book//", strict_slashes=False) def book(competition, club): found_club = [c for c in clubs if c["name"] == club][0] found_competition = [c for c in competitions if c["name"] == competition][0] @@ -48,22 +49,39 @@ def book(competition, club): return render_template("welcome.html", club=club, competitions=competitions) -@app.route("/purchasePlaces", methods=["POST"]) -def purchasePlaces(): - competition = [c for c in competitions if c["name"] == request.form["competition"]][ +@app.route("/purchase-places", methods=["POST"], strict_slashes=False) +def purchase_places(): + compet = [c for c in competitions if c["name"] == request.form["competition_name"]][ 0 ] - club = [c for c in clubs if c["name"] == request.form["club"]][0] - placesRequired = int(request.form["places"]) - competition["numberOfPlaces"] = int(competition["numberOfPlaces"]) - placesRequired - flash("Great-booking complete!") - return render_template("welcome.html", club=club, competitions=competitions) + club = [c for c in clubs if c["name"] == request.form["club_name"]][0] + places_required = int(request.form["places"]) + if places_required > int(club["points"]): + flash( + f"You cannot perform this action" + f"You asked {places_required} place(s), and you have {club['points']} point(s)" + f"A place costs {PLACE_COST} point(s)" + f"Therefore you need {places_required * PLACE_COST} point(s) to book {places_required}" + ) + return render_template("booking.html", club=club, competition=compet) + elif places_required > int(compet["numberOfPlaces"]): + flash( + f"You cannot perform this action" + f"You asked {places_required} place(s)" + f"and the competition has {compet['numberOfPlaces']} places left" + ) + return render_template("booking.html", club=club, competition=compet) + else: + flash("Great-booking complete!") + club["points"] = int(club["points"]) - places_required + compet["numberOfPlaces"] = int(compet["numberOfPlaces"]) - places_required + return render_template("welcome.html", club=club, competitions=competitions) # TODO: Add route for points display -@app.route("/logout") +@app.route("/logout", strict_slashes=False) def logout(): return redirect(url_for("index")) diff --git a/templates/booking.html b/templates/booking.html index 06ae1156c..7712845be 100644 --- a/templates/booking.html +++ b/templates/booking.html @@ -7,10 +7,19 @@

{{competition['name']}}

Places available: {{competition['numberOfPlaces']}} -
- - - + {% with messages = get_flashed_messages()%} + {% if messages %} +
    + {% for message in messages %} +
  • {{message}}
  • + {% endfor %} +
+ {% endif%} + {% endwith %} + + + +
diff --git a/templates/index.html b/templates/index.html index 566ca7f9c..afd351ef3 100644 --- a/templates/index.html +++ b/templates/index.html @@ -7,7 +7,7 @@

Welcome to the GUDLFT Registration Portal!

Please enter your secretary email to continue: -
+ diff --git a/templates/welcome.html b/templates/welcome.html index ff6b261a2..0b945f5c8 100644 --- a/templates/welcome.html +++ b/templates/welcome.html @@ -5,7 +5,7 @@ Summary | GUDLFT Registration -

Welcome, {{club['email']}}

Logout +

Welcome, {{club['email']}}

Logout {% with messages = get_flashed_messages()%} {% if messages %} @@ -15,7 +15,7 @@

Welcome, {{club['email']}}

Logout {% endfor %} {% endif%} - Points available: {{club['points']}} + Points Available: {{club['points']}}

Competitions:

    {% for comp in competitions%} diff --git a/tests/conftest.py b/tests/conftest.py index 57483c320..534dbcff4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -11,15 +11,12 @@ def client(): @pytest.fixture def competition(): - next_year = str(int(dt.datetime.now().strftime("%Y")) + 1) - competition = [ - { - "name": "test future competition", - "date": dt.datetime.now().strftime(f"%{next_year}-%m-%d %H:%M:%S"), - "numberOfPlaces": "100", - } - ] - return competition() + competition = { + "name": "test competition 1", + "date": "2100-12-10 10:00:00", + "numberOfPlaces": "10", + } + return competition @pytest.fixture @@ -59,7 +56,7 @@ def past_competition(): @pytest.fixture def club(): - club = {"name": "test club", "email": "test1@test.com", "points": "100"} + club = {"name": "test club 1", "email": "test1@test.com", "points": "100"} return club @@ -76,7 +73,7 @@ def unlisted_club(): @pytest.fixture def clubs(): clubs = [ - {"name": "test club 1", "email": "test1@test.com", "points": "10"}, + {"name": "test club 1", "email": "test1@test.com", "points": "100"}, {"name": "test club 2", "email": "test2@test.com", "points": "20"}, {"name": "test club 3", "email": "test3@test.com", "points": "30"}, ] diff --git a/tests/unit/test_server.py b/tests/unit/test_server.py index 4488dd111..bccd24077 100644 --- a/tests/unit/test_server.py +++ b/tests/unit/test_server.py @@ -1,15 +1,35 @@ +import random import server import pytest +class TestEndpoints: + @pytest.mark.parametrize("endpoint, status_code", [("/", 200), ("/logout", 200)]) + def test_access_unauthenticated_should_200(self, client, endpoint, status_code): + """Checks response when unauthenticated user request""" + response = client.get(endpoint, follow_redirects=True) + assert response.status_code == status_code + + @pytest.mark.parametrize( + "endpoint, status_code", [("/show-summary", 405), ("/purchase-places", 405)] + ) + def test_access_unauthenticated_user_should_405( + self, client, endpoint, status_code + ): + """Checks response when unauthenticated user request""" + response = client.get(endpoint, follow_redirects=True) + assert response.status_code == status_code + + class TestLogin: - def test_login_listed_email_shoudl_200( + def test_login_listed_email_should_200( self, client, mocker, club, clubs, competitions ): + """Checks response when authenticated user request""" mocker.patch.object(server, "clubs", clubs) mocker.patch.object(server, "competitions", competitions) data = {"email": club["email"]} - response = client.post("/showSummary", data=data, follow_redirects=True) + response = client.post("/show-summary", data=data, follow_redirects=True) assert response.status_code == 200 @pytest.mark.parametrize( @@ -23,14 +43,58 @@ def test_login_listed_email_shoudl_200( def test_login_unlisted_mails_should_404( self, client, mocker, email, status_code, clubs, competitions ): + """Checks response when unauthenticated user request""" mocker.patch.object(server, "clubs", clubs) mocker.patch.object(server, "competitions", competitions) - response = client.post("/showSummary", data=email, follow_redirects=True) + response = client.post("/show-summary", data=email, follow_redirects=True) assert response.status_code == status_code def test_login_bad_request(self, client, mocker, club, clubs, competitions): + """Checks response when bad request""" mocker.patch.object(server, "clubs", clubs) mocker.patch.object(server, "competitions", competitions) data = {"address": club["email"]} - response = client.post("/showSummary", data=data, follow_redirects=True) + response = client.post("/show-summary", data=data, follow_redirects=True) assert response.status_code == 400 + + +class TestPurchase: + def test_should_not_use_more_points_than_have( + self, client, mocker, club, competition, clubs, competitions + ): + """Checks response when user try to use more points than his club has""" + mocker.patch.object(server, "clubs", clubs) + mocker.patch.object(server, "competitions", competitions) + places = int(competition["numberOfPlaces"]) + 1 + data = { + "club_name": club["name"], + "competition_name": competition["name"], + "places": str(places), + } + response = client.post("/purchase-places", data=data, follow_redirects=True) + data = response.data.decode("utf-8").split() + assert "cannot" in data + + def test_should_deduct_points_from_club( + self, client, mocker, club, competition, clubs, competitions + ): + """Check club points in response after user book places""" + mocker.patch.object(server, "clubs", clubs) + mocker.patch.object(server, "competitions", competitions) + places = int(competition["numberOfPlaces"]) + club_points = int(club["points"]) + if places > 0: + if club_points > places: + max = places + else: + max = club_points + places_required = random.choice(range(1, max + 1)) + data = { + "club_name": club["name"], + "competition_name": competition["name"], + "places": str(places_required), + } + response = client.post("/purchase-places", data=data, follow_redirects=True) + data = response.data.decode("utf-8").split() + club_points_left = int(data[(data.index("Available:") + 1)]) + assert club_points_left == club_points - places_required