diff --git a/README.md b/README.md index 75cf165..23d5ebc 100644 --- a/README.md +++ b/README.md @@ -11,3 +11,21 @@ Giga - the gig booking site that'll be _huge_. * Create the databases specified in `lib/database_connection.py` - `giga` and `giga_test` * Seed the production database e.g. `psql -h 127.0.0.1 giga -f seeds/test_gigs.sql -f seeds/test_users.sql -f seeds/test_bookings.sql` * Run the server with `python app.py` + +## Adding Users + +Users can be added to the "users" table with values of a username (string) and a +password generated via a hash. + +The following example shows steps that can be used to generate a hashed +password, starting with your virtual environment activated: + +```python +(giga_venv) % python +>>> from werkzeug.security import generate_password_hash +>>> generate_password_hash("mypassword123") +'scrypt:32768:8:1$IuT0ev03aikfxfuB$724fc6b7123b7eb74b7bc9084b8a0cdd2be087cac401bf93e057191babcd8c5f9b3a93d3e27188bd756b40ac333b026297878d9cc35928099dd454f53f015370' +``` + +The `scrypt` string can be inserted into the table - see the existing seed file +for reference. diff --git a/app.py b/app.py index eea6321..467c28f 100644 --- a/app.py +++ b/app.py @@ -2,6 +2,7 @@ from flask import Flask, request, render_template, redirect, url_for from lib.database_connection import get_flask_database_connection from lib.gig_repository import GigRepository +from lib.gig import Gig from lib.booking_repository import BookingRepository from flask_login import ( LoginManager, @@ -13,6 +14,7 @@ ) from werkzeug.security import generate_password_hash, check_password_hash from functools import wraps +import json app = Flask(__name__) app.config.update( @@ -34,6 +36,9 @@ def password_valid(self, connection, username, password_attempt): def get_user_database_id(self, connection, username): rows = connection.execute('SELECT * FROM users WHERE username = %s', [username]) return rows[0]["id"] + def get_username(self, connection, id): + rows = connection.execute('SELECT * FROM users WHERE id = %s', [id]) + return rows[0]["username"] @login_manager.user_loader def user_loader(username: str): @@ -44,15 +49,34 @@ def user_loader(username: str): return user_model return None +def admin_user_required(func): + @wraps(func) + def decorated_view(*args, **kwargs): + if current_user.__dict__.get("id") and current_user.id == "admin": + return func(*args, **kwargs) + else: + return redirect(url_for('get_home')) + return decorated_view + +@app.errorhandler(401) +def unauthorised(e): + return render_template('401.html'), 401 + +@app.errorhandler(404) +def page_not_found(e): + return render_template('404.html'), 404 + @app.route('/home', methods=['GET']) def get_home(): - return render_template('home.html') + logged_in_as = ", " + str(current_user.id) if current_user.__dict__.get("id") else None + return render_template('home.html', logged_in_as=logged_in_as) @app.route('/about', methods=['GET']) def get_about(): - return render_template('about.html') + logged_in_as = ", " + str(current_user.id) if current_user.__dict__.get("id") else None + return render_template('about.html', logged_in_as=logged_in_as) @app.route('/gigs', methods=['GET', 'POST']) def get_gigs(): @@ -65,14 +89,15 @@ def get_gigs(): selected_location = "All" if "location" in request.form.keys(): selected_location = request.form["location"] - date_from = "1900-01-01" - if "date_from" in request.form.keys(): + date_from = "2000-01-01" + if "date_from" in request.form.keys() and request.form["date_from"]: date_from = request.form["date_from"] - date_to = "3000-01-01" - if "date_to" in request.form.keys(): + date_to = "2100-01-01" + if "date_to" in request.form.keys() and request.form["date_to"]: date_to = request.form["date_to"] gigs = repo.get_by_location_and_dates(selected_location, date_from, date_to) - return render_template('gigs.html', gigs=gigs, locations=locations, selected_location=selected_location, date_from=date_from, date_to=date_to) + logged_in_as = ", " + str(current_user.id) if current_user.__dict__.get("id") else None + return render_template('gigs.html', gigs=gigs, locations=locations, selected_location=selected_location, date_from=date_from, date_to=date_to, logged_in_as=logged_in_as) @app.route('/gigs/', methods=['GET']) def get_gig_by_id(id): @@ -82,12 +107,22 @@ def get_gig_by_id(id): logged_in_as = str(current_user.id) if current_user.__dict__.get("id") else None repo = BookingRepository(connection) if current_user.__dict__ != {}: - already_booked_gig = gig.id in [booking.gig_id for booking in repo.get_bookings(1)] + user_database_id = User().get_user_database_id(connection, current_user.id) + already_booked_gig = gig.id in [booking.gig_id for booking in repo.get_bookings(user_database_id)] else: already_booked_gig = False return render_template('gig.html', gig=gig, logged_in_as=logged_in_as, already_booked_gig=already_booked_gig) -@app.route("/book_gig/", methods=["POST"]) +@app.route('/bands/', methods=["GET"]) +def get_band_by_name(band_name): + connection = get_flask_database_connection(app) + repo = GigRepository(connection) + gigs = repo.get_by_band_name(band_name) + logged_in_as = ", " + str(current_user.id) if current_user.__dict__.get("id") else None + return render_template('band.html', gigs=gigs, band_name=band_name, logged_in_as=logged_in_as) + +@app.route('/book_gig/', methods=["POST"]) +@login_required def post_book_gig(gig_id): connection = get_flask_database_connection(app) repo = BookingRepository(connection) @@ -95,9 +130,9 @@ def post_book_gig(gig_id): repo.make_booking(gig_id, user_database_id, request.form["ticket_count"]) return redirect(url_for('get_account')) -@app.route("/login", methods=["POST"]) +@app.route('/login', methods=["POST"]) def post_login(): - username = request.form["usernmae"] + username = request.form["username"] password = request.form["password"] connection = get_flask_database_connection(app) @@ -113,12 +148,14 @@ def post_login(): @app.route('/login', methods=['GET']) def get_login(): - return render_template('login.html') + logged_in_as = ", " + str(current_user.id) if current_user.__dict__.get("id") else None + return render_template('login.html', logged_in_as=logged_in_as) @app.route('/logout', methods=['GET']) def get_logout(): logout_user() - return render_template('logout.html') + logged_in_as = ", " + str(current_user.id) if current_user.__dict__.get("id") else None + return render_template('logout.html', logged_in_as=logged_in_as) @app.route('/account', methods=['GET']) @login_required @@ -136,7 +173,83 @@ def get_account(): "ticket_count": ticket_text, "gig": repo.get_by_id(booking.gig_id) }) - return render_template('account.html', booking_details=booking_details) + logged_in_as = ", " + str(current_user.id) if current_user.__dict__.get("id") else None + return render_template('account.html', booking_details=booking_details, logged_in_as=logged_in_as) + + + +# Admin routes + +@app.route('/admin', methods=["GET"]) +@admin_user_required +def get_admin(): + logged_in_as = ", " + str(current_user.id) if current_user.__dict__.get("id") else None + return render_template('admin.html', logged_in_as=logged_in_as) + +@app.route('/admin_add_gig', methods=["POST"]) +@admin_user_required +def admin_add_gig(): + connection = get_flask_database_connection(app) + repo = GigRepository(connection) + datetime = request.form["datetime"] + band = request.form["band"] + venue = request.form["venue"] + location = request.form["location"] + postcode = request.form["postcode"] + repo.add_gig(Gig(None, datetime, band, venue, location, postcode)) + return render_template('admin.html') + + + +# API routes + +@app.route('/api') +def api_root(): + return "You need to specify a resource such as \"gigs\" via a request like GET /api/<resource>" + +@app.route('/api/') +def api_resource(resource): + match resource: + case "gigs": + if request.method == "GET": + selected_location = "All" + if "location" in request.args.keys(): + selected_location = request.args["location"] + date_from = "2000-01-01" + if "date_from" in request.args.keys(): + date_from = request.args["date_from"] + date_to = "2100-01-01" + if "date_to" in request.args.keys(): + date_to = request.args["date_to"] + connection = get_flask_database_connection(app) + repo = GigRepository(connection) + gigs = repo.get_by_location_and_dates(selected_location, date_from, date_to) + return json.dumps([gig.jsonify() for gig in gigs]) + case "bands": + if request.method == "GET": + connection = get_flask_database_connection(app) + repo = GigRepository(connection) + gigs = repo.all() + bands = set([gig.band for gig in gigs]) + return json.dumps(list(bands)) + case "accounts" | "bookings": + return "You need to specify an Id for this resource via a request like GET /api/<resource>/<Id>" + case _: + return "Unknown API resource: " + resource + +@app.route('/api/gigs/') +def api_gig(id): + connection = get_flask_database_connection(app) + repo = GigRepository(connection) + gig = repo.get_by_id(id) + return json.dumps(gig.jsonify()) + +@app.route('/api/bands/') +def api_band(name): + connection = get_flask_database_connection(app) + repo = GigRepository(connection) + gigs = repo.get_by_band_name(name) + return json.dumps([gig.jsonify() for gig in gigs]) if __name__ == '__main__': app.run(debug=True, port=int(os.environ.get('PORT', 5001))) diff --git a/lib/database_connection.py b/lib/database_connection.py index f9f41ff..9df3b7f 100644 --- a/lib/database_connection.py +++ b/lib/database_connection.py @@ -44,7 +44,7 @@ def execute(self, query, params=[]): 'in your app.py file (or in your tests)?' def _check_connection(self): - if self.connection is None: + if not hasattr(self, "connection"): raise Exception(self.CONNECTION_MESSAGE) def _database_name(self): diff --git a/lib/gig.py b/lib/gig.py index a33a9bf..f911219 100644 --- a/lib/gig.py +++ b/lib/gig.py @@ -26,3 +26,13 @@ def __eq__(self, other): def datetime_pretty(self): return self.datetime.strftime("%Y-%m-%d %H:%M") + + def jsonify(self): + return { + "id": self.id, + "datetime": self.datetime_pretty(), + "band": self.band, + "venue": self.venue, + "location": self.location, + "postcode": self.postcode + } diff --git a/lib/gig_repository.py b/lib/gig_repository.py index cf1fc6a..1241bcf 100644 --- a/lib/gig_repository.py +++ b/lib/gig_repository.py @@ -1,11 +1,12 @@ from lib.gig import Gig +from datetime import date class GigRepository: def __init__(self, connection): self._connection = connection def all(self): - rows = self._connection.execute('SELECT * FROM gigs') + rows = self._connection.execute('SELECT * FROM gigs ORDER BY datetime') gigs = [] for row in rows: gigs.append(Gig(row["id"], row["datetime"], row["band"], row["venue"], row["location"], row["postcode"])) @@ -26,14 +27,16 @@ def get_by_location(self, location): gigs.append(Gig(row["id"], row["datetime"], row["band"], row["venue"], row["location"], row["postcode"])) return gigs - def get_by_dates(self, date_from="1900-01-01", date_to="3000-01-01"): - rows = self._connection.execute('SELECT * FROM gigs WHERE datetime BETWEEN %s AND %s ORDER BY datetime', [date_from, date_to]) + def get_by_dates(self, date_from="2000-01-01", date_to="2100-01-01"): + if date.fromisoformat(date_from) > date.fromisoformat(date_to): + raise Exception("Past cannot be after future") + rows = self._connection.execute('SELECT * FROM gigs WHERE datetime BETWEEN %s AND %s ORDER BY datetime', [date_from + " 00:00", date_to + " 23:59"]) gigs = [] for row in rows: gigs.append(Gig(row["id"], row["datetime"], row["band"], row["venue"], row["location"], row["postcode"])) return gigs - def get_by_location_and_dates(self, location, date_from="1900-01-01", date_to="3000-01-01"): + def get_by_location_and_dates(self, location, date_from="2000-01-01", date_to="2100-01-01"): if location == "All": gigs_by_location = self.all() else: @@ -44,3 +47,14 @@ def get_by_location_and_dates(self, location, date_from="1900-01-01", date_to="3 if gig in gigs_by_dates: matches.append(gig) return matches + + def get_by_band_name(self, band_name): + rows = self._connection.execute('SELECT * FROM gigs WHERE band = %s ORDER BY datetime', [band_name]) + gigs = [] + for row in rows: + gigs.append(Gig(row["id"], row["datetime"], row["band"], row["venue"], row["location"], row["postcode"])) + return gigs + + def add_gig(self, gig): + self._connection.execute('INSERT INTO gigs (datetime, band, venue, location, postcode) VALUES (%s, %s, %s, %s, %s)', + [gig.datetime, gig.band, gig.venue, gig.location, gig.postcode]) diff --git a/static/images/band.png b/static/images/band.png new file mode 100644 index 0000000..18956a9 Binary files /dev/null and b/static/images/band.png differ diff --git a/static/images/front.png b/static/images/front.png new file mode 100644 index 0000000..bc52a8a Binary files /dev/null and b/static/images/front.png differ diff --git a/static/images/logo.bmp b/static/images/logo.bmp index add9567..5c68f9c 100644 Binary files a/static/images/logo.bmp and b/static/images/logo.bmp differ diff --git a/static/images/logo.png b/static/images/logo.png new file mode 100644 index 0000000..e9e1fe9 Binary files /dev/null and b/static/images/logo.png differ diff --git a/static/style.css b/static/style.css index 3f4f5b2..a2963ac 100644 --- a/static/style.css +++ b/static/style.css @@ -1,10 +1,48 @@ body { background-color: #d7d4c9; } -table { - background-color: #000000; - width: 100%; +#page-content { + margin-left: 20px; + max-width: 850px; } -table a { - color: #FFFFFF; +h2 { + margin-bottom: 0; +} +#logo { + margin: 15px; + margin-right: 30px; +} +.menu_bar { + background-color: black; + height: 3em; + margin-bottom: 10px; /** spacing below menu, before page content **/ +} +.btn-menu{ + --bs-btn-color:#fff; + --bs-btn-bg:#c7c4b9; + --bs-btn-border-color:#c7c4b9; + --bs-btn-hover-color:#fff; + --bs-btn-hover-bg:#a7a499; + --bs-btn-hover-border-color:#a7a499; + --bs-btn-active-color:#bfb; + --bs-btn-active-bg:#979489; + --bs-btn-active-border-color:#979489; + --bs-btn-padding-y:0.25rem; + --bs-btn-font-size:0.875rem; + --bs-btn-border-radius:var(--bs-border-radius-sm) +} +.date-picker { + min-width: 160px; +} +.gig { + background-color: #fff; + padding-left: 15px; + padding-top: 15px; + padding-bottom: 1px; + margin-bottom: 20px; + width: 600px; + border-radius: 0.375rem; +} +.login-field { + margin-bottom: 10px; } \ No newline at end of file diff --git a/templates/401.html b/templates/401.html new file mode 100644 index 0000000..1d652c6 --- /dev/null +++ b/templates/401.html @@ -0,0 +1,18 @@ + + + + 401 Unauthorised + + + + + +

