Skip to content

Commit 4e1e43e

Browse files
committed
(fix) Handle redeeming more points than available
1 parent 9304c72 commit 4e1e43e

File tree

8 files changed

+227
-42
lines changed

8 files changed

+227
-42
lines changed

.coverage

0 Bytes
Binary file not shown.

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ MarkupSafe==1.1.1
66
Werkzeug==1.0.1
77
pytest
88
black
9-
flake8
9+
flake8

server.py

Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -62,21 +62,66 @@ def book(competition, club):
6262
)
6363

6464

65+
def get_competition_from_name(name):
66+
try:
67+
competition = [
68+
competition for competition in
69+
competitions if competition["name"] == name
70+
][0]
71+
return competition
72+
except IndexError:
73+
return None
74+
75+
76+
def get_club_from_name(name):
77+
try:
78+
club = [
79+
club for club in clubs if club["name"] == name
80+
][0]
81+
return club
82+
except IndexError:
83+
return None
84+
85+
86+
def check_places(places, club):
87+
if not places or int(places) < 1:
88+
return "Places required must be a positive integer"
89+
if int(places) > int(club["points"]):
90+
return "Places required exceed club's total points"
91+
92+
93+
def take_places(places, club, competition):
94+
try:
95+
competition["numberOfPlaces"] = \
96+
int(competition["numberOfPlaces"]) - places
97+
club["points"] = int(club["points"]) - places
98+
return True
99+
except Exception:
100+
return False
101+
102+
65103
@app.route("/purchasePlaces", methods=["POST"])
66104
def purchasePlaces():
67-
competition = [
68-
c for c in competitions if c["name"] == request.form["competition"]
69-
][0]
70-
club = [c for c in clubs if c["name"] == request.form["club"]][0]
105+
competition = get_competition_from_name(request.form["competition"])
106+
club = get_club_from_name(request.form["club"])
107+
108+
error_message = check_places(request.form["places"], club)
109+
if error_message:
110+
flash(error_message)
111+
return redirect(
112+
url_for("book", competition=competition["name"], club=club["name"])
113+
)
71114
placesRequired = int(request.form["places"])
72-
competition["numberOfPlaces"] = (
73-
int(competition["numberOfPlaces"]) - placesRequired
74-
)
75-
flash("Great-booking complete!")
76-
return render_template("welcome.html", club=club, competitions=competitions)
77115

78-
79-
# TODO: Add route for points display
116+
if take_places(placesRequired, club, competition):
117+
flash("Great-booking complete!")
118+
return render_template("welcome.html", club=club,
119+
competitions=competitions)
120+
else:
121+
flash("Something went wrong-please try again")
122+
return redirect(
123+
url_for("book", competition=competition["name"], club=club["name"])
124+
)
80125

81126

82127
@app.route("/logout")

templates/booking.html

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
<!DOCTYPE html>
12
<html lang="en">
23
<head>
34
<meta charset="UTF-8">
@@ -6,11 +7,20 @@
67
</head>
78
<body>
89
<h2>{{competition['name']}}</h2>
10+
{% with messages = get_flashed_messages()%}
11+
{% if messages %}
12+
<ul>
13+
{% for message in messages %}
14+
<li>{{message}}</li>
15+
{% endfor %}
16+
</ul>
17+
{% endif%}
18+
{% endwith %}
919
Places available: {{competition['numberOfPlaces']}}
1020
<form action="/purchasePlaces" method="post">
1121
<input type="hidden" name="club" value="{{club['name']}}">
1222
<input type="hidden" name="competition" value="{{competition['name']}}">
13-
<label for="places">How many places?</label><input type="number" name="places" id=""/>
23+
<label for="places">How many places?</label><input type="number" name="places" id="places"/>
1424
<button type="submit">Book</button>
1525
</form>
1626
</body>

tests/integration_tests/conftest.py

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,21 @@
33
from unittest.mock import mock_open, patch
44

55
# Mock data for clubs.json and competitions.json
6-
mock_clubs_json = json.dumps(
7-
{
8-
"clubs": [
9-
{"name": "Club 1", "email": "club1@example.com"},
10-
{"name": "Club 2", "email": "club2@example.com"},
11-
]
12-
}
13-
)
14-
15-
mock_competitions_json = json.dumps(
16-
{
17-
"competitions": [
18-
{"name": "Competition 1", "numberOfPlaces": "25"},
19-
{"name": "Competition 2", "numberOfPlaces": "15"},
20-
]
21-
}
22-
)
6+
mock_clubs_json = json.dumps({
7+
"clubs": [
8+
{"name": "Club 1", "email": "club1@example.com", "points": "10"},
9+
{"name": "Club 2", "email": "club2@example.com", "points": "15"}
10+
]
11+
})
12+
13+
mock_competitions_json = json.dumps({
14+
"competitions": [
15+
{"name": "Competition 1", "date": "2023-03-27 10:00:00",
16+
"numberOfPlaces": "25"},
17+
{"name": "Competition 2", "date": "2023-10-22 13:30:00",
18+
"numberOfPlaces": "15"}
19+
]
20+
})
2321

2422

