]> littlesong.place Git - littlesongplace.git/commitdiff
Test updates
authorChris Fulljames <christianfulljames@gmail.com>
Mon, 20 Jan 2025 23:12:48 +0000 (18:12 -0500)
committerChris Fulljames <christianfulljames@gmail.com>
Mon, 20 Jan 2025 23:12:48 +0000 (18:12 -0500)
main.py
templates/song-list.html
test.py
test/sample-3s.mp3 [new file with mode: 0644]
test/sample-6s.mp3 [new file with mode: 0644]

diff --git a/main.py b/main.py
index 866b08861e2fda5d4a1af6c70f26df722d92449c..1b8e9049a85fc5604915393dd47bbd9bebef19fa 100644 (file)
--- 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/<userid>/<songid>")
-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/<int:songid>")
+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):
index 3eca33c56a95d25f25f76ab0b014200b52bd908d..c445a8d96dac8dda5f95dab93c0f38b4f27fad3e 100644 (file)
@@ -30,7 +30,7 @@
                 <a href="/edit-song?songid={{ song.songid }}" class="song-list-button">
                     <img src="/static/lsp_btn_edit.gif" alt="Edit">
                 </a>
-                <a href="/delete-song/{{ song.userid }}/{{ song.songid }}" onclick="return confirm(&#34;Are you sure you want to delete this song?&#34;)" class="song-list-button">
+                <a href="/delete-song/{{ song.songid }}" onclick="return confirm(&#34;Are you sure you want to delete this song?&#34;)" class="song-list-button">
                     <img src="/static/lsp_btn_delete.gif" alt="Delete">
                 </a>
                 {% endif %}
diff --git a/test.py b/test.py
index 7942be258d45d92e2ae4036b2eabf25ec9172315..f427854c248f99f6a78ae5890d0541714bc4d7f5 100644 (file)
--- 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'<div class="profile-bio" id="profile-bio"></div>' 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'<div class="profile-bio" id="profile-bio">this is the bio</div>' 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 &#39;song title&#39;")
+
+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 &#39;song title&#39;", 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 &#39;song title&#39;" 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 (file)
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 (file)
index 0000000..2aaa278
Binary files /dev/null and b/test/sample-6s.mp3 differ