+
+

401 Unauthorised

+

You are not allowed to access this page.

+


+

Why don't you try to Log In first...

+
+ + diff --git a/templates/404.html b/templates/404.html new file mode 100644 index 0000000..5f5f419 --- /dev/null +++ b/templates/404.html @@ -0,0 +1,21 @@ + + + + 404 Not Found + + + + + +

+
+

404 Not Found

+

I'm sorry to inform you that you have sailed off the edge of the world.

+

There is nothing to find nor look at here.

+

Just infinite emptiness.

+

Goodbye.

+


+

Alternatively, you could come back to us...

+
+ + diff --git a/templates/about.html b/templates/about.html index 7a32bcd..cb1cbaf 100644 --- a/templates/about.html +++ b/templates/about.html @@ -2,31 +2,41 @@ About Giga + + -

About Giga

+ +

About Giga

{% include 'menu_bar.html' %} -

Let's learn all about Giga!

-

Giga isn't just a website; it's your personal concert concierge with a backstage - pass to the best live music around. Forget about FOMO - Giga's got you covered. - Discover a vibrant universe of bands, from up-and-coming sensations to your - seasoned favourites. Dive into a city's vibrant music scene or venture into - uncharted musical territories. Whether you're a dedicated genrer aficionado or - just looking to shake things up, Giga is your go-to passport to a world of live - music.

