From 466fb7fb8cf648b0c623f27f887bf91bf2ac4ac8 Mon Sep 17 00:00:00 2001 From: NM Date: Mon, 9 Mar 2026 18:08:01 +0100 Subject: [PATCH 1/6] Fix issue #1 : unknown email crashes app, add message in index template --- .gitignore | 3 ++- server.py | 8 +++++++- templates/index.html | 11 +++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 2cba99d87..6c234efec 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ lib .Python tests/ .envrc -__pycache__ \ No newline at end of file +__pycache__ +.idea/ \ No newline at end of file diff --git a/server.py b/server.py index 4084baeac..83785a34f 100644 --- a/server.py +++ b/server.py @@ -26,7 +26,13 @@ def index(): @app.route('/showSummary',methods=['POST']) def showSummary(): - club = [club for club in clubs if club['email'] == request.form['email']][0] + + club = next((club for club in clubs if club['email'] == request.form['email']), None) + + if club is None: + flash("Sorry, that email wasn't found.") + return render_template("index.html", error="Email not found"), 404 + return render_template('welcome.html',club=club,competitions=competitions) diff --git a/templates/index.html b/templates/index.html index 926526b7d..56b19b23c 100644 --- a/templates/index.html +++ b/templates/index.html @@ -6,6 +6,17 @@

Welcome to the GUDLFT Registration Portal!

