Files
radio-go/internal/domain/playlist_test.go
T

118 lines
2.9 KiB
Go

package domain
import (
"testing"
"time"
)
func mkTrack(id string, created time.Time, rating int) Track {
return Track{
ID: id,
Genre: "synthwave",
Title: "Song " + id,
Style: "dreamy synth",
Source: "generated",
CreatedAt: UnixTime{created},
Rating: rating,
}
}
func TestExcludesDisliked(t *testing.T) {
now := time.Now()
tracks := []Track{
mkTrack("a", now.Add(-3*time.Hour), 0),
mkTrack("b", now.Add(-2*time.Hour), -1), // disliked
mkTrack("c", now.Add(-1*time.Hour), 1),
}
got := FilterPlayable(tracks)
if len(got) != 2 {
t.Fatalf("want 2 playable, got %d", len(got))
}
for _, tr := range got {
if tr.ID == "b" {
t.Fatal("disliked track b must be excluded")
}
}
}
func TestRollingPlaylistSizeAndNoDupes(t *testing.T) {
now := time.Now()
var tracks []Track
for i := 0; i < 20; i++ {
tracks = append(tracks, mkTrack(string(rune('a'+i)), now.Add(time.Duration(i)*time.Minute), 0))
}
pl := BuildPlaylist(tracks, 8, 3, time.Now().Unix())
if len(pl) != 8 {
t.Fatalf("want 8, got %d", len(pl))
}
seen := map[string]bool{}
for _, tr := range pl {
if seen[tr.ID] {
t.Fatalf("duplicate %s in playlist", tr.ID)
}
seen[tr.ID] = true
}
}
func TestNewnessBiasPrefersNewest(t *testing.T) {
now := time.Now()
var tracks []Track
for i := 0; i < 50; i++ {
tracks = append(tracks, mkTrack(idFor(i), now.Add(-time.Duration(50-i)*time.Minute), 0))
}
// strong bias: bias=1 means newest rank0 weight=1, rank49 weight=1/50
newestCount := 0
const runs = 200
for r := 0; r < runs; r++ {
pl := BuildPlaylist(tracks, 5, 1, int64(r*7919))
for _, tr := range pl {
if tr.ID == idFor(49) { // the newest
newestCount++
}
}
}
// with strong bias the newest track should appear in most playlists
if newestCount < runs/2 {
t.Fatalf("newest track appeared in only %d/%d playlists — bias not working", newestCount, runs)
}
}
func TestUniformWhenBiasHigh(t *testing.T) {
// bias >> pool size => near-uniform; just sanity check no panic and full size
now := time.Now()
var tracks []Track
for i := 0; i < 10; i++ {
tracks = append(tracks, mkTrack(idFor(i), now.Add(-time.Duration(10-i)*time.Minute), 0))
}
pl := BuildPlaylist(tracks, 10, 1000, 42)
if len(pl) != 10 {
t.Fatalf("want 10, got %d", len(pl))
}
}
func TestEmptyAndSmallPool(t *testing.T) {
if pl := BuildPlaylist(nil, 8, 3, 1); len(pl) != 0 {
t.Fatal("empty pool must give empty playlist")
}
now := time.Now()
pl := BuildPlaylist([]Track{mkTrack("x", now, 0)}, 8, 3, 1)
if len(pl) != 1 {
t.Fatalf("pool of 1 should give playlist of 1, got %d", len(pl))
}
}
func TestTitleForDisplay(t *testing.T) {
tr := mkTrack("t1", time.Now(), 0)
if got := tr.DisplayTitle(); got != "Song t1" {
t.Fatalf("DisplayTitle = %q", got)
}
tr.Title = ""
if got := tr.DisplayTitle(); got != tr.Style {
t.Fatalf("fallback DisplayTitle = %q, want style %q", got, tr.Style)
}
}
func idFor(i int) string {
return "id" + string(rune('A'+i%26)) + string(rune('a'+i/26))
}