OpenSubsonic: indexBasedQueue (getPlayQueueByIndex, savePlayQueueByIndex) with real duplicate-song support
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
package subsonic
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.noroutes.net/NoRoutes/vibetunes/internal/database"
|
||||
)
|
||||
|
||||
func TestPlayQueueByIndexRoundTrip(t *testing.T) {
|
||||
s, srv := newTestRecorderServer(t)
|
||||
createTestUser(t, s, "alice", "password123", false)
|
||||
seedLibrary(t, s)
|
||||
songs, err := allSongIDs(t, s)
|
||||
if err != nil || len(songs) < 2 {
|
||||
t.Fatalf("need >=2 songs, got %v %v", songs, err)
|
||||
}
|
||||
|
||||
q := baseParams("alice", "password123")
|
||||
q.Add("id", songs[0])
|
||||
q.Add("id", songs[1])
|
||||
q.Set("currentIndex", "1")
|
||||
q.Set("position", "5000")
|
||||
resp := getPath(t, srv.URL, "/rest/savePlayQueueByIndex.view", q)
|
||||
x := decodeXML(t, resp)
|
||||
if x.Status != "ok" {
|
||||
t.Fatalf("savePlayQueueByIndex failed: %+v", x)
|
||||
}
|
||||
|
||||
q2 := baseParams("alice", "password123")
|
||||
q2.Set("f", "json")
|
||||
resp = getPath(t, srv.URL, "/rest/getPlayQueueByIndex.view", q2)
|
||||
j := decodeJSON(t, resp)
|
||||
pq, ok := j["playQueueByIndex"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("playQueueByIndex missing: %v", j)
|
||||
}
|
||||
if int(pq["currentIndex"].(float64)) != 1 {
|
||||
t.Fatalf("currentIndex = %v, want 1", pq["currentIndex"])
|
||||
}
|
||||
entries, ok := pq["entry"].([]any)
|
||||
if !ok || len(entries) != 2 {
|
||||
t.Fatalf("entries = %v, want 2", pq["entry"])
|
||||
}
|
||||
}
|
||||
|
||||
// TestPlayQueueByIndexHandlesDuplicateSongs is the whole point of this
|
||||
// extension: the same song ID can appear twice, and currentIndex
|
||||
// unambiguously identifies which occurrence is playing — unlike the
|
||||
// classic getPlayQueue's "current" song-ID field, which can't
|
||||
// distinguish between two occurrences of the same song.
|
||||
func TestPlayQueueByIndexHandlesDuplicateSongs(t *testing.T) {
|
||||
s, srv := newTestRecorderServer(t)
|
||||
createTestUser(t, s, "alice", "password123", false)
|
||||
seedLibrary(t, s)
|
||||
songs, err := allSongIDs(t, s)
|
||||
if err != nil || len(songs) == 0 {
|
||||
t.Fatalf("need >=1 song, got %v %v", songs, err)
|
||||
}
|
||||
same := songs[0]
|
||||
|
||||
q := baseParams("alice", "password123")
|
||||
q.Add("id", same)
|
||||
q.Add("id", same)
|
||||
q.Add("id", same)
|
||||
q.Set("currentIndex", "2") // the third occurrence, not the first
|
||||
resp := getPath(t, srv.URL, "/rest/savePlayQueueByIndex.view", q)
|
||||
x := decodeXML(t, resp)
|
||||
if x.Status != "ok" {
|
||||
t.Fatalf("save failed: %+v", x)
|
||||
}
|
||||
|
||||
q2 := baseParams("alice", "password123")
|
||||
q2.Set("f", "json")
|
||||
resp = getPath(t, srv.URL, "/rest/getPlayQueueByIndex.view", q2)
|
||||
j := decodeJSON(t, resp)
|
||||
pq := j["playQueueByIndex"].(map[string]any)
|
||||
if int(pq["currentIndex"].(float64)) != 2 {
|
||||
t.Fatalf("currentIndex = %v, want 2 (third occurrence)", pq["currentIndex"])
|
||||
}
|
||||
entries := pq["entry"].([]any)
|
||||
if len(entries) != 3 {
|
||||
t.Fatalf("entries = %v, want 3 (duplicates preserved)", entries)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSavePlayQueueByIndexClears(t *testing.T) {
|
||||
s, srv := newTestRecorderServer(t)
|
||||
createTestUser(t, s, "alice", "password123", false)
|
||||
seedLibrary(t, s)
|
||||
songs, _ := allSongIDs(t, s)
|
||||
|
||||
q := baseParams("alice", "password123")
|
||||
q.Add("id", songs[0])
|
||||
q.Set("currentIndex", "0")
|
||||
getPath(t, srv.URL, "/rest/savePlayQueueByIndex.view", q)
|
||||
|
||||
clear := baseParams("alice", "password123")
|
||||
resp := getPath(t, srv.URL, "/rest/savePlayQueueByIndex.view", clear)
|
||||
x := decodeXML(t, resp)
|
||||
if x.Status != "ok" {
|
||||
t.Fatalf("clear failed: %+v", x)
|
||||
}
|
||||
|
||||
q2 := baseParams("alice", "password123")
|
||||
q2.Set("f", "json")
|
||||
resp = getPath(t, srv.URL, "/rest/getPlayQueueByIndex.view", q2)
|
||||
j := decodeJSON(t, resp)
|
||||
pq := j["playQueueByIndex"].(map[string]any)
|
||||
if _, ok := pq["entry"]; ok {
|
||||
t.Fatalf("expected empty queue after clearing, got %v", pq)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSavePlayQueueByIndexMissingCurrentIndex(t *testing.T) {
|
||||
s, srv := newTestRecorderServer(t)
|
||||
createTestUser(t, s, "alice", "password123", false)
|
||||
seedLibrary(t, s)
|
||||
songs, _ := allSongIDs(t, s)
|
||||
|
||||
q := baseParams("alice", "password123")
|
||||
q.Add("id", songs[0])
|
||||
resp := getPath(t, srv.URL, "/rest/savePlayQueueByIndex.view", q)
|
||||
x := decodeXML(t, resp)
|
||||
if x.Status != "failed" || x.Error == nil || x.Error.Code != ErrMissingParameter {
|
||||
t.Fatalf("expected error 10 when ids given without currentIndex, got %+v", x)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPlayQueueByIndexEmpty(t *testing.T) {
|
||||
s, srv := newTestRecorderServer(t)
|
||||
createTestUser(t, s, "alice", "password123", false)
|
||||
|
||||
q := baseParams("alice", "password123")
|
||||
q.Set("f", "json")
|
||||
resp := getPath(t, srv.URL, "/rest/getPlayQueueByIndex.view", q)
|
||||
j := decodeJSON(t, resp)
|
||||
if j["status"] != "ok" {
|
||||
t.Fatalf("status = %v", j["status"])
|
||||
}
|
||||
}
|
||||
|
||||
func allSongIDs(t *testing.T, s *Server) ([]string, error) {
|
||||
t.Helper()
|
||||
songs, err := database.ListSongs(s.DB, 10, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ids := make([]string, len(songs))
|
||||
for i, song := range songs {
|
||||
ids[i] = song.ID
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
Reference in New Issue
Block a user