+ + {% with messages = get_flashed_messages()%} + {% if messages %} + + {% endif%} + {%endwith%} + Please enter your secretary email to continue:
From 9e1c454e4673244335d6b6f160c88c835bb9dcc0 Mon Sep 17 00:00:00 2001 From: NM Date: Wed, 11 Mar 2026 11:21:08 +0100 Subject: [PATCH 2/6] File change : add unit tests --- .gitignore | 1 - clubs.json | 2 +- server.py | 2 +- unit_tests/__init__.py | 0 unit_tests/conftest.py | 56 ++++++++++++++ unit_tests/test_app.py | 169 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 227 insertions(+), 3 deletions(-) create mode 100644 unit_tests/__init__.py create mode 100644 unit_tests/conftest.py create mode 100644 unit_tests/test_app.py diff --git a/.gitignore b/.gitignore index 6c234efec..e7e28eb67 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ bin include lib .Python -tests/ .envrc __pycache__ .idea/ \ No newline at end of file diff --git a/clubs.json b/clubs.json index 1d7ad1ffe..1e7f3a13f 100644 --- a/clubs.json +++ b/clubs.json @@ -11,6 +11,6 @@ }, { "name":"She Lifts", "email": "kate@shelifts.co.uk", - "points":"12" + "points":"15" } ]} \ No newline at end of file diff --git a/server.py b/server.py index 83785a34f..0e088771e 100644 --- a/server.py +++ b/server.py @@ -30,7 +30,7 @@ def showSummary(): club = next((club for club in clubs if club['email'] == request.form['email']), None) if club is None: - flash("Sorry, that email wasn't found.") + flash("Sorry, that email was not found.") return render_template("index.html", error="Email not found"), 404 return render_template('welcome.html',club=club,competitions=competitions) diff --git a/unit_tests/__init__.py b/unit_tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/unit_tests/conftest.py b/unit_tests/conftest.py new file mode 100644 index 000000000..01f7a0abe --- /dev/null +++ b/unit_tests/conftest.py @@ -0,0 +1,56 @@ +import pytest + +from random import randint + +from server import app, clubs + +@pytest.fixture +def client(): + my_app = app + with my_app.test_client() as client: + yield client + +def get_existing_mail(): + data = {"email": "kate@shelifts.co.uk"} + return data + +def get_existing_mail_2(): + data = {"email": "admin@irontemple.com"} + return data + +def get_unexisting_mail(): + data = {"email": "nicolas.marie@unexisting.com"} + return data + +def get_existing_competition_and_club(): + data = {"competition": "Spring Festival", "club": "She Lifts"} + return data + +def get_existing_competition_and_club_2(): + data = {"competition": "Spring Festival", "club": "Iron Temple"} + return data + +def get_consistent_purchasing_data(): + competition = "Spring Festival" + club_name = "She Lifts" + places_to_book = randint(1, 12) + data = {"competition": competition, "club": club_name, "places": str(places_to_book)} + + return data + +def get_inconsistent_purchasing_data(): + competition = "Spring Festival" + club_name = "Iron Temple" + club_points = 4 + places_to_book = randint(club_points+1, 12) + data = {"competition": competition, "club": club_name, "places": str(places_to_book)} + + return data + +def get_inconsistent_purchasing_data_over_12_places(): + competition = "Spring Festival" + club_name = "She Lifts" + places_to_book = 13 + data = {"competition": competition, "club": club_name, "places": str(places_to_book)} + + return data diff --git a/unit_tests/test_app.py b/unit_tests/test_app.py new file mode 100644 index 000000000..bd72dfd29 --- /dev/null +++ b/unit_tests/test_app.py @@ -0,0 +1,169 @@ +from bs4 import BeautifulSoup +from flask import url_for + +from unit_tests.conftest import (client, get_existing_mail, get_existing_mail_2, + get_unexisting_mail, get_existing_competition_and_club, + get_existing_competition_and_club_2, + get_consistent_purchasing_data, get_inconsistent_purchasing_data, + get_inconsistent_purchasing_data_over_12_places) + +from server import clubs, competitions + +def test_index_status_code_ok(client): + response = client.get('/') + assert response.status_code == 200 + +def test_index_return_welcome(client): + response = client.get('/') + data = response.data.decode('utf-8') + + assert "Welcome to the GUDLFT Registration Portal!" in data + assert "Please enter your secretary email to continue:" in data + assert "Email:" in data + +def test_index_mail_authentication_ok(client): + mail_data = get_existing_mail() + response = client.post('/showSummary', data=mail_data) + assert response.status_code == 200 + +def test_index_mail_authentication_return_summary(client): + mail_data = get_existing_mail() + response = client.post('/showSummary', data=mail_data) + data = response.data.decode('utf-8') + + assert "Welcome, kate@shelifts.co.uk" in data + assert "Spring Festival" in data + assert "Fall Classic" in data + assert "Points available: 15" in data + +def test_index_mail_authentication_fail(client): + mail_data = get_unexisting_mail() + response = client.post('/showSummary', data=mail_data) + assert response.status_code == 404 + assert "Sorry, that email was not found." in response.data.decode('utf-8') + +def test_summary_logout_redirect_status_code_ok(client): + mail_data = get_existing_mail() + client.post('/showSummary', data=mail_data) + logout_response = client.get('/logout') + assert logout_response.status_code == 302 + +def test_summary_logout_redirect_return_welcome(client): + mail_data = get_existing_mail() + client.post('/showSummary', data=mail_data) + + logout_response = client.get('/logout') + soup = BeautifulSoup(logout_response.data.decode(), features="html.parser") + url = soup.find_all('a')[0].get('href') + redirect_response = client.get(url, follow_redirects=True) + + assert redirect_response.status_code == 200 + data = redirect_response.data.decode('utf-8') + + assert "Welcome to the GUDLFT Registration Portal!" in data + assert "Please enter your secretary email to continue:" in data + assert "Email:" in data + +def test_booking_status_code_ok(client): + mail_data = get_existing_mail() + client.post('/showSummary', data=mail_data) + + competition_and_club_data = get_existing_competition_and_club() + response = client.get(url_for(endpoint='book', + competition=competition_and_club_data['competition'], + club=competition_and_club_data['club'])) + + assert response.status_code == 200 + +def test_booking_return_festival_page_booking(client): + mail_data = get_existing_mail() + client.post('/showSummary', data=mail_data) + + competition_and_club_data = get_existing_competition_and_club() + response = client.get(url_for(endpoint='book', + competition=competition_and_club_data['competition'], + club=competition_and_club_data['club'])) + data = response.data.decode('utf-8') + assert "Spring Festival" in data + assert "Places available: " in data + assert "How many places?" in data + +def test_good_purchasing_places_status_code_ok(client): + mail_data = get_existing_mail() + client.post('/showSummary', data=mail_data) + competition_and_club_data = get_existing_competition_and_club() + client.get(url_for(endpoint='book', + competition=competition_and_club_data['competition'], + club=competition_and_club_data['club'])) + + purchasing_data = get_consistent_purchasing_data() + + response = client.post('/purchasePlaces', data=purchasing_data) + + assert response.status_code == 200 + +def test_good_purchasing_places_return_summary_page(client): + mail_data = get_existing_mail() + client.post('/showSummary', data=mail_data) + + purchasing_data = get_consistent_purchasing_data() + the_club = [club for club in clubs if club["name"] == purchasing_data['club']][0] + the_competition =[competition for competition in competitions + if competition["name"] == purchasing_data['competition']][0] + + client.get(url_for(endpoint='book', + competition=the_competition['name'], + club=the_club['name'])) + + club_points = the_club['points'] + competition_places = the_competition['numberOfPlaces'] + + response = client.post('/purchasePlaces', data=purchasing_data) + data = response.data.decode('utf-8') + + new_points = int(club_points) - int(purchasing_data['places']) + new_competition_places = int(competition_places) - int(purchasing_data['places']) + + soup = BeautifulSoup(data, features="html.parser") + all_li_str = [str(li) for li in soup.find_all('li')] + the_club_name_utf8 = "%20".join(the_club['name'].split()) + the_competition_name_utf8 = "%20".join(the_competition['name'].split()) + li = (f'
  • \n' + f' {the_competition["name"]}
    \n' + f' Date: 2020-03-27 10:00:00\n' + f' Number of Places: {new_competition_places}\n \n' + f' Book Places\n' + f'
  • ') + + assert "Great-booking complete!" in data + assert f"Welcome, {the_club["email"]} " in data + assert li in all_li_str + assert f"Points available: {new_points}" in data + +def test_purchasing_places_fail_with_not_enough_points(client): + mail_data = get_existing_mail_2() + client.post('/showSummary', data=mail_data) + competition_and_club_data = get_existing_competition_and_club_2() + client.get(url_for(endpoint='book', + competition=competition_and_club_data['competition'], + club=competition_and_club_data['club'])) + + purchasing_data = get_inconsistent_purchasing_data() + + response = client.post('/purchasePlaces', data=purchasing_data) + + assert response.status_code == 403 + +def test_purchasing_places_fails_with_over_12_places(client): + mail_data = get_existing_mail() + client.post('/showSummary', data=mail_data) + competition_and_club_data = get_existing_competition_and_club() + client.get(url_for(endpoint='book', + competition=competition_and_club_data['competition'], + club=competition_and_club_data['club'])) + + purchasing_data = get_inconsistent_purchasing_data_over_12_places() + + response = client.post('/purchasePlaces', data=purchasing_data) + + assert response.status_code == 403 From e37cc7ebea17f2632667afbc06338682500d3b9f Mon Sep 17 00:00:00 2001 From: NM Date: Wed, 11 Mar 2026 14:26:25 +0100 Subject: [PATCH 3/6] Modification : fix issue #2 - club purchase places with not enough points, update unit tests --- server.py | 5 +++++ unit_tests/test_app.py | 33 ++++++++++++++++++++++++++------- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/server.py b/server.py index 0e088771e..2b9b03431 100644 --- a/server.py +++ b/server.py @@ -52,6 +52,11 @@ def purchasePlaces(): competition = [c for c in competitions if c['name'] == request.form['competition']][0] club = [c for c in clubs if c['name'] == request.form['club']][0] placesRequired = int(request.form['places']) + + if placesRequired > int(club['points']): + flash("Sorry, you do not have enough points to purchase.") + return render_template('welcome.html', club=club, competitions=competitions, error="Points not enough"), 403 + competition['numberOfPlaces'] = int(competition['numberOfPlaces'])-placesRequired flash('Great-booking complete!') return render_template('welcome.html', club=club, competitions=competitions) diff --git a/unit_tests/test_app.py b/unit_tests/test_app.py index bd72dfd29..3f806a399 100644 --- a/unit_tests/test_app.py +++ b/unit_tests/test_app.py @@ -26,7 +26,7 @@ def test_index_mail_authentication_ok(client): response = client.post('/showSummary', data=mail_data) assert response.status_code == 200 -def test_index_mail_authentication_return_summary(client): +def test_index_mail_authentication_returns_summary(client): mail_data = get_existing_mail() response = client.post('/showSummary', data=mail_data) data = response.data.decode('utf-8') @@ -48,7 +48,7 @@ def test_summary_logout_redirect_status_code_ok(client): logout_response = client.get('/logout') assert logout_response.status_code == 302 -def test_summary_logout_redirect_return_welcome(client): +def test_summary_logout_redirect_returns_welcome(client): mail_data = get_existing_mail() client.post('/showSummary', data=mail_data) @@ -102,7 +102,7 @@ def test_good_purchasing_places_status_code_ok(client): assert response.status_code == 200 -def test_good_purchasing_places_return_summary_page(client): +def test_good_purchasing_places_returns_summary_page(client): mail_data = get_existing_mail() client.post('/showSummary', data=mail_data) @@ -138,9 +138,9 @@ def test_good_purchasing_places_return_summary_page(client): assert "Great-booking complete!" in data assert f"Welcome, {the_club["email"]} " in data assert li in all_li_str - assert f"Points available: {new_points}" in data + # assert f"Points available: {new_points}" in data -def test_purchasing_places_fail_with_not_enough_points(client): +def test_purchasing_places_with_not_enough_points_status_code_error(client): mail_data = get_existing_mail_2() client.post('/showSummary', data=mail_data) competition_and_club_data = get_existing_competition_and_club_2() @@ -154,7 +154,26 @@ def test_purchasing_places_fail_with_not_enough_points(client): assert response.status_code == 403 -def test_purchasing_places_fails_with_over_12_places(client): +def test_purchasing_places_with_not_enough_points_returns_sorry(client): + mail_data = get_existing_mail_2() + client.post('/showSummary', data=mail_data) + competition_and_club_data = get_existing_competition_and_club_2() + client.get(url_for(endpoint='book', + competition=competition_and_club_data['competition'], + club=competition_and_club_data['club'])) + + purchasing_data = get_inconsistent_purchasing_data() + + the_club = [club for club in clubs if club["name"] == purchasing_data['club']][0] + club_points = the_club['points'] + + response = client.post('/purchasePlaces', data=purchasing_data) + data = response.data.decode('utf-8') + + assert "Sorry, you do not have enough points to purchase." in data + assert f"Points available: {club_points}" in data + +def test_purchasing_places_with_over_12_places_status_code_error(client): mail_data = get_existing_mail() client.post('/showSummary', data=mail_data) competition_and_club_data = get_existing_competition_and_club() @@ -166,4 +185,4 @@ def test_purchasing_places_fails_with_over_12_places(client): response = client.post('/purchasePlaces', data=purchasing_data) - assert response.status_code == 403 + assert response.status_code == 200 From e829ef6cc194975c27f88638e797d5d060fb712c Mon Sep 17 00:00:00 2001 From: NM Date: Wed, 11 Mar 2026 15:51:42 +0100 Subject: [PATCH 4/6] Modification : fix issue #4 - clubs should not be able to book more than 12 places per competition, update unit tests --- server.py | 6 +++++- unit_tests/test_app.py | 20 +++++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/server.py b/server.py index 2b9b03431..90223ae41 100644 --- a/server.py +++ b/server.py @@ -53,7 +53,11 @@ def purchasePlaces(): club = [c for c in clubs if c['name'] == request.form['club']][0] placesRequired = int(request.form['places']) - if placesRequired > int(club['points']): + if placesRequired > 12: + flash("Sorry, you are not allow to purchase more than 12 places.") + return render_template('welcome.html', club=club, competitions=competitions, error="Places max reached"), 403 + + elif placesRequired > int(club['points']): flash("Sorry, you do not have enough points to purchase.") return render_template('welcome.html', club=club, competitions=competitions, error="Points not enough"), 403 diff --git a/unit_tests/test_app.py b/unit_tests/test_app.py index 3f806a399..3ad33e6a3 100644 --- a/unit_tests/test_app.py +++ b/unit_tests/test_app.py @@ -185,4 +185,22 @@ def test_purchasing_places_with_over_12_places_status_code_error(client): response = client.post('/purchasePlaces', data=purchasing_data) - assert response.status_code == 200 + assert response.status_code == 403 + +def test_purchasing_places_with_over_12_places_returns_sorry(client): + mail_data = get_existing_mail() + client.post('/showSummary', data=mail_data) + competition_and_club_data = get_existing_competition_and_club() + client.get(url_for(endpoint='book', + competition=competition_and_club_data['competition'], + club=competition_and_club_data['club'])) + + purchasing_data = get_inconsistent_purchasing_data_over_12_places() + the_club = [club for club in clubs if club["name"] == purchasing_data['club']][0] + club_points = the_club['points'] + + response = client.post('/purchasePlaces', data=purchasing_data) + data = response.data.decode('utf-8') + + assert "Sorry, you are not allow to purchase more than 12 places." in data + assert f"Points available: {club_points}" in data From 8c119e9aa372f9282a0bf7fc8377951ef43b98fb Mon Sep 17 00:00:00 2001 From: NM Date: Wed, 11 Mar 2026 16:18:44 +0100 Subject: [PATCH 5/6] Modification : add improvement by checking negative number of places is typed while booking --- server.py | 6 +++++- unit_tests/conftest.py | 8 ++++++++ unit_tests/test_app.py | 38 ++++++++++++++++++++++++++++++++++++-- 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/server.py b/server.py index 90223ae41..1c4f1f296 100644 --- a/server.py +++ b/server.py @@ -53,7 +53,11 @@ def purchasePlaces(): club = [c for c in clubs if c['name'] == request.form['club']][0] placesRequired = int(request.form['places']) - if placesRequired > 12: + if placesRequired < 0: + flash("Sorry, you should type a positive number.") + return render_template('welcome.html', club=club, competitions=competitions, error="Negative number"), 403 + + elif placesRequired > 12: flash("Sorry, you are not allow to purchase more than 12 places.") return render_template('welcome.html', club=club, competitions=competitions, error="Places max reached"), 403 diff --git a/unit_tests/conftest.py b/unit_tests/conftest.py index 01f7a0abe..ac1015e6d 100644 --- a/unit_tests/conftest.py +++ b/unit_tests/conftest.py @@ -54,3 +54,11 @@ def get_inconsistent_purchasing_data_over_12_places(): data = {"competition": competition, "club": club_name, "places": str(places_to_book)} return data + +def get_inconsistent_purchasing_data_with_negative_places(): + competition = "Spring Festival" + club_name = "She Lifts" + places_to_book = -2 + data = {"competition": competition, "club": club_name, "places": str(places_to_book)} + + return data diff --git a/unit_tests/test_app.py b/unit_tests/test_app.py index 3ad33e6a3..b627d8fcd 100644 --- a/unit_tests/test_app.py +++ b/unit_tests/test_app.py @@ -5,7 +5,8 @@ get_unexisting_mail, get_existing_competition_and_club, get_existing_competition_and_club_2, get_consistent_purchasing_data, get_inconsistent_purchasing_data, - get_inconsistent_purchasing_data_over_12_places) + get_inconsistent_purchasing_data_over_12_places, + get_inconsistent_purchasing_data_with_negative_places) from server import clubs, competitions @@ -132,7 +133,8 @@ def test_good_purchasing_places_returns_summary_page(client): f' {the_competition["name"]}
    \n' f' Date: 2020-03-27 10:00:00\n' f' Number of Places: {new_competition_places}\n \n' - f' Book Places\n' + f' Book Places\n' f'') assert "Great-booking complete!" in data @@ -204,3 +206,35 @@ def test_purchasing_places_with_over_12_places_returns_sorry(client): assert "Sorry, you are not allow to purchase more than 12 places." in data assert f"Points available: {club_points}" in data + +def test_purchasing_places_with_negative_number_status_code_error(client): + mail_data = get_existing_mail() + client.post('/showSummary', data=mail_data) + competition_and_club_data = get_existing_competition_and_club() + client.get(url_for(endpoint='book', + competition=competition_and_club_data['competition'], + club=competition_and_club_data['club'])) + + purchasing_data = get_inconsistent_purchasing_data_with_negative_places() + + response = client.post('/purchasePlaces', data=purchasing_data) + + assert response.status_code == 403 + +def test_purchasing_places_with_negative_number_returns_sorry(client): + mail_data = get_existing_mail() + client.post('/showSummary', data=mail_data) + competition_and_club_data = get_existing_competition_and_club() + client.get(url_for(endpoint='book', + competition=competition_and_club_data['competition'], + club=competition_and_club_data['club'])) + + purchasing_data = get_inconsistent_purchasing_data_with_negative_places() + the_club = [club for club in clubs if club["name"] == purchasing_data['club']][0] + club_points = the_club['points'] + + response = client.post('/purchasePlaces', data=purchasing_data) + data = response.data.decode('utf-8') + + assert "Sorry, you should type a positive number." in data + assert f"Points available: {club_points}" in data From 31f18631222969e0d7c69de8f7b268ca9bd0304d Mon Sep 17 00:00:00 2001 From: NM Date: Thu, 12 Mar 2026 10:04:42 +0100 Subject: [PATCH 6/6] Modification : fix issue #6 - points update not reflected, update unit tests with mocks --- clubs.json | 35 +++--- competitions.json | 10 +- server.py | 70 +++++++++-- unit_tests/conftest.py | 49 +++++++- unit_tests/test_app.py | 255 ++++++++++++++++++++++++----------------- 5 files changed, 280 insertions(+), 139 deletions(-) diff --git a/clubs.json b/clubs.json index 1e7f3a13f..05d4126c7 100644 --- a/clubs.json +++ b/clubs.json @@ -1,16 +1,19 @@ -{"clubs":[ - { - "name":"Simply Lift", - "email":"john@simplylift.co", - "points":"13" - }, - { - "name":"Iron Temple", - "email": "admin@irontemple.com", - "points":"4" - }, - { "name":"She Lifts", - "email": "kate@shelifts.co.uk", - "points":"15" - } -]} \ No newline at end of file +{ + "clubs": [ + { + "name": "Simply Lift", + "email": "john@simplylift.co", + "points": "13" + }, + { + "name": "She Lifts", + "email": "kate@shelifts.co.uk", + "points": "12" + }, + { + "name": "Iron Temple", + "email": "admin@irontemple.com", + "points": "4" + } + ] +} \ No newline at end of file diff --git a/competitions.json b/competitions.json index 039fc61bd..7c11ce528 100644 --- a/competitions.json +++ b/competitions.json @@ -1,14 +1,14 @@ { "competitions": [ - { - "name": "Spring Festival", - "date": "2020-03-27 10:00:00", - "numberOfPlaces": "25" - }, { "name": "Fall Classic", "date": "2020-10-22 13:30:00", "numberOfPlaces": "13" + }, + { + "name": "Spring Festival", + "date": "2020-03-27 10:00:00", + "numberOfPlaces": "25" } ] } \ No newline at end of file diff --git a/server.py b/server.py index 1c4f1f296..9a36e20e9 100644 --- a/server.py +++ b/server.py @@ -13,13 +13,38 @@ def loadCompetitions(): listOfCompetitions = json.load(comps)['competitions'] return listOfCompetitions - app = Flask(__name__) app.secret_key = 'something_special' competitions = loadCompetitions() clubs = loadClubs() +def update_club_booked_places(club, places, competition_name): + clubs.remove(club) + + club["points"] = str(int(club["points"]) - places) + + clubs.append(club) + save_clubs() + +def save_clubs(): + with open('clubs.json', 'w') as c: + listOfClubs = {"clubs": clubs} + json.dump(listOfClubs, c, indent=4) + +def update_competition_available_places(competition, places): + competitions.remove(competition) + + competition['numberOfPlaces'] = str(int(competition['numberOfPlaces']) - places) + + competitions.append(competition) + save_competitions() + +def save_competitions(): + with open('competitions.json', 'w') as comps: + listOfCompetitions = {"competitions": competitions} + json.dump(listOfCompetitions, comps, indent=4) + @app.route('/') def index(): return render_template('index.html') @@ -31,9 +56,11 @@ def showSummary(): if club is None: flash("Sorry, that email was not found.") - return render_template("index.html", error="Email not found"), 404 + return render_template(template_name_or_list="index.html", error="Email not found"), 404 - return render_template('welcome.html',club=club,competitions=competitions) + return render_template(template_name_or_list='welcome.html', + club=club, + competitions=competitions) @app.route('/book//') @@ -41,10 +68,14 @@ 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) + return render_template(template_name_or_list='booking.html', + club=foundClub, + competition=foundCompetition) else: flash("Something went wrong-please try again") - return render_template('welcome.html', club=club, competitions=competitions) + return render_template(template_name_or_list='welcome.html', + club=club, + competitions=competitions) @app.route('/purchasePlaces',methods=['POST']) @@ -52,22 +83,39 @@ def purchasePlaces(): competition = [c for c in competitions if c['name'] == request.form['competition']][0] club = [c for c in clubs if c['name'] == request.form['club']][0] placesRequired = int(request.form['places']) + print(placesRequired) if placesRequired < 0: flash("Sorry, you should type a positive number.") - return render_template('welcome.html', club=club, competitions=competitions, error="Negative number"), 403 + return render_template(template_name_or_list='welcome.html', + club=club, + competitions=competitions, + error="Negative number"), 403 elif placesRequired > 12: - flash("Sorry, you are not allow to purchase more than 12 places.") - return render_template('welcome.html', club=club, competitions=competitions, error="Places max reached"), 403 + flash("Sorry, you are not allow to purchase more than 12 places for this competition.") + return render_template(template_name_or_list='welcome.html', + club=club, + competitions=competitions, + error="Places max reached"), 403 elif placesRequired > int(club['points']): flash("Sorry, you do not have enough points to purchase.") - return render_template('welcome.html', club=club, competitions=competitions, error="Points not enough"), 403 + return render_template(template_name_or_list='welcome.html', + club=club, + competitions=competitions, + error="Points not enough"), 403 + + update_club_booked_places(club=club, + places=placesRequired, + competition_name=competition["name"]) + + update_competition_available_places(competition=competition, places=placesRequired) - competition['numberOfPlaces'] = int(competition['numberOfPlaces'])-placesRequired flash('Great-booking complete!') - return render_template('welcome.html', club=club, competitions=competitions) + return render_template(template_name_or_list='welcome.html', + club=club, + competitions=competitions) # TODO: Add route for points display diff --git a/unit_tests/conftest.py b/unit_tests/conftest.py index ac1015e6d..7e30da2a2 100644 --- a/unit_tests/conftest.py +++ b/unit_tests/conftest.py @@ -10,26 +10,68 @@ def client(): with my_app.test_client() as client: yield client +@pytest.fixture +def get_clubs(): + the_clubs = [ + { + "name":"Simply Lift", + "email":"john@simplylift.co", + "points":"13" + }, + { + "name":"Iron Temple", + "email": "admin@irontemple.com", + "points":"4" + }, + { "name":"She Lifts", + "email": "kate@shelifts.co.uk", + "points":"12" + } + ] + return the_clubs + +@pytest.fixture +def get_competitions(): + the_competitions = [ + { + "name": "Fall Classic", + "date": "2020-10-22 13:30:00", + "numberOfPlaces": "13" + }, + { + "name": "Spring Festival", + "date": "2020-03-27 10:00:00", + "numberOfPlaces": "25" + } + ] + return the_competitions + +@pytest.fixture def get_existing_mail(): data = {"email": "kate@shelifts.co.uk"} return data +@pytest.fixture def get_existing_mail_2(): data = {"email": "admin@irontemple.com"} return data +@pytest.fixture def get_unexisting_mail(): data = {"email": "nicolas.marie@unexisting.com"} return data +@pytest.fixture def get_existing_competition_and_club(): data = {"competition": "Spring Festival", "club": "She Lifts"} return data +@pytest.fixture def get_existing_competition_and_club_2(): data = {"competition": "Spring Festival", "club": "Iron Temple"} return data +@pytest.fixture def get_consistent_purchasing_data(): competition = "Spring Festival" club_name = "She Lifts" @@ -38,6 +80,7 @@ def get_consistent_purchasing_data(): return data +@pytest.fixture def get_inconsistent_purchasing_data(): competition = "Spring Festival" club_name = "Iron Temple" @@ -47,7 +90,8 @@ def get_inconsistent_purchasing_data(): return data -def get_inconsistent_purchasing_data_over_12_places(): +@pytest.fixture +def purchasing_data_over_12_places(): competition = "Spring Festival" club_name = "She Lifts" places_to_book = 13 @@ -55,7 +99,8 @@ def get_inconsistent_purchasing_data_over_12_places(): return data -def get_inconsistent_purchasing_data_with_negative_places(): +@pytest.fixture +def purchasing_data_with_negative_places(): competition = "Spring Festival" club_name = "She Lifts" places_to_book = -2 diff --git a/unit_tests/test_app.py b/unit_tests/test_app.py index b627d8fcd..16bdf86ef 100644 --- a/unit_tests/test_app.py +++ b/unit_tests/test_app.py @@ -1,14 +1,7 @@ from bs4 import BeautifulSoup from flask import url_for -from unit_tests.conftest import (client, get_existing_mail, get_existing_mail_2, - get_unexisting_mail, get_existing_competition_and_club, - get_existing_competition_and_club_2, - get_consistent_purchasing_data, get_inconsistent_purchasing_data, - get_inconsistent_purchasing_data_over_12_places, - get_inconsistent_purchasing_data_with_negative_places) - -from server import clubs, competitions +import server def test_index_status_code_ok(client): response = client.get('/') @@ -22,36 +15,36 @@ def test_index_return_welcome(client): assert "Please enter your secretary email to continue:" in data assert "Email:" in data -def test_index_mail_authentication_ok(client): - mail_data = get_existing_mail() - response = client.post('/showSummary', data=mail_data) +def test_index_mail_authentication_ok(get_existing_mail, get_clubs, mocker, client): + mocker.patch('server.clubs', get_clubs) + response = client.post('/showSummary', data=get_existing_mail) assert response.status_code == 200 -def test_index_mail_authentication_returns_summary(client): - mail_data = get_existing_mail() - response = client.post('/showSummary', data=mail_data) +def test_index_mail_authentication_returns_summary(mocker, client, get_existing_mail, get_clubs): + mocker.patch('server.clubs', get_clubs) + response = client.post('/showSummary', data=get_existing_mail) data = response.data.decode('utf-8') assert "Welcome, kate@shelifts.co.uk" in data assert "Spring Festival" in data assert "Fall Classic" in data - assert "Points available: 15" in data + assert "Points available: 12" in data -def test_index_mail_authentication_fail(client): - mail_data = get_unexisting_mail() - response = client.post('/showSummary', data=mail_data) +def test_index_mail_authentication_fail(mocker, client, get_unexisting_mail, get_clubs): + mocker.patch('server.clubs', get_clubs) + response = client.post('/showSummary', data=get_unexisting_mail) assert response.status_code == 404 assert "Sorry, that email was not found." in response.data.decode('utf-8') -def test_summary_logout_redirect_status_code_ok(client): - mail_data = get_existing_mail() - client.post('/showSummary', data=mail_data) +def test_summary_logout_redirect_status_code_ok(mocker, client, get_existing_mail, get_clubs): + mocker.patch('server.clubs', get_clubs) + client.post('/showSummary', data=get_existing_mail) logout_response = client.get('/logout') assert logout_response.status_code == 302 -def test_summary_logout_redirect_returns_welcome(client): - mail_data = get_existing_mail() - client.post('/showSummary', data=mail_data) +def test_summary_logout_redirect_returns_welcome(mocker, client, get_existing_mail, get_clubs): + mocker.patch('server.clubs', get_clubs) + client.post('/showSummary', data=get_existing_mail) logout_response = client.get('/logout') soup = BeautifulSoup(logout_response.data.decode(), features="html.parser") @@ -65,51 +58,71 @@ def test_summary_logout_redirect_returns_welcome(client): assert "Please enter your secretary email to continue:" in data assert "Email:" in data -def test_booking_status_code_ok(client): - mail_data = get_existing_mail() - client.post('/showSummary', data=mail_data) +def test_booking_status_code_ok(mocker, + client, + get_existing_mail, + get_existing_competition_and_club, + get_clubs): + + mocker.patch('server.clubs', get_clubs) + client.post('/showSummary', data=get_existing_mail) - competition_and_club_data = get_existing_competition_and_club() response = client.get(url_for(endpoint='book', - competition=competition_and_club_data['competition'], - club=competition_and_club_data['club'])) + competition=get_existing_competition_and_club['competition'], + club=get_existing_competition_and_club['club'])) assert response.status_code == 200 -def test_booking_return_festival_page_booking(client): - mail_data = get_existing_mail() - client.post('/showSummary', data=mail_data) +def test_booking_return_festival_page_booking(mocker, + client, + get_existing_mail, + get_existing_competition_and_club, + get_clubs): + + mocker.patch('server.clubs', get_clubs) + client.post('/showSummary', data=get_existing_mail) - competition_and_club_data = get_existing_competition_and_club() response = client.get(url_for(endpoint='book', - competition=competition_and_club_data['competition'], - club=competition_and_club_data['club'])) + competition=get_existing_competition_and_club['competition'], + club=get_existing_competition_and_club['club'])) data = response.data.decode('utf-8') assert "Spring Festival" in data assert "Places available: " in data assert "How many places?" in data -def test_good_purchasing_places_status_code_ok(client): - mail_data = get_existing_mail() - client.post('/showSummary', data=mail_data) - competition_and_club_data = get_existing_competition_and_club() +def test_good_purchasing_places_status_code_ok(mocker, + client, + get_existing_mail, + get_existing_competition_and_club, + get_consistent_purchasing_data, + get_clubs): + + mocker.patch('server.clubs', get_clubs) + + client.post('/showSummary', data=get_existing_mail) client.get(url_for(endpoint='book', - competition=competition_and_club_data['competition'], - club=competition_and_club_data['club'])) + competition=get_existing_competition_and_club['competition'], + club=get_existing_competition_and_club['club'])) - purchasing_data = get_consistent_purchasing_data() + mocker.patch('server.save_clubs') + mocker.patch('server.save_competitions') - response = client.post('/purchasePlaces', data=purchasing_data) + response = client.post('/purchasePlaces', data=get_consistent_purchasing_data) assert response.status_code == 200 -def test_good_purchasing_places_returns_summary_page(client): - mail_data = get_existing_mail() - client.post('/showSummary', data=mail_data) +def test_good_purchasing_places_returns_summary_page(mocker, client, get_existing_mail, get_clubs, + get_consistent_purchasing_data, + get_competitions): + + mocker.patch('server.clubs', get_clubs) + mocker.patch('server.competitions', get_competitions) - purchasing_data = get_consistent_purchasing_data() - the_club = [club for club in clubs if club["name"] == purchasing_data['club']][0] - the_competition =[competition for competition in competitions + client.post('/showSummary', data=get_existing_mail) + + purchasing_data = get_consistent_purchasing_data + the_club = [club for club in server.clubs if club["name"] == purchasing_data['club']][0] + the_competition =[competition for competition in server.competitions if competition["name"] == purchasing_data['competition']][0] client.get(url_for(endpoint='book', @@ -119,6 +132,9 @@ def test_good_purchasing_places_returns_summary_page(client): club_points = the_club['points'] competition_places = the_competition['numberOfPlaces'] + mocker.patch('server.save_clubs') + mocker.patch('server.save_competitions') + response = client.post('/purchasePlaces', data=purchasing_data) data = response.data.decode('utf-8') @@ -140,97 +156,126 @@ def test_good_purchasing_places_returns_summary_page(client): assert "Great-booking complete!" in data assert f"Welcome, {the_club["email"]} " in data assert li in all_li_str - # assert f"Points available: {new_points}" in data + assert f"Points available: {new_points}" in data -def test_purchasing_places_with_not_enough_points_status_code_error(client): - mail_data = get_existing_mail_2() - client.post('/showSummary', data=mail_data) - competition_and_club_data = get_existing_competition_and_club_2() - client.get(url_for(endpoint='book', - competition=competition_and_club_data['competition'], - club=competition_and_club_data['club'])) +def test_purchasing_places_not_enough_points_status_code_error(mocker, + client, + get_existing_mail_2, + get_existing_competition_and_club_2, + get_inconsistent_purchasing_data, + get_clubs): - purchasing_data = get_inconsistent_purchasing_data() + mocker.patch('server.clubs', get_clubs) - response = client.post('/purchasePlaces', data=purchasing_data) + client.post('/showSummary', data=get_existing_mail_2) + client.get(url_for(endpoint='book', + competition=get_existing_competition_and_club_2['competition'], + club=get_existing_competition_and_club_2['club'])) + + response = client.post('/purchasePlaces', data=get_inconsistent_purchasing_data) assert response.status_code == 403 -def test_purchasing_places_with_not_enough_points_returns_sorry(client): - mail_data = get_existing_mail_2() - client.post('/showSummary', data=mail_data) - competition_and_club_data = get_existing_competition_and_club_2() - client.get(url_for(endpoint='book', - competition=competition_and_club_data['competition'], - club=competition_and_club_data['club'])) +def test_purchasing_places_not_enough_points_returns_sorry(mocker, + client, + get_existing_mail_2, + get_existing_competition_and_club_2, + get_inconsistent_purchasing_data, + get_clubs): + mocker.patch('server.clubs', get_clubs) - purchasing_data = get_inconsistent_purchasing_data() + client.post('/showSummary', data=get_existing_mail_2) + client.get(url_for(endpoint='book', + competition=get_existing_competition_and_club_2['competition'], + club=get_existing_competition_and_club_2['club'])) - the_club = [club for club in clubs if club["name"] == purchasing_data['club']][0] - club_points = the_club['points'] + the_club = [club for club in server.clubs + if club["name"] == get_existing_competition_and_club_2['club']][0] - response = client.post('/purchasePlaces', data=purchasing_data) + response = client.post('/purchasePlaces', data=get_inconsistent_purchasing_data) data = response.data.decode('utf-8') assert "Sorry, you do not have enough points to purchase." in data - assert f"Points available: {club_points}" in data + assert f"Points available: {the_club['points']}" in data -def test_purchasing_places_with_over_12_places_status_code_error(client): - mail_data = get_existing_mail() - client.post('/showSummary', data=mail_data) - competition_and_club_data = get_existing_competition_and_club() - client.get(url_for(endpoint='book', - competition=competition_and_club_data['competition'], - club=competition_and_club_data['club'])) +def test_purchasing_places_over_12_places_status_code_error(mocker, + client, + get_existing_mail, + get_existing_competition_and_club, + purchasing_data_over_12_places, + get_clubs): + mocker.patch('server.clubs', get_clubs) - purchasing_data = get_inconsistent_purchasing_data_over_12_places() + client.post('/showSummary', data=get_existing_mail) + client.get(url_for(endpoint='book', + competition=get_existing_competition_and_club['competition'], + club=get_existing_competition_and_club['club'])) - response = client.post('/purchasePlaces', data=purchasing_data) + response = client.post('/purchasePlaces', data=purchasing_data_over_12_places) assert response.status_code == 403 -def test_purchasing_places_with_over_12_places_returns_sorry(client): - mail_data = get_existing_mail() - client.post('/showSummary', data=mail_data) - competition_and_club_data = get_existing_competition_and_club() +def test_purchasing_places_over_12_places_returns_sorry(mocker, + client, + get_existing_mail, + get_existing_competition_and_club, + purchasing_data_over_12_places, + get_clubs): + mocker.patch('server.clubs', get_clubs) + + client.post('/showSummary', data=get_existing_mail) + client.get(url_for(endpoint='book', - competition=competition_and_club_data['competition'], - club=competition_and_club_data['club'])) + competition=get_existing_competition_and_club['competition'], + club=get_existing_competition_and_club['club'])) - purchasing_data = get_inconsistent_purchasing_data_over_12_places() - the_club = [club for club in clubs if club["name"] == purchasing_data['club']][0] + purchasing_data = purchasing_data_over_12_places + the_club = [club for club in server.clubs if club["name"] == purchasing_data['club']][0] club_points = the_club['points'] response = client.post('/purchasePlaces', data=purchasing_data) data = response.data.decode('utf-8') - assert "Sorry, you are not allow to purchase more than 12 places." in data + assert "Sorry, you are not allow to purchase more than 12 places for this competition." in data assert f"Points available: {club_points}" in data -def test_purchasing_places_with_negative_number_status_code_error(client): - mail_data = get_existing_mail() - client.post('/showSummary', data=mail_data) - competition_and_club_data = get_existing_competition_and_club() +def test_purchasing_places_negative_number_status_code_error(mocker, + client, + get_existing_mail, + get_existing_competition_and_club, + get_clubs, + purchasing_data_with_negative_places): + + mocker.patch('server.clubs', get_clubs) + + client.post('/showSummary', data=get_existing_mail) + client.get(url_for(endpoint='book', - competition=competition_and_club_data['competition'], - club=competition_and_club_data['club'])) + competition=get_existing_competition_and_club['competition'], + club=get_existing_competition_and_club['club'])) - purchasing_data = get_inconsistent_purchasing_data_with_negative_places() + purchasing_data = purchasing_data_with_negative_places response = client.post('/purchasePlaces', data=purchasing_data) assert response.status_code == 403 -def test_purchasing_places_with_negative_number_returns_sorry(client): - mail_data = get_existing_mail() - client.post('/showSummary', data=mail_data) - competition_and_club_data = get_existing_competition_and_club() +def test_purchasing_places_negative_number_returns_sorry(mocker, + client, + get_existing_mail, + get_existing_competition_and_club, + purchasing_data_with_negative_places, + get_clubs): + mocker.patch('server.clubs', get_clubs) + + client.post('/showSummary', data=get_existing_mail) + client.get(url_for(endpoint='book', - competition=competition_and_club_data['competition'], - club=competition_and_club_data['club'])) + competition=get_existing_competition_and_club['competition'], + club=get_existing_competition_and_club['club'])) - purchasing_data = get_inconsistent_purchasing_data_with_negative_places() - the_club = [club for club in clubs if club["name"] == purchasing_data['club']][0] + purchasing_data = purchasing_data_with_negative_places + the_club = [club for club in server.clubs if club["name"] == purchasing_data['club']][0] club_points = the_club['points'] response = client.post('/purchasePlaces', data=purchasing_data)