From: Chris Fulljames Date: Mon, 20 Jan 2025 23:12:48 +0000 (-0500) Subject: Test updates X-Git-Url: https://littlesong.place/gitweb/?a=commitdiff_plain;h=198da3cc198e0a5df72babf8ce39e5872676290c;p=littlesongplace.git Test updates --- diff --git a/main.py b/main.py index 866b088..1b8e904 100644 --- a/main.py +++ b/main.py @@ -204,7 +204,7 @@ def edit_song(): songid = int(request.args["songid"]) except ValueError: # Invalid song id - file not found - app.logger.warning(f"Failed song edit - {session['username']} - invalid song ID {songid}") + app.logger.warning(f"Failed song edit - {session['username']} - invalid song ID {request.args['songid']}") abort(404) try: @@ -276,7 +276,7 @@ def validate_song_form(): collaborators = request.form["collabs"] collaborators = [c.strip() for c in collaborators.split(",")] for collab in collaborators: - if not collab.isprintable() or len(collab) > 31: + if not collab.isprintable() or len(collab) > 31: # 30ch username + @ flash_and_log(f"'{collab}' is not a valid collaborator name", "error") error = True @@ -396,19 +396,8 @@ def create_song(): flash_and_log(f"Successfully uploaded '{title}'", "success") return False -@app.get("/delete-song//") -def delete_song(userid, songid): - try: - # Make sure values are valid integers - int(userid) - int(songid) - except ValueError: - abort(404) - - # Users can only delete their own songs - if int(userid) != session["userid"]: - app.logger.warning(f"Failed song delete - {session['username']} - user doesn't own song") - abort(401) +@app.get("/delete-song/") +def delete_song(songid): song_data = query_db("select * from songs where songid = ?", [songid], one=True) @@ -416,6 +405,11 @@ def delete_song(userid, songid): app.logger.warning(f"Failed song delete - {session['username']} - song doesn't exist") abort(404) # Song doesn't exist + # Users can only delete their own songs + if song_data["userid"] != session["userid"]: + app.logger.warning(f"Failed song delete - {session['username']} - user doesn't own song") + abort(401) + # Delete tags, collaborators query_db("delete from song_tags where songid = ?", [songid]) query_db("delete from song_collaborators where songid = ?", [songid]) @@ -425,12 +419,12 @@ def delete_song(userid, songid): get_db().commit() # Delete song file from disk - songpath = DATA_DIR / "songs" / userid / (songid + ".mp3") + songpath = DATA_DIR / "songs" / str(session["userid"]) / (str(songid) + ".mp3") if songpath.exists(): os.remove(songpath) app.logger.info(f"{session['username']} deleted song: {song_data['title']}") - flash_and_log(f"Deleted {song_data['title']}") + flash_and_log(f"Deleted '{song_data['title']}'") return redirect(request.referrer) @@ -551,11 +545,11 @@ class Song: @classmethod def get_all_for_username_and_tag(cls, username, tag): - return cls._from_db(f"select * from song_tags inner join songs on song_tags.songid = songs.songid inner join users on songs.userid = users.userid where (username = ? and tag = ?)", [username, tag]) + return cls._from_db(f"select * from song_tags inner join songs on song_tags.songid = songs.songid inner join users on songs.userid = users.userid where (username = ? and tag = ?) order by songs.created desc", [username, tag]) @classmethod def get_all_for_tag(cls, tag): - return cls._from_db(f"select * from song_tags inner join songs on song_tags.songid = songs.songid inner join users on songs.userid = users.userid where (tag = ?)", [tag]) + return cls._from_db(f"select * from song_tags inner join songs on song_tags.songid = songs.songid inner join users on songs.userid = users.userid where (tag = ?) order by songs.created desc", [tag]) @classmethod def get_latest(cls, count): diff --git a/templates/song-list.html b/templates/song-list.html index 3eca33c..c445a8d 100644 --- a/templates/song-list.html +++ b/templates/song-list.html @@ -30,7 +30,7 @@ Edit - + Delete {% endif %} diff --git a/test.py b/test.py index 7942be2..f427854 100644 --- a/test.py +++ b/test.py @@ -1,6 +1,11 @@ +import html +import json +import re import tempfile from pathlib import Path +from unittest.mock import patch +import bcrypt import pytest from flask import session @@ -23,7 +28,10 @@ def app(): @pytest.fixture def client(app): - return app.test_client() + # Mock bcrypt to speed up tests + with patch.object(bcrypt, "hashpw", lambda passwd, salt: passwd), \ + patch.object(bcrypt, "checkpw", lambda passwd, saved: passwd == saved): + yield app.test_client() ################################################################################ # Signup @@ -91,11 +99,16 @@ def test_login_get(client): response = client.get("/login") assert b"Sign In" in response.data -def _create_user(client, username, password): +def _create_user(client, username, password="password", login=False): response = _post_signup_form(client, username, password) assert response.status_code == 302 assert response.headers["Location"] == "/login" + if login: + response = client.post("/login", data={"username": username, "password": password}) + assert response.status_code == 302 + assert response.headers["Location"] == f"/users/{username}" + def test_login_success(client): _create_user(client, "username", "password") response = client.post("/login", data={"username": "username", "password": "password"}) @@ -134,20 +147,352 @@ def test_logout(client, app): assert "userid" not in session ################################################################################ -# Profile +# Profile/Bio +################################################################################ + +def test_default_bio_empty(client): + _create_user(client, "user", "password") + + response = client.get("/users/user") + assert b'
' in response.data + +def test_update_bio(client): + _create_user(client, "user", "password", login=True) + + response = client.post("/update-bio", data={"bio": "this is the bio"}) + assert response.status_code == 302 + assert response.headers["Location"] == "/users/user" + + response = client.get("/users/user") + assert b'
this is the bio
' in response.data + +################################################################################ +# Upload Song +################################################################################ + +def _test_upload_song(client, msg, error=False, songid=None, user="user", **kwargs): + song_file = open("test/sample-3s.mp3", "rb") + + data = { + "song": song_file, + "title": "song title", + "description": "song description", + "tags": "tag", + "collabs": "collab", + } + for k, v in kwargs.items(): + data[k] = v + + if songid: + response = client.post(f"/upload-song?songid={songid}", data=data) + else: + response = client.post("/upload-song", data=data) + + assert response.status_code == 302 + if error: + assert response.headers["Location"] == "None" + else: + assert response.headers["Location"] == f"/users/{user}" + + response = client.get(f"/users/{user}") + assert msg in response.data + +def test_upload_song_success(client): + _create_user(client, "user", "password", login=True) + _test_upload_song(client, b"Successfully uploaded 'song title'") + +def test_upload_song_bad_title(client): + _create_user(client, "user", "password", login=True) + _test_upload_song(client, b"not a valid song title", error=True, title="\r\n") + +def test_upload_song_title_too_long(client): + _create_user(client, "user", "password", login=True) + _test_upload_song(client, b"cannot be more than 80 characters", error=True, title="a"*81) + +def test_upload_song_description_too_long(client): + _create_user(client, "user", "password", login=True) + _test_upload_song(client, b"cannot be more than 10k characters", error=True, description="a"*10_001) + +def test_upload_song_invalid_tag(client): + _create_user(client, "user", "password", login=True) + _test_upload_song(client, b"not a valid tag name", error=True, tags="a\r\na") + +def test_upload_song_tag_too_long(client): + _create_user(client, "user", "password", login=True) + _test_upload_song(client, b"not a valid tag name", error=True, tags="a"*31) + +def test_upload_song_invalid_collab(client): + _create_user(client, "user", "password", login=True) + _test_upload_song(client, b"not a valid collaborator name", error=True, collabs="a\r\na") + +def test_upload_song_collab_too_long(client): + _create_user(client, "user", "password", login=True) + _test_upload_song(client, b"not a valid collaborator name", error=True, collabs="a"*32) + +def test_upload_song_invalid_mp3(client): + _create_user(client, "user", "password", login=True) + song_file = open("test.py", "rb") + _test_upload_song(client, b"Invalid mp3 file", error=True, song=song_file) + ################################################################################ +# Edit Song +################################################################################ + +def test_edit_invalid_song(client): + _create_user(client, "user", "password", login=True) + response = client.get("/edit-song?songid=1") + assert response.status_code == 404 + +def test_edit_invalid_id(client): + _create_user(client, "user", "password", login=True) + response = client.get("/edit-song?songid=abc") + assert response.status_code == 404 + +def test_edit_other_users_song(client): + _create_user(client, "user", "password", login=True) + _test_upload_song(client, b"Success") + + _create_user(client, "user2", "password", login=True) + response = client.get("/edit-song?songid=1") + assert response.status_code == 401 + +def _create_user_and_song(client): + _create_user(client, "user", "password", login=True) + _test_upload_song(client, b"Success") + +def test_update_song_success(client): + _create_user_and_song(client) + _test_upload_song(client, b"Successfully updated 'song title'", songid=1) + +def test_update_song_bad_title(client): + _create_user_and_song(client) + _test_upload_song(client, b"not a valid song title", error=True, songid=1, title="\r\n") + +def test_update_song_title_too_long(client): + _create_user_and_song(client) + _test_upload_song(client, b"cannot be more than 80 characters", error=True, songid=1, title="a"*81) + +def test_update_song_description_too_long(client): + _create_user_and_song(client) + _test_upload_song(client, b"cannot be more than 10k characters", error=True, songid=1, description="a"*10_001) + +def test_update_song_invalid_tag(client): + _create_user_and_song(client) + _test_upload_song(client, b"not a valid tag name", error=True, songid=1, tags="a\r\na") + +def test_update_song_tag_too_long(client): + _create_user_and_song(client) + _test_upload_song(client, b"not a valid tag name", error=True, songid=1, tags="a"*31) + +def test_update_song_invalid_collab(client): + _create_user_and_song(client) + _test_upload_song(client, b"not a valid collaborator name", error=True, songid=1, collabs="a\r\na") + +def test_update_song_collab_too_long(client): + _create_user_and_song(client) + _test_upload_song(client, b"not a valid collaborator name", error=True, songid=1, collabs="a"*32) + +def test_update_song_invalid_mp3(client): + _create_user_and_song(client) + song_file = open("test.py", "rb") + _test_upload_song(client, b"Invalid mp3 file", error=True, songid=1, song=song_file) + +def test_update_song_invalid_song(client): + _create_user_and_song(client) + + data = { + "song": open("test/sample-3s.mp3", "rb"), + "title": "song title", + "description": "song description", + "tags": "tag", + "collabs": "collab", + } + + response = client.post(f"/upload-song?songid=2", data=data) + assert response.status_code == 400 + +def test_update_song_invalid_id(client): + _create_user_and_song(client) + + data = { + "song": open("test/sample-3s.mp3", "rb"), + "title": "song title", + "description": "song description", + "tags": "tag", + "collabs": "collab", + } + + response = client.post(f"/upload-song?songid=abc", data=data) + assert response.status_code == 400 + +def test_update_song_other_users_song(client): + _create_user_and_song(client) + _create_user(client, "user2", login=True) + + data = { + "song": open("test/sample-3s.mp3", "rb"), + "title": "song title", + "description": "song description", + "tags": "tag", + "collabs": "collab", + } + + response = client.post(f"/upload-song?songid=1", data=data) + assert response.status_code == 401 + +################################################################################ +# Delete Song +################################################################################ + +def test_delete_song_success(client): + _create_user_and_song(client) + response = client.get("/delete-song/1") + assert response.status_code == 302 + assert response.headers["Location"] == "None" + + response = client.get("/") + assert b"Deleted 'song title'" in response.data + + # mp3 file deleted + response = client.get("/song/1/1") + assert response.status_code == 404 + +def test_delete_song_invalid_song(client): + _create_user_and_song(client) + response = client.get("/delete-song/2") + assert response.status_code == 404 -# TODO +def test_delete_song_invalid_id(client): + _create_user_and_song(client) + response = client.get("/delete-song/abc") + assert response.status_code == 404 + +def test_delete_song_other_users_song(client): + _create_user_and_song(client) + _create_user(client, "user2", login=True) + response = client.get("/delete-song/1") + assert response.status_code == 401 ################################################################################ -# Upload/Edit Song +# Song mp3 file ################################################################################ -# TODO +def test_get_song(client): + _create_user_and_song(client) + response = client.get("/song/1/1") + with open("test/sample-3s.mp3", "rb") as mp3file: + assert response.data == mp3file.read() + +def test_get_song_invalid_song(client): + _create_user_and_song(client) + response = client.get("/song/1/2") + assert response.status_code == 404 + +def test_get_song_invalid_user(client): + _create_user_and_song(client) + response = client.get("/song/2/1") + assert response.status_code == 404 ################################################################################ -# Song Lists +# Song Lists (Profile/Homepage/Songs) ################################################################################ -# TODO +# Profile + +def _get_song_list_from_page(client, url): + response = client.get(url) + matches = re.findall('data-song="(.*)">', response.data.decode()) + return [json.loads(html.unescape(m)) for m in matches] + +def test_profile_songs_one_song(client): + _create_user_and_song(client) + songs = _get_song_list_from_page(client, "/users/user") + + assert len(songs) == 1 + assert songs[0]["title"] == "song title" + +def test_profile_songs_two_songs(client): + _create_user_and_song(client) + _test_upload_song(client, b"Success", title="title2") + songs = _get_song_list_from_page(client, "/users/user") + + # Newest first + assert len(songs) == 2 + assert songs[0]["title"] == "title2" + assert songs[1]["title"] == "song title" + +# Homepage + +def test_homepage_songs_two_songs(client): + _create_user(client, "user1", "password", login=True) + _test_upload_song(client, b"Success", user="user1", title="song1") + + _create_user(client, "user2", "password", login=True) + _test_upload_song(client, b"Success", user="user2", title="song2") + + songs = _get_song_list_from_page(client, "/") + + # Newest first (all songs) + assert len(songs) == 2 + assert songs[0]["title"] == "song2" + assert songs[0]["username"] == "user2" + + assert songs[1]["title"] == "song1" + assert songs[1]["username"] == "user1" + +# Songs by tag + +def test_songs_by_tag_no_user(client): + _create_user(client, "user1", "password", login=True) + _test_upload_song(client, b"Success", user="user1", title="song1", tags="tag") + + _create_user(client, "user2", "password", login=True) + _test_upload_song(client, b"Success", user="user2", title="song2", tags="") + _test_upload_song(client, b"Success", user="user2", title="song3", tags="tag") + + songs = _get_song_list_from_page(client, "/songs?tag=tag") + + # Newest first + assert len(songs) == 2 + assert songs[0]["title"] == "song3" + assert songs[0]["username"] == "user2" + + # Song 2 not shown, no tag + + assert songs[1]["title"] == "song1" + assert songs[1]["username"] == "user1" + +def test_songs_by_tag_with_user(client): + _create_user(client, "user1", "password", login=True) + _test_upload_song(client, b"Success", user="user1", title="song1", tags="tag") + _test_upload_song(client, b"Success", user="user1", title="song2", tags="") + + _create_user(client, "user2", "password", login=True) + _test_upload_song(client, b"Success", user="user2", title="song3", tags="tag") + + songs = _get_song_list_from_page(client, "/songs?tag=tag&user=user1") + + assert len(songs) == 1 + assert songs[0]["title"] == "song1" + assert songs[0]["username"] == "user1" + # Song 2 not shown, no tag; song 3 not shown, by different user + +def test_songs_by_user(client): + _create_user(client, "user1", "password", login=True) + _test_upload_song(client, b"Success", user="user1", title="song1", tags="tag") + _test_upload_song(client, b"Success", user="user1", title="song2", tags="") + + _create_user(client, "user2", "password", login=True) + _test_upload_song(client, b"Success", user="user2", title="song3", tags="tag") + + songs = _get_song_list_from_page(client, "/songs?user=user1") + + # Newest first + assert len(songs) == 2 + assert songs[0]["title"] == "song2" + assert songs[0]["username"] == "user1" + + assert songs[1]["title"] == "song1" + assert songs[1]["username"] == "user1" + # Song 3 not shown, by different user diff --git a/test/sample-3s.mp3 b/test/sample-3s.mp3 new file mode 100644 index 0000000..23c2e3a Binary files /dev/null and b/test/sample-3s.mp3 differ diff --git a/test/sample-6s.mp3 b/test/sample-6s.mp3 new file mode 100644 index 0000000..2aaa278 Binary files /dev/null and b/test/sample-6s.mp3 differ