- -

But that's not all. Giga is rewriting the rules of the concert game. It's not - just about finding gigs, it's being able to experience them all. We're - talking free tickets, people. Yep, you read that right. No more shelling out big - bucks for your fave bands. Gig is here to make live music accessible and - affordable for everyone. From intimate club shows that feel like you've stumbled - across a secret to massive arena tours that will blow your mind - we've got you - covered. With Giga, you can sample new genrers, discover up-and-coming talent or - revisit the magic of your musical heroes - all without spending a penny on tickets.

- -

So, what are you waiting for? Join the Giga revolution and unlock a world of - endless possibilties. Discover new sounds, score free tickets, and create - unforgettable memories. It's time to turn up the volume on your life.

+
+

Let's learn all about Giga!

+

Giga isn't just a website; it's your personal concert concierge with a backstage + pass to the best live music around. Forget about FOMO - Giga's got you covered. + Discover a vibrant universe of bands, from up-and-coming sensations to your + seasoned favourites. Dive into a city's vibrant music scene or venture into + uncharted musical territories. Whether you're a dedicated genre aficionado or + just looking to shake things up, Giga is your go-to passport to a world of live + music.

+ +

But that's not all. Giga is rewriting the rules of the concert game. It's not + just about finding gigs, it's being able to experience them all. We're + talking free tickets, people. Yep, you read that right. No more shelling out big + bucks for your fave bands. Giga is here to make live music accessible and + affordable for everyone. From intimate club shows that feel like you've stumbled + across a secret to massive arena tours that will blow your mind - we've got you + covered. With Giga, you can sample new genres, discover up-and-coming talent or + revisit the magic of your musical heroes - all without spending a penny on tickets.

