package subsonic import ( "testing" "git.noroutes.net/NoRoutes/vibetunes/internal/database" ) func TestReportPlaybackPlaying(t *testing.T) { s, db := newTestServer(t) createTestUser(t, s, "alice", "password123", false) seedLibrary(t, s) song := firstSong(t, s) srv := newSubsonicTestServer(t, s) q := baseParams("alice", "password123") q.Set("mediaId", song.ID) q.Set("mediaType", "song") q.Set("positionMs", "5000") q.Set("state", "playing") resp := getPath(t, srv, "/rest/reportPlayback.view", q) x := decodeXML(t, resp) if x.Status != "ok" { t.Fatalf("reportPlayback failed: %+v", x) } nowPlaying, err := database.ListNowPlaying(db) if err != nil { t.Fatal(err) } if len(nowPlaying) != 1 || nowPlaying[0].SongID != song.ID { t.Fatalf("now playing = %v, want 1 entry for %s", nowPlaying, song.ID) } } func TestReportPlaybackStoppedClearsNowPlayingAndRecordsHistory(t *testing.T) { s, db := newTestServer(t) u := createTestUser(t, s, "alice", "password123", false) seedLibrary(t, s) song := firstSong(t, s) srv := newSubsonicTestServer(t, s) q := baseParams("alice", "password123") q.Set("mediaId", song.ID) q.Set("mediaType", "song") q.Set("positionMs", "1000") q.Set("state", "playing") getPath(t, srv, "/rest/reportPlayback.view", q) q2 := baseParams("alice", "password123") q2.Set("mediaId", song.ID) q2.Set("mediaType", "song") q2.Set("positionMs", "45000") q2.Set("state", "stopped") resp := getPath(t, srv, "/rest/reportPlayback.view", q2) x := decodeXML(t, resp) if x.Status != "ok" { t.Fatalf("reportPlayback stopped failed: %+v", x) } nowPlaying, err := database.ListNowPlaying(db) if err != nil { t.Fatal(err) } if len(nowPlaying) != 0 { t.Fatalf("now playing should be cleared after stopped, got %v", nowPlaying) } history, err := database.ListRecentlyPlayed(db, u.ID, 10) if err != nil { t.Fatal(err) } if len(history) != 1 { t.Fatalf("expected a playback_history entry after stopped, got %v", history) } } func TestReportPlaybackIgnoreScrobble(t *testing.T) { s, db := newTestServer(t) u := createTestUser(t, s, "alice", "password123", false) seedLibrary(t, s) song := firstSong(t, s) srv := newSubsonicTestServer(t, s) q := baseParams("alice", "password123") q.Set("mediaId", song.ID) q.Set("mediaType", "song") q.Set("positionMs", "45000") q.Set("state", "stopped") q.Set("ignoreScrobble", "true") getPath(t, srv, "/rest/reportPlayback.view", q) history, err := database.ListRecentlyPlayed(db, u.ID, 10) if err != nil { t.Fatal(err) } if len(history) != 0 { t.Fatalf("ignoreScrobble=true should not record history, got %v", history) } } func TestReportPlaybackInvalidState(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("mediaId", song.ID) q.Set("mediaType", "song") q.Set("positionMs", "1000") q.Set("state", "bogus") resp := getPath(t, srv.URL, "/rest/reportPlayback.view", q) x := decodeXML(t, resp) if x.Status != "failed" { t.Fatalf("expected failure for invalid state, got %+v", x) } } func TestReportPlaybackMissingParams(t *testing.T) { s, srv := newTestRecorderServer(t) createTestUser(t, s, "alice", "password123", false) resp := getPath(t, srv.URL, "/rest/reportPlayback.view", baseParams("alice", "password123")) x := decodeXML(t, resp) if x.Status != "failed" || x.Error == nil || x.Error.Code != ErrMissingParameter { t.Fatalf("expected error 10, got %+v", x) } } func TestReportPlaybackUnknownMedia(t *testing.T) { s, srv := newTestRecorderServer(t) createTestUser(t, s, "alice", "password123", false) q := baseParams("alice", "password123") q.Set("mediaId", "sg_doesnotexist") q.Set("mediaType", "song") q.Set("positionMs", "0") q.Set("state", "playing") resp := getPath(t, srv.URL, "/rest/reportPlayback.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) } } // TestReportPlaybackDoesNotLeakOtherUsersBookmarksOrQueue guards the // "never expose one user's private state to another" requirement: each // user's saved play queue is scoped by user, so alice's report never // becomes visible in bob's queue. func TestReportPlaybackDoesNotLeakAcrossUsers(t *testing.T) { s, srv := newTestRecorderServer(t) createTestUser(t, s, "alice", "password123", false) createTestUser(t, s, "bob", "password123", false) seedLibrary(t, s) song := firstSong(t, s) q := baseParams("alice", "password123") q.Set("mediaId", song.ID) q.Set("mediaType", "song") q.Set("positionMs", "1000") q.Set("state", "playing") getPath(t, srv.URL, "/rest/reportPlayback.view", q) q2 := baseParams("bob", "password123") q2.Set("f", "json") resp := getPath(t, srv.URL, "/rest/getPlayQueue.view", q2) j := decodeJSON(t, resp) if _, ok := j["playQueue"]; ok { t.Fatalf("bob should not see alice's queue/report state: %v", j) } }