Subsonic: playlist endpoints (getPlaylists, getPlaylist, createPlaylist, updatePlaylist, deletePlaylist)

This commit is contained in:
2026-08-23 00:09:36 -05:00
parent a297454065
commit 6e2b875eaa
3 changed files with 461 additions and 0 deletions
+146
View File
@@ -0,0 +1,146 @@
package subsonic
import (
"net/http/httptest"
"testing"
"git.noroutes.net/NoRoutes/vibetunes/internal/database"
)
func TestPlaylistLifecycle(t *testing.T) {
s, db := newTestServer(t)
createTestUser(t, s, "alice", "password123", false)
seedLibrary(t, s)
srv := httptest.NewServer(s.Router())
defer srv.Close()
songs, err := database.ListSongs(db, 2, 0)
if err != nil || len(songs) != 2 {
t.Fatalf("ListSongs: %v %v", songs, err)
}
// Create
q := baseParams("alice", "password123")
q.Set("name", "My Playlist")
q.Add("songId", songs[0].ID)
q.Set("f", "json")
resp := getPath(t, srv.URL, "/rest/createPlaylist.view", q)
j := decodeJSON(t, resp)
playlist := j["playlist"].(map[string]any)
if playlist["name"] != "My Playlist" {
t.Fatalf("created playlist name = %v", playlist["name"])
}
playlistID := playlist["id"].(string)
if entrySongs, ok := playlist["entry"].([]any); !ok || len(entrySongs) != 1 {
t.Fatalf("created playlist entries = %v, want 1", playlist["entry"])
}
// getPlaylists lists it
resp = getPath(t, srv.URL, "/rest/getPlaylists.view", q)
j = decodeJSON(t, resp)
playlists := j["playlists"].(map[string]any)["playlist"].([]any)
if len(playlists) != 1 {
t.Fatalf("getPlaylists = %v, want 1", playlists)
}
// getPlaylist shows the one song
q2 := baseParams("alice", "password123")
q2.Set("id", playlistID)
q2.Set("f", "json")
resp = getPath(t, srv.URL, "/rest/getPlaylist.view", q2)
j = decodeJSON(t, resp)
got := j["playlist"].(map[string]any)
if entries, ok := got["entry"].([]any); !ok || len(entries) != 1 {
t.Fatalf("getPlaylist entries = %v, want 1", got["entry"])
}
// updatePlaylist: rename, add second song
q3 := baseParams("alice", "password123")
q3.Set("playlistId", playlistID)
q3.Set("name", "Renamed")
q3.Add("songIdToAdd", songs[1].ID)
resp = getPath(t, srv.URL, "/rest/updatePlaylist.view", q3)
x := decodeXML(t, resp)
if x.Status != "ok" {
t.Fatalf("updatePlaylist failed: %+v", x)
}
resp = getPath(t, srv.URL, "/rest/getPlaylist.view", q2)
j = decodeJSON(t, resp)
got = j["playlist"].(map[string]any)
if got["name"] != "Renamed" {
t.Fatalf("playlist name after update = %v, want Renamed", got["name"])
}
if entries, ok := got["entry"].([]any); !ok || len(entries) != 2 {
t.Fatalf("entries after add = %v, want 2", got["entry"])
}
// updatePlaylist: remove index 0
q4 := baseParams("alice", "password123")
q4.Set("playlistId", playlistID)
q4.Add("songIndexToRemove", "0")
getPath(t, srv.URL, "/rest/updatePlaylist.view", q4)
resp = getPath(t, srv.URL, "/rest/getPlaylist.view", q2)
j = decodeJSON(t, resp)
got = j["playlist"].(map[string]any)
if entries, ok := got["entry"].([]any); !ok || len(entries) != 1 {
t.Fatalf("entries after remove = %v, want 1", got["entry"])
}
// deletePlaylist
q5 := baseParams("alice", "password123")
q5.Set("id", playlistID)
resp = getPath(t, srv.URL, "/rest/deletePlaylist.view", q5)
x = decodeXML(t, resp)
if x.Status != "ok" {
t.Fatalf("deletePlaylist failed: %+v", x)
}
resp = getPath(t, srv.URL, "/rest/getPlaylists.view", q)
j = decodeJSON(t, resp)
pls := j["playlists"].(map[string]any)
if _, ok := pls["playlist"]; ok {
t.Fatalf("expected no playlists after delete, got %v", pls)
}
}
func TestPlaylistPermissions(t *testing.T) {
s, db := newTestServer(t)
owner := createTestUser(t, s, "owner", "password123", false)
createTestUser(t, s, "other", "password123", false)
srv := httptest.NewServer(s.Router())
defer srv.Close()
p, err := database.CreatePlaylist(db, owner.ID, "Private", "", false)
if err != nil {
t.Fatal(err)
}
q := baseParams("other", "password123")
q.Set("id", p.ID)
resp := getPath(t, srv.URL, "/rest/getPlaylist.view", q)
x := decodeXML(t, resp)
if x.Status != "failed" || x.Error == nil || x.Error.Code != ErrUnauthorized {
t.Fatalf("expected error 50 accessing another user's private playlist, got %+v", x)
}
resp = getPath(t, srv.URL, "/rest/deletePlaylist.view", q)
x = decodeXML(t, resp)
if x.Status != "failed" || x.Error == nil || x.Error.Code != ErrUnauthorized {
t.Fatalf("expected error 50 deleting another user's playlist, got %+v", x)
}
}
func TestGetPlaylistNotFound(t *testing.T) {
s, srv := newTestRecorderServer(t)
createTestUser(t, s, "alice", "password123", false)
q := baseParams("alice", "password123")
q.Set("id", "pl_doesnotexist")
resp := getPath(t, srv.URL, "/rest/getPlaylist.view", q)
x := decodeXML(t, resp)
if x.Status != "failed" || x.Error == nil || x.Error.Code != ErrNotFound {
t.Fatalf("expected error 70, got %+v", x)
}
}