OpenSubsonic: playbackReport extension (reportPlayback), integrated with now-playing/history
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user