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) } }