08591debee
Validates seed_track + optional limit (default cfg.Recommendation.RadioSize, clamped to RadioSizeMax). Calls recommendation.LoadCandidates + recommendation.Shuffle. Prepends seed to result. Server seeds a math/rand source at startup; handlers package threads that as a func() float64 so tests inject deterministic RNGs. Mount + server.New gain a RecommendationConfig parameter.
131 lines
4.0 KiB
Go
131 lines
4.0 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
)
|
|
|
|
func callRadio(h *handlers, user interface{}, query string) *httptest.ResponseRecorder {
|
|
req := httptest.NewRequest(http.MethodGet, "/api/radio?"+query, nil)
|
|
req = req.WithContext(context.WithValue(req.Context(), userCtxKeyForTest(), user))
|
|
w := httptest.NewRecorder()
|
|
h.handleRadio(w, req)
|
|
return w
|
|
}
|
|
|
|
func TestHandleRadio_ColdStart_OnlySeedReturned(t *testing.T) {
|
|
h, pool := testHandlers(t)
|
|
truncateLibrary(t, pool)
|
|
user := seedUser(t, pool, "alice", "x", false)
|
|
artist := seedArtist(t, pool, "X")
|
|
album := seedAlbum(t, pool, artist.ID, "X", 1990)
|
|
seed := seedTrack(t, pool, album.ID, artist.ID, "Seed", 1, 100_000)
|
|
|
|
w := callRadio(h, user, "seed_track="+uuidToString(seed.ID))
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("status = %d body=%s", w.Code, w.Body.String())
|
|
}
|
|
var resp RadioResponse
|
|
_ = json.Unmarshal(w.Body.Bytes(), &resp)
|
|
if len(resp.Tracks) != 1 {
|
|
t.Fatalf("len = %d, want 1", len(resp.Tracks))
|
|
}
|
|
if resp.Tracks[0].Title != "Seed" {
|
|
t.Errorf("seed not first: %v", resp.Tracks[0].Title)
|
|
}
|
|
}
|
|
|
|
func TestHandleRadio_Typical_SeedFirstPlusPicks(t *testing.T) {
|
|
h, pool := testHandlers(t)
|
|
truncateLibrary(t, pool)
|
|
user := seedUser(t, pool, "alice", "x", false)
|
|
artist := seedArtist(t, pool, "X")
|
|
album := seedAlbum(t, pool, artist.ID, "X", 1990)
|
|
seed := seedTrack(t, pool, album.ID, artist.ID, "Seed", 1, 100_000)
|
|
for i := 2; i <= 6; i++ {
|
|
seedTrack(t, pool, album.ID, artist.ID, "T"+string(rune('0'+i)), i, 100_000)
|
|
}
|
|
|
|
w := callRadio(h, user, "seed_track="+uuidToString(seed.ID))
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("status = %d", w.Code)
|
|
}
|
|
var resp RadioResponse
|
|
_ = json.Unmarshal(w.Body.Bytes(), &resp)
|
|
if len(resp.Tracks) != 6 {
|
|
t.Fatalf("len = %d, want 6 (seed + 5 picks)", len(resp.Tracks))
|
|
}
|
|
if resp.Tracks[0].Title != "Seed" {
|
|
t.Errorf("seed not at index 0: %v", resp.Tracks[0].Title)
|
|
}
|
|
}
|
|
|
|
func TestHandleRadio_UnknownSeedIs404(t *testing.T) {
|
|
h, pool := testHandlers(t)
|
|
user := seedUser(t, pool, "alice", "x", false)
|
|
w := callRadio(h, user, "seed_track=00000000-0000-0000-0000-000000000000")
|
|
if w.Code != http.StatusNotFound {
|
|
t.Errorf("status = %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestHandleRadio_BadSeedIs400(t *testing.T) {
|
|
h, pool := testHandlers(t)
|
|
user := seedUser(t, pool, "alice", "x", false)
|
|
w := callRadio(h, user, "seed_track=not-a-uuid")
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("status = %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestHandleRadio_MissingSeedIs400(t *testing.T) {
|
|
h, pool := testHandlers(t)
|
|
user := seedUser(t, pool, "alice", "x", false)
|
|
w := callRadio(h, user, "")
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("status = %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestHandleRadio_BadLimitIs400(t *testing.T) {
|
|
h, pool := testHandlers(t)
|
|
truncateLibrary(t, pool)
|
|
user := seedUser(t, pool, "alice", "x", false)
|
|
artist := seedArtist(t, pool, "X")
|
|
album := seedAlbum(t, pool, artist.ID, "X", 1990)
|
|
seed := seedTrack(t, pool, album.ID, artist.ID, "Seed", 1, 100_000)
|
|
w := callRadio(h, user, "seed_track="+uuidToString(seed.ID)+"&limit=0")
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("limit=0 status = %d", w.Code)
|
|
}
|
|
w = callRadio(h, user, "seed_track="+uuidToString(seed.ID)+"&limit=-1")
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("limit=-1 status = %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestHandleRadio_LimitClampedToMax(t *testing.T) {
|
|
h, pool := testHandlers(t)
|
|
truncateLibrary(t, pool)
|
|
user := seedUser(t, pool, "alice", "x", false)
|
|
artist := seedArtist(t, pool, "X")
|
|
album := seedAlbum(t, pool, artist.ID, "X", 1990)
|
|
seed := seedTrack(t, pool, album.ID, artist.ID, "Seed", 1, 100_000)
|
|
for i := 2; i <= 6; i++ {
|
|
seedTrack(t, pool, album.ID, artist.ID, "T"+string(rune('0'+i)), i, 100_000)
|
|
}
|
|
w := callRadio(h, user, "seed_track="+uuidToString(seed.ID)+"&limit=99999")
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("status = %d", w.Code)
|
|
}
|
|
var resp RadioResponse
|
|
_ = json.Unmarshal(w.Body.Bytes(), &resp)
|
|
// We only have 6 tracks; clamped limit (max 200) returns all 6.
|
|
if len(resp.Tracks) != 6 {
|
|
t.Errorf("len = %d, want 6", len(resp.Tracks))
|
|
}
|
|
}
|