+ +

So, what are you waiting for? Join the Giga revolution and unlock a world of + endless possibilities. Discover new sounds, score free tickets, and create + unforgettable memories. It's time to turn up the volume on your + life.

+
diff --git a/templates/account.html b/templates/account.html index 2ace1c8..293fe5a 100644 --- a/templates/account.html +++ b/templates/account.html @@ -2,15 +2,24 @@ Account + -

Account

+ +

Account

{% include 'menu_bar.html' %} - {% for booking_detail in booking_details %} -
-

{{ booking_detail.ticket_count }} for {{ booking_detail.gig.band }} @ {{ booking_detail.gig.venue }}, {{ booking_detail.gig.location }} on {{ booking_detail.gig.datetime_pretty() }}

+
+ {% if booking_details %} +

You currently have these tickets and gigs booked:

+ {% for booking_detail in booking_details %} +
+

{{ booking_detail.ticket_count }} for {{ booking_detail.gig.band }} @ {{ booking_detail.gig.venue }}, {{ booking_detail.gig.location }} on {{ booking_detail.gig.datetime_pretty() }}

+
+ {% endfor %} + {% else %} +

You currently have no tickets to any gigs! Go book some!

+ {% endif %}
- {% endfor %} diff --git a/templates/admin.html b/templates/admin.html new file mode 100644 index 0000000..1d6abc4 --- /dev/null +++ b/templates/admin.html @@ -0,0 +1,40 @@ + + + + Secret Admin Page + + + + + +

