Subsonic: internet radio, chat, bookmarks, play queue, scanner status endpoints
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
package subsonic
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestInternetRadioLifecycle(t *testing.T) {
|
||||
s, srv := newTestRecorderServer(t)
|
||||
createTestUser(t, s, "admin", "password123", true)
|
||||
|
||||
q := baseParams("admin", "password123")
|
||||
q.Set("name", "Test Station")
|
||||
q.Set("streamUrl", "http://example.com/stream.mp3")
|
||||
resp := getPath(t, srv.URL, "/rest/createInternetRadioStation.view", q)
|
||||
x := decodeXML(t, resp)
|
||||
if x.Status != "ok" {
|
||||
t.Fatalf("createInternetRadioStation failed: %+v", x)
|
||||
}
|
||||
|
||||
q2 := baseParams("admin", "password123")
|
||||
q2.Set("f", "json")
|
||||
resp = getPath(t, srv.URL, "/rest/getInternetRadioStations.view", q2)
|
||||
j := decodeJSON(t, resp)
|
||||
stations := j["internetRadioStations"].(map[string]any)["internetRadioStation"].([]any)
|
||||
if len(stations) != 1 {
|
||||
t.Fatalf("stations = %v, want 1", stations)
|
||||
}
|
||||
stationID := stations[0].(map[string]any)["id"].(string)
|
||||
|
||||
q3 := baseParams("admin", "password123")
|
||||
q3.Set("id", stationID)
|
||||
resp = getPath(t, srv.URL, "/rest/deleteInternetRadioStation.view", q3)
|
||||
x = decodeXML(t, resp)
|
||||
if x.Status != "ok" {
|
||||
t.Fatalf("deleteInternetRadioStation failed: %+v", x)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInternetRadioRequiresAdmin(t *testing.T) {
|
||||
s, srv := newTestRecorderServer(t)
|
||||
createTestUser(t, s, "alice", "password123", false)
|
||||
|
||||
q := baseParams("alice", "password123")
|
||||
q.Set("name", "X")
|
||||
q.Set("streamUrl", "http://example.com/x.mp3")
|
||||
resp := getPath(t, srv.URL, "/rest/createInternetRadioStation.view", q)
|
||||
x := decodeXML(t, resp)
|
||||
if x.Status != "failed" || x.Error == nil || x.Error.Code != ErrUnauthorized {
|
||||
t.Fatalf("expected error 50 for non-admin, got %+v", x)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChatMessages(t *testing.T) {
|
||||
s, srv := newTestRecorderServer(t)
|
||||
createTestUser(t, s, "alice", "password123", false)
|
||||
|
||||
q := baseParams("alice", "password123")
|
||||
q.Set("message", "hello world")
|
||||
resp := getPath(t, srv.URL, "/rest/addChatMessage.view", q)
|
||||
x := decodeXML(t, resp)
|
||||
if x.Status != "ok" {
|
||||
t.Fatalf("addChatMessage failed: %+v", x)
|
||||
}
|
||||
|
||||
q2 := baseParams("alice", "password123")
|
||||
q2.Set("f", "json")
|
||||
resp = getPath(t, srv.URL, "/rest/getChatMessages.view", q2)
|
||||
j := decodeJSON(t, resp)
|
||||
messages := j["chatMessages"].(map[string]any)["chatMessage"].([]any)
|
||||
if len(messages) != 1 {
|
||||
t.Fatalf("messages = %v, want 1", messages)
|
||||
}
|
||||
msg := messages[0].(map[string]any)
|
||||
if msg["message"] != "hello world" || msg["username"] != "alice" {
|
||||
t.Fatalf("message = %v, want hello world from alice", msg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBookmarkLifecycle(t *testing.T) {
|
||||
s, srv := newTestRecorderServer(t)
|
||||
createTestUser(t, s, "alice", "password123", false)
|
||||
seedLibrary(t, s)
|
||||
song := firstSong(t, s)
|
||||
|
||||
q := baseParams("alice", "password123")
|
||||
q.Set("id", song.ID)
|
||||
q.Set("position", "5000")
|
||||
q.Set("comment", "resume here")
|
||||
resp := getPath(t, srv.URL, "/rest/createBookmark.view", q)
|
||||
x := decodeXML(t, resp)
|
||||
if x.Status != "ok" {
|
||||
t.Fatalf("createBookmark failed: %+v", x)
|
||||
}
|
||||
|
||||
q2 := baseParams("alice", "password123")
|
||||
q2.Set("f", "json")
|
||||
resp = getPath(t, srv.URL, "/rest/getBookmarks.view", q2)
|
||||
j := decodeJSON(t, resp)
|
||||
bookmarks := j["bookmarks"].(map[string]any)["bookmark"].([]any)
|
||||
if len(bookmarks) != 1 {
|
||||
t.Fatalf("bookmarks = %v, want 1", bookmarks)
|
||||
}
|
||||
bm := bookmarks[0].(map[string]any)
|
||||
if bm["comment"] != "resume here" {
|
||||
t.Fatalf("bookmark comment = %v", bm["comment"])
|
||||
}
|
||||
|
||||
q3 := baseParams("alice", "password123")
|
||||
q3.Set("id", song.ID)
|
||||
resp = getPath(t, srv.URL, "/rest/deleteBookmark.view", q3)
|
||||
x = decodeXML(t, resp)
|
||||
if x.Status != "ok" {
|
||||
t.Fatalf("deleteBookmark failed: %+v", x)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlayQueueRoundTrip(t *testing.T) {
|
||||
s, srv := newTestRecorderServer(t)
|
||||
createTestUser(t, s, "alice", "password123", false)
|
||||
seedLibrary(t, s)
|
||||
song := firstSong(t, s)
|
||||
|
||||
q := baseParams("alice", "password123")
|
||||
q.Add("id", song.ID)
|
||||
q.Set("current", song.ID)
|
||||
q.Set("position", "1234")
|
||||
resp := getPath(t, srv.URL, "/rest/savePlayQueue.view", q)
|
||||
x := decodeXML(t, resp)
|
||||
if x.Status != "ok" {
|
||||
t.Fatalf("savePlayQueue failed: %+v", x)
|
||||
}
|
||||
|
||||
q2 := baseParams("alice", "password123")
|
||||
q2.Set("f", "json")
|
||||
resp = getPath(t, srv.URL, "/rest/getPlayQueue.view", q2)
|
||||
j := decodeJSON(t, resp)
|
||||
pq := j["playQueue"].(map[string]any)
|
||||
if pq["current"] != song.ID {
|
||||
t.Fatalf("playQueue.current = %v, want %v", pq["current"], song.ID)
|
||||
}
|
||||
entries, ok := pq["entry"].([]any)
|
||||
if !ok || len(entries) != 1 {
|
||||
t.Fatalf("playQueue entries = %v, want 1", pq["entry"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPlayQueueEmpty(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/getPlayQueue.view", q)
|
||||
j := decodeJSON(t, resp)
|
||||
if j["status"] != "ok" {
|
||||
t.Fatalf("empty getPlayQueue should still succeed, got %v", j)
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanStatusAndStartScan(t *testing.T) {
|
||||
s, srv := newTestRecorderServer(t)
|
||||
createTestUser(t, s, "admin", "password123", true)
|
||||
seedLibrary(t, s)
|
||||
|
||||
q := baseParams("admin", "password123")
|
||||
q.Set("f", "json")
|
||||
resp := getPath(t, srv.URL, "/rest/getScanStatus.view", q)
|
||||
j := decodeJSON(t, resp)
|
||||
status := j["scanStatus"].(map[string]any)
|
||||
if status["scanning"] != false {
|
||||
t.Fatalf("scanning = %v, want false when idle", status["scanning"])
|
||||
}
|
||||
if count, ok := status["count"].(float64); !ok || count < 2 {
|
||||
t.Fatalf("count = %v, want >= 2 (seeded songs)", status["count"])
|
||||
}
|
||||
|
||||
resp = getPath(t, srv.URL, "/rest/startScan.view", q)
|
||||
j = decodeJSON(t, resp)
|
||||
if j["status"] != "ok" {
|
||||
t.Fatalf("startScan failed: %v", j)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartScanRequiresAdmin(t *testing.T) {
|
||||
s, srv := newTestRecorderServer(t)
|
||||
createTestUser(t, s, "alice", "password123", false)
|
||||
|
||||
resp := getPath(t, srv.URL, "/rest/startScan.view", baseParams("alice", "password123"))
|
||||
x := decodeXML(t, resp)
|
||||
if x.Status != "failed" || x.Error == nil || x.Error.Code != ErrUnauthorized {
|
||||
t.Fatalf("expected error 50 for non-admin startScan, got %+v", x)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user