2523
def mocked_open(file, *args, **kwargs):
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
2+
3+
def test_purchase_places_valid(client):
4+
"""
5+
Test purchasing places with valid club points and valid competition.
6+
"""
7+
response = client.post("/purchasePlaces", data={
8+
"competition": "Competition 1",
9+
"club": "Club 1",
10+
"places": "5"
11+
})
12+
13+
assert response.status_code == 200
14+
assert b"Great-booking complete!" in response.data
15+
16+
17+
def test_purchase_places_insufficient_points(client):
18+
"""
19+
Test purchasing more places than the club's available points.
20+
"""
21+
response = client.post("/purchasePlaces", data={
22+
"competition": "Competition 1",
23+
"club": "Club 1",
24+
"places": "20" # Club 1 has only 10 points
25+
})
26+
27+
assert response.status_code == 302 # Redirect expected
28+
response = client.get(response.headers["Location"]) # Follow the redirect
29+
assert b"Places required exceed club&#39;s total points" in response.data
30+
31+
32+
def test_purchase_places_zero_places(client):
33+
"""
34+
Test purchasing zero places, which is an invalid input.
35+
"""
36+
response = client.post("/purchasePlaces", data={
37+
"competition": "Competition 1",
38+
"club": "Club 1",
39+
"places": "0"
40+
})
41+
42+
assert response.status_code == 302 # Redirect expected
43+
response = client.get(response.headers["Location"]) # Follow the redirect
44+
assert b"Places required must be a positive integer" in response.data
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
from unittest.mock import patch
2+
from .utils import mock_clubs_list, mock_competitions_list
3+
4+
5+
# Unit Tests for get_competition_from_name
6+
@patch("server.competitions", mock_competitions_list)
7+
def test_get_competition_from_name_valid():
8+
from server import get_competition_from_name
9+
10+
competition = get_competition_from_name("Competition 1")
11+
assert competition is not None
12+
assert competition["name"] == "Competition 1"
13+
14+
15+
@patch("server.competitions", mock_competitions_list)
16+
def test_get_competition_from_name_invalid():
17+
from server import get_competition_from_name
18+
19+
competition = get_competition_from_name("Invalid Competition")
20+
assert competition is None
21+
22+
23+
# Unit Tests for get_club_from_name ###
24+
@patch("server.clubs", mock_clubs_list)
25+
def test_get_club_from_name_valid():
26+
from server import get_club_from_name
27+
28+
club = get_club_from_name("Club 1")
29+
assert club is not None
30+
assert club["name"] == "Club 1"
31+
32+
33+
@patch("server.clubs", mock_clubs_list)
34+
def test_get_club_from_name_invalid():
35+
from server import get_club_from_name
36+
37+
club = get_club_from_name("Invalid Club")
38+
assert club is None
39+
40+
41+
# Unit Tests for check_places ###
42+
def test_check_places_valid():
43+
from server import check_places
44+
45+
error = check_places("5", mock_clubs_list[0])
46+
assert error is None
47+
48+
49+
def test_check_places_invalid_zero():
50+
from server import check_places
51+
52+
error = check_places("0", mock_clubs_list[0])
53+
assert error == "Places required must be a positive integer"
54+
55+
56+
def test_check_places_invalid_negative():
57+
from server import check_places
58+
59+
error = check_places("-3", mock_clubs_list[0])
60+
assert error == "Places required must be a positive integer"
61+
62+
63+
def test_check_places_exceeds_points():
64+
from server import check_places
65+
66+
error = check_places("20", mock_clubs_list[0])
67+
assert error == "Places required exceed club's total points"
68+
69+
70+
# Unit Tests for take_places ###
71+
def test_take_places_valid():
72+
from server import take_places
73+
74+
result = take_places(5, mock_clubs_list[0], mock_competitions_list[0])
75+
assert result is True
76+
assert mock_clubs_list[0]["points"] == 5 # 10 - 5
77+
assert mock_competitions_list[0]["numberOfPlaces"] == 20 # 25 - 5
78+
79+
80+
def test_take_places_invalid():
81+
from server import take_places
82+
83+
result = take_places(
84+
"invalid", mock_clubs_list[0], mock_competitions_list[0]
85+
)
86+
assert result is False

tests/unit_tests/utils.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,25 @@
1-
21
import json
32

43

54
# Mock data for clubs and competitions
65
mock_clubs_list = [
7-
{"name": "Club 1", "email": "club1@example.com"},
8-
{"name": "Club 2", "email": "club2@example.com"}
6+
{"name": "Club 1", "email": "club1@example.com", "points": "10"},
7+
{"name": "Club 2", "email": "club2@example.com", "points": "15"},
98
]
109

1110
mock_competitions_list = [
12-
{"name": "Competition 1", "numberOfPlaces": "25"},
13-
{"name": "Competition 2", "numberOfPlaces": "15"}
11+
{
12+
"name": "Competition 1",
13+
"date": "2023-03-27 10:00:00",
14+
"numberOfPlaces": "25",
15+
},
16+
{
17+
"name": "Competition 2",
18+
"date": "2023-10-22 13:30:00",
19+
"numberOfPlaces": "15",
20+
},
1421
]
15-
1622
# Mock data for clubs.json and competitions.json
17-
mock_clubs_json = json.dumps({
18-
"clubs": mock_clubs_list
19-
})
23+
mock_clubs_json = json.dumps({"clubs": mock_clubs_list})
2024

21-
mock_competitions_json = json.dumps({
22-
"competitions": mock_competitions_list
23-
})
25+
mock_competitions_json = json.dumps({"competitions": mock_competitions_list})

0 commit comments

Comments
 (0)