Secret Admin Page

+ {% include 'menu_bar.html' %} +

Content in here is debug only

+

Add gigs to database

+
+

+ + +

+

+ + +

+

+ + +

+

+ + +

+

+ + +

+

+ +

+
+ + \ No newline at end of file diff --git a/templates/band.html b/templates/band.html new file mode 100644 index 0000000..cdb67ec --- /dev/null +++ b/templates/band.html @@ -0,0 +1,30 @@ + + + + {{ band_name }}: Gig Dates + + + + + + +

{{ band_name }}: Gig Dates

+ {% include 'menu_bar.html' %} +
+
+ {% for gig in gigs %} +
+

{{ gig.band }} @ {{ gig.venue }}, {{ gig.location }}

+

When: {{ gig.datetime_pretty() }}

+

More details & booking

+
+ {% endfor %} +
+
+ + \ No newline at end of file diff --git a/templates/gig.html b/templates/gig.html index a0e3df4..faeaaf2 100644 --- a/templates/gig.html +++ b/templates/gig.html @@ -2,26 +2,30 @@ Gig: {{ gig.band }} + -

Gig: {{ gig.band }} @ {{ gig.venue }}

+ +

Gig: {{ gig.band }} @ {{ gig.venue }}

{% include 'menu_bar.html' %} - When -

{{ gig.datetime_pretty() }}

- Where -

{{ gig.venue }}

{{ gig.location }}

{{ gig.postcode }}

- Book this gig now on Giga! - {% if already_booked_gig %} -

You have already booked this gig

- {% elif logged_in_as %} -
- - - -
- {% else %} -

You need to log in before booking tickets

- {% endif %} +
+ When +

{{ gig.datetime_pretty() }}

+ Where +

{{ gig.venue }}

{{ gig.location }}

{{ gig.postcode }}

+ Book this gig now on Giga! + {% if already_booked_gig %} +

You have already booked this gig

+ {% elif logged_in_as %} +
+ + + +
+ {% else %} +

You need to log in before booking tickets

+ {% endif %} +
diff --git a/templates/gigs.html b/templates/gigs.html index da08cb2..b1e8270 100644 --- a/templates/gigs.html +++ b/templates/gigs.html @@ -2,34 +2,79 @@ Gigs + + -

Gigs

+ + +

Gigs

{% include 'menu_bar.html' %} -
- - + {% for location in locations %} + {% if location == selected_location %} + + {% else %} + + {% endif %} + {% endfor %} + +
+
+ + + +
+
+ + + +
+

+ + + +
+ {% for gig in gigs %} +
+
+
+

{{ gig.band }} @ {{ gig.venue }}, {{ gig.location }}

+

When: {{ gig.datetime_pretty() }}

+

More details & booking

+
+
+ +
+
+
{% endfor %} - - - - - -

- - {% for gig in gigs %} -
-

{{ gig.band }} @ {{ gig.venue }}, {{ gig.location }}

-

{{ gig.datetime_pretty() }}

-

More details & booking

+
- {% endfor %} diff --git a/templates/home.html b/templates/home.html index 429ab64..286ef55 100644 --- a/templates/home.html +++ b/templates/home.html @@ -2,14 +2,30 @@ Welcome to Giga + -

Welcome to Giga

+ +

Welcome to Giga{% if logged_in_as %}{{ logged_in_as }}{% endif %}

{% include 'menu_bar.html' %} -

Here you'll find a highly-curated list of gigs from around the UK, - from some of the most amazing bands on the planet!

-

This is your one-stop-shop for finding all about the gigs that are - available and booking tickets to gigs for free.

+
+
+
+
+ +
+
+

Here you'll find a highly-curated list of gigs from around the UK, + from some of the most amazing bands on the planet!

+

This is your one-stop-shop for finding all about the gigs that are + available and booking tickets to gigs for free.

+
+
+
+

All images on this site are AI-generated

+
+
+
diff --git a/templates/login.html b/templates/login.html index 1cfe82e..af2d096 100644 --- a/templates/login.html +++ b/templates/login.html @@ -2,15 +2,44 @@ Log In + + -

Log In

+ +

Log In

{% include 'menu_bar.html' %} -
- - - -
+
+
+
+ + +
+
+ +
+
+
+
+
diff --git a/templates/logout.html b/templates/logout.html index c1ba0db..9517277 100644 --- a/templates/logout.html +++ b/templates/logout.html @@ -2,11 +2,20 @@ Log Out + + -

Log Out

+ +

Log Out

{% include 'menu_bar.html' %} -

You have logged out.

+
+

You have logged out.

