feat(subsonic): add getStarred and getStarred2 handlers
Both return user's starred artists/albums/songs sorted liked_at DESC. Cap at 500 entries per category for v1 (Subsonic spec doesn't define pagination on these endpoints; M3+ can revisit if needed).
This commit is contained in:
@@ -2,6 +2,7 @@ package subsonic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
@@ -190,3 +191,61 @@ func contains(s, substr string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func TestHandleGetStarred2_ReturnsAllThreeArrays(t *testing.T) {
|
||||
pool, user, track, album, artist := testStarPool(t)
|
||||
m := newStarHandlers(pool)
|
||||
q := dbq.New(pool)
|
||||
|
||||
// Star one of each.
|
||||
_ = q.LikeTrack(context.Background(), dbq.LikeTrackParams{UserID: user.ID, TrackID: track.ID})
|
||||
_ = q.LikeAlbum(context.Background(), dbq.LikeAlbumParams{UserID: user.ID, AlbumID: album.ID})
|
||||
_ = q.LikeArtist(context.Background(), dbq.LikeArtistParams{UserID: user.ID, ArtistID: artist.ID})
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/rest/getStarred2?f=json", nil)
|
||||
ctx := context.WithValue(req.Context(), userCtxKey, user)
|
||||
w := httptest.NewRecorder()
|
||||
m.handleGetStarred2(w, req.WithContext(ctx))
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d body=%s", w.Code, w.Body.String())
|
||||
}
|
||||
// Subsonic JSON envelope: {"subsonic-response":{...,"starred2":{...}}}
|
||||
var raw map[string]any
|
||||
_ = json.Unmarshal(w.Body.Bytes(), &raw)
|
||||
resp, _ := raw["subsonic-response"].(map[string]any)
|
||||
starred, _ := resp["starred2"].(map[string]any)
|
||||
if starred == nil {
|
||||
t.Fatalf("missing starred2 envelope: %v", raw)
|
||||
}
|
||||
songs, _ := starred["song"].([]any)
|
||||
albums, _ := starred["album"].([]any)
|
||||
artists, _ := starred["artist"].([]any)
|
||||
if len(songs) != 1 || len(albums) != 1 || len(artists) != 1 {
|
||||
t.Errorf("counts: songs=%d albums=%d artists=%d", len(songs), len(albums), len(artists))
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleGetStarred2_CrossUserIsolation(t *testing.T) {
|
||||
pool, alice, track, _, _ := testStarPool(t)
|
||||
q := dbq.New(pool)
|
||||
bob, _ := q.CreateUser(context.Background(), dbq.CreateUserParams{
|
||||
Username: "bob", PasswordHash: "x", ApiToken: "y", IsAdmin: false,
|
||||
})
|
||||
_ = q.LikeTrack(context.Background(), dbq.LikeTrackParams{UserID: alice.ID, TrackID: track.ID})
|
||||
|
||||
m := newStarHandlers(pool)
|
||||
req := httptest.NewRequest(http.MethodGet, "/rest/getStarred2?f=json", nil)
|
||||
ctx := context.WithValue(req.Context(), userCtxKey, bob)
|
||||
w := httptest.NewRecorder()
|
||||
m.handleGetStarred2(w, req.WithContext(ctx))
|
||||
|
||||
var raw map[string]any
|
||||
_ = json.Unmarshal(w.Body.Bytes(), &raw)
|
||||
resp, _ := raw["subsonic-response"].(map[string]any)
|
||||
starred, _ := resp["starred2"].(map[string]any)
|
||||
songs, _ := starred["song"].([]any)
|
||||
if len(songs) != 0 {
|
||||
t.Errorf("bob's starred songs should be empty, got %d", len(songs))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user