Files
minstrel/internal/playlists/system_mixes_test.go
T
bvandeusenandClaude Opus 5.5 fcf5b7e5a8 docs: wanted, dbtest, recommendation and playlists comments say what the code does (#6277)
The same rewrite as d191a282, for four packages whose comments carried the
most history: tuning stories, quotes from the operator, single-install
measurements in library_scale.go, and 'used to' accounts of bugs. The
constraint each story protected stays as a present-tense reason (why tier 3
is reduced not removed, why the randomised arms take an order seed, why the
seed artist is eligible in Songs-like). Comments naming system_cron.go and
config.RecommendationConfig, neither of which holds what they claimed, now
name the scheduler and internal/recsettings.

Checked: non-comment tokens identical to HEAD; gofmt clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 12:19:15 -04:00

105 lines
3.3 KiB
Go

package playlists
import (
"testing"
"github.com/jackc/pgx/v5/pgtype"
)
func mixTrack(b byte, kind string) discoverTrack {
return discoverTrack{ID: pgtype.UUID{Bytes: [16]byte{b}, Valid: true}, PickKind: kind}
}
func idsOf(pool []discoverTrack) []byte {
out := make([]byte, len(pool))
for i, t := range pool {
out[i] = t.ID.Bytes[0]
}
return out
}
func TestRotateForDay_RotatesWithinTierBlocks(t *testing.T) {
// Tiered pools arrive tier-ordered; rotation must vary the daily
// slice WITHIN each tier without hoisting tier-3 filler above
// tier-1's exact fits.
pool := []discoverTrack{
mixTrack(1, pickKindTier1), mixTrack(2, pickKindTier1), mixTrack(3, pickKindTier1),
mixTrack(4, pickKindTier2), mixTrack(5, pickKindTier2),
mixTrack(6, pickKindTier3),
}
u := pgtype.UUID{Bytes: [16]byte{42}, Valid: true}
got := rotateForDay(pool, u, "2026-07-03")
if len(got) != len(pool) {
t.Fatalf("len = %d, want %d", len(got), len(pool))
}
wantKinds := []string{
pickKindTier1, pickKindTier1, pickKindTier1,
pickKindTier2, pickKindTier2, pickKindTier3,
}
for i, k := range wantKinds {
if got[i].PickKind != k {
t.Fatalf("pos %d kind = %q, want %q (tier order broken: %v)",
i, got[i].PickKind, k, idsOf(got))
}
}
// Each block is a rotation of its input: contiguity check for the
// 3-element tier-1 block (successor relation preserved cyclically).
wantOrder := map[byte]byte{1: 2, 2: 3, 3: 1}
for i := 0; i < 2; i++ {
if got[i+1].ID.Bytes[0] != wantOrder[got[i].ID.Bytes[0]] {
t.Errorf("tier1 block is not a rotation: %v", idsOf(got[:3]))
break
}
}
// Determinism within a day.
again := rotateForDay(pool, u, "2026-07-03")
for i := range got {
if got[i].ID != again[i].ID {
t.Fatal("rotation must be deterministic for the same (user, day)")
}
}
}
func TestRotateForDay_UntieredPoolIsWholeRotation(t *testing.T) {
// Untiered pools (all PickKind "") are one block — the original
// whole-pool rotation semantics.
pool := []discoverTrack{mixTrack(1, ""), mixTrack(2, ""), mixTrack(3, ""), mixTrack(4, "")}
got := rotateForDay(pool, pgtype.UUID{Bytes: [16]byte{7}, Valid: true}, "2026-07-03")
if len(got) != 4 {
t.Fatalf("len = %d, want 4", len(got))
}
wantNext := map[byte]byte{1: 2, 2: 3, 3: 4, 4: 1}
for i := 0; i < 3; i++ {
cur, next := got[i].ID.Bytes[0], got[i+1].ID.Bytes[0]
if wantNext[cur] != next {
t.Errorf("not a rotation of the input: %v", idsOf(got))
break
}
}
}
func TestFinishMix_PropagatesPickKind(t *testing.T) {
// Tier stamps ride discoverTrack through diversify/truncate into
// the rankedCandidates that insertSystemPlaylist persists.
pool := []discoverTrack{mixTrack(1, pickKindTier1), mixTrack(2, pickKindTier2)}
got := finishMix(pool, false)
if len(got) != 2 {
t.Fatalf("len = %d, want 2", len(got))
}
if got[0].PickKind != pickKindTier1 || got[1].PickKind != pickKindTier2 {
t.Errorf("pick kinds = %q, %q — want tier1, tier2", got[0].PickKind, got[1].PickKind)
}
}
func TestPickKindForMixTier(t *testing.T) {
cases := []struct {
tier int32
want string
}{{1, pickKindTier1}, {2, pickKindTier2}, {3, pickKindTier3}, {9, pickKindTier3}}
for _, c := range cases {
if got := pickKindForMixTier(c.tier); got != c.want {
t.Errorf("pickKindForMixTier(%d) = %q, want %q", c.tier, got, c.want)
}
}
}