+
diff --git a/templates/menu_bar.html b/templates/menu_bar.html index 248fd07..580d228 100644 --- a/templates/menu_bar.html +++ b/templates/menu_bar.html @@ -1,8 +1,13 @@ - - - - - - - -
HomeGigsLog InLog OutAccountAbout
\ No newline at end of file +
+ +
\ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py index 2cc73b8..e418ac7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -31,3 +31,12 @@ def web_client(): app.config['TESTING'] = True # This gets us better errors with app.test_client() as client: yield client + +@pytest.fixture +def logged_in_page_username(page, test_web_address, db_connection): + db_connection.seed("seeds/test_users.sql") + page.goto(f"http://{test_web_address}/login") + page.fill("input[name='username']", "username") + page.fill("input[name='password']", "password") + page.click("text='Log in'") + return page diff --git a/tests/test_app.py b/tests/test_app.py index b4ef784..611b825 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -2,45 +2,85 @@ def test_get_index(page, test_web_address): page.goto(f"http://{test_web_address}/home") - p_tag = page.locator("p#element-734ab6c") - expect(p_tag).to_contain_text("Welcome to Giga") + h2_tag = page.locator("h2#element-734ab6c") + expect(h2_tag).to_contain_text("Welcome to Giga") def test_get_about(page, test_web_address): page.goto(f"http://{test_web_address}/about") - p_tag = page.locator("p#element-ad4752a") - expect(p_tag).to_contain_text("About Giga") + h2_tag = page.locator("h2#element-ad4752a") + expect(h2_tag).to_contain_text("About Giga") def test_gigs(page, test_web_address, db_connection): db_connection.seed("seeds/test_gigs.sql") page.goto(f"http://{test_web_address}/gigs") - gig_tags = page.locator("div") + gig_tags = page.locator("div.gig") expect(gig_tags).to_contain_text([ - "Placebo @ Brixton Academy, London\n2024-06-04 19:30", - "Portishead @ Brixton Academy, London\n2024-06-11 19:30", - "Placebo @ The Roundhouse, London\n2024-06-11 20:00", - "Phantogram @ Corn Exchange, Cambridge\n2024-06-18 20:30" + "Placebo @ Brixton Academy, London\nWhen: 2024-06-04 19:30", + "Portishead @ Brixton Academy, London\nWhen: 2024-06-11 19:30", + "Placebo @ The Roundhouse, London\nWhen: 2024-06-11 20:00", + "Phantogram @ Corn Exchange, Cambridge\nWhen: 2024-06-18 20:30" ]) def test_individual_gig(page, test_web_address): page.goto(f"http://{test_web_address}/gigs/2") - p_tag = page.locator("p#element-632c964") - expect(p_tag).to_have_text("Gig: Portishead @ Brixton Academy") + h2_tag = page.locator("h2#element-632c964") + expect(h2_tag).to_have_text("Gig: Portishead @ Brixton Academy") + +def test_individual_band(page, test_web_address): + page.goto(f"http://{test_web_address}/bands/Placebo") + h2_tag = page.locator("h2#element-99ea37c") + expect(h2_tag).to_have_text("Placebo: Gig Dates") + gig_tags = page.locator("div.gig") + expect(gig_tags).to_contain_text([ + "Placebo @ Brixton Academy, London\nWhen: 2024-06-04 19:30", + "Placebo @ The Roundhouse, London\nWhen: 2024-06-11 20:00" + ]) def test_get_login(page, test_web_address): page.goto(f"http://{test_web_address}/login") - p_tag = page.locator("p#element-2b685e0") - expect(p_tag).to_contain_text("Log In") + h2_tag = page.locator("h2#element-2b685e0") + expect(h2_tag).to_contain_text("Log In") def test_get_logout(page, test_web_address): page.goto(f"http://{test_web_address}/logout") - p_tag = page.locator("p#element-064fcc0") - expect(p_tag).to_contain_text("Log Out") + h2_tag = page.locator("h2#element-064fcc0") + expect(h2_tag).to_contain_text("Log Out") + +def test_denied_access_to_account_page(web_client, test_web_address): + response = web_client.get(f"http://{test_web_address}/account") + assert response.status_code == 401 def test_can_switch_between_gigs_and_home(page, test_web_address): page.goto(f"http://{test_web_address}/home") page.click("text='Gigs'") - p_tag = page.locator("p#element-c27b2f2") - expect(p_tag).to_contain_text("Gigs") + h2_tag = page.locator("h2#element-c27b2f2") + expect(h2_tag).to_contain_text("Gigs") page.click("text='Home'") - p_tag = page.locator("p#element-734ab6c") - expect(p_tag).to_contain_text("Welcome to Giga") + h2_tag = page.locator("h2#element-734ab6c") + expect(h2_tag).to_contain_text("Welcome to Giga") + +def test_login_as_username(page, test_web_address, db_connection): + db_connection.seed("seeds/test_users.sql") + page.goto(f"http://{test_web_address}/login") + page.fill("input[name='username']", "username") + page.fill("input[name='password']", "password") + page.click("text='Log in'") + h2_tag = page.locator("h2") + expect(h2_tag).to_have_text("Welcome to Giga, username") + +def test_logout_as_username(logged_in_page_username, web_client, test_web_address): + logged_in_page_username.goto(f"http://{test_web_address}/logout") + logged_in_page_username.goto(f"http://{test_web_address}/account") + h1_tag = logged_in_page_username.locator("h1") + expect(h1_tag).to_have_text("401 Unauthorised") + +def test_get_account_when_logged_in(logged_in_page_username, test_web_address, db_connection): + db_connection.seed("seeds/test_bookings.sql") + logged_in_page_username.goto(f"http://{test_web_address}/account") + h2_tag = logged_in_page_username.locator("h2#element-1cfd0cf") + expect(h2_tag).to_contain_text("Account") + p_tags = logged_in_page_username.locator("p.element-35cbe6d") + expect(p_tags).to_contain_text([ + "1 ticket for Placebo @ Brixton Academy, London on 2024-06-04 19:30", + "4 tickets for Phantogram @ Corn Exchange, Cambridge on 2024-06-18 20:30" + ]) diff --git a/tests/test_database_connection.py b/tests/test_database_connection.py new file mode 100644 index 0000000..2a5a0b5 --- /dev/null +++ b/tests/test_database_connection.py @@ -0,0 +1,28 @@ +from lib.database_connection import DatabaseConnection +import pytest + +def test_database_name_method_uses_constants(): + dbc = DatabaseConnection(True) + assert dbc._database_name() == dbc.TEST_DATABASE_NAME + dbc.test_mode = False + assert dbc._database_name() == dbc.DEV_DATABASE_NAME + +def test_exception_when_database_not_created(): + dbc = DatabaseConnection(True) + dbc.TEST_DATABASE_NAME = "thiswasnotcreated" + with pytest.raises(Exception) as e: + dbc.connect() + assert str(e.value) == "Couldn't connect to the database thiswasnotcreated! Did you create it using `createdb thiswasnotcreated`?" + +def test_exception_when_no_connection(): + dbc = DatabaseConnection(True) + with pytest.raises(Exception) as e: + dbc._check_connection() + assert "Cannot run a SQL query" in str(e.value) + +def test_exception_when_unknown_seed_file(): + dbc = DatabaseConnection(True) + dbc.connect() + with pytest.raises(Exception) as e: + dbc.seed("doesnotexist.sql") + assert str(e.value) == "File doesnotexist.sql does not exist" diff --git a/tests/test_gig.py b/tests/test_gig.py index 332b6dd..85dbc51 100644 --- a/tests/test_gig.py +++ b/tests/test_gig.py @@ -13,3 +13,14 @@ def test_gig_attributes(): def test_pretty_datetime(): gig = Gig(None, "2024-06-04 19:30", None, None, None, None) assert gig.datetime_pretty() == "2024-06-04 19:30" + +def test_jsonify_object(): + gig = Gig(111, "2024-06-04 19:30", "Placebo", "Brixton Academy", "London", "SW1 2AA") + assert gig.jsonify() == { + "id": 111, + "datetime": "2024-06-04 19:30", + "band": "Placebo", + "venue": "Brixton Academy", + "location": "London", + "postcode": "SW1 2AA" + } diff --git a/tests/test_gig_repository.py b/tests/test_gig_repository.py index 94f2f11..b94d838 100644 --- a/tests/test_gig_repository.py +++ b/tests/test_gig_repository.py @@ -1,5 +1,6 @@ from lib.gig_repository import GigRepository from lib.gig import Gig +import pytest def test_get_gigs(db_connection): db_connection.seed("seeds/test_gigs.sql") @@ -17,6 +18,11 @@ def test_get_by_id(db_connection): repo = GigRepository(db_connection) assert repo.get_by_id(2) == Gig(2, "2024-06-11 19:30", "Portishead", "Brixton Academy", "London", "SW9 9SL") +def test_get_by_id_nothing_found(db_connection): + db_connection.seed("seeds/test_gigs.sql") + repo = GigRepository(db_connection) + assert repo.get_by_id(100) == None + def test_get_by_location(db_connection): db_connection.seed("seeds/test_gigs.sql") repo = GigRepository(db_connection) @@ -37,6 +43,13 @@ def test_get_by_simple_date_range(db_connection): Gig(3, "2024-06-11 20:00", "Placebo", "The Roundhouse", "London", "NW1 8EH") ] +def test_get_by_dates_invalid_range(db_connection): + db_connection.seed("seeds/test_gigs.sql") + repo = GigRepository(db_connection) + with pytest.raises(Exception) as e: + repo.get_by_dates("2024-06-14", "2024-06-12") + assert str(e.value) == "Past cannot be after future" + def test_get_by_location_and_dates_specific(db_connection): db_connection.seed("seeds/test_gigs.sql") repo = GigRepository(db_connection) @@ -52,3 +65,24 @@ def test_get_by_location_and_dates_all(db_connection): Gig(3, "2024-06-11 20:00", "Placebo", "The Roundhouse", "London", "NW1 8EH"), Gig(4, "2024-06-18 20:30", "Phantogram", "Corn Exchange", "Cambridge", "CB2 3QB") ] + +def test_get_by_band_name(db_connection): + db_connection.seed("seeds/test_gigs.sql") + repo = GigRepository(db_connection) + assert repo.get_by_band_name("Placebo") == [ + Gig(1, "2024-06-04 19:30", "Placebo", "Brixton Academy", "London", "SW9 9SL"), + Gig(3, "2024-06-11 20:00", "Placebo", "The Roundhouse", "London", "NW1 8EH") + ] + +def test_add_gig(db_connection): + db_connection.seed("seeds/test_gigs.sql") + repo = GigRepository(db_connection) + repo.add_gig(Gig(None, "2024-06-10 21:00", "Bush", "Corn Exchange", "Cambridge", "CB2 3QB")) + gigs = repo.all() + assert gigs == [ + Gig(1, "2024-06-04 19:30", "Placebo", "Brixton Academy", "London", "SW9 9SL"), + Gig(5, "2024-06-10 21:00", "Bush", "Corn Exchange", "Cambridge", "CB2 3QB"), + Gig(2, "2024-06-11 19:30", "Portishead", "Brixton Academy", "London", "SW9 9SL"), + Gig(3, "2024-06-11 20:00", "Placebo", "The Roundhouse", "London", "NW1 8EH"), + Gig(4, "2024-06-18 20:30", "Phantogram", "Corn Exchange", "Cambridge", "CB2 3QB") + ]