Files
wehub-resource-sync ead81af521
Deploy to GitHub Pages / deploy (push) Failing after 0s
CI / go (push) Has been cancelled
CI / build (darwin, macos-14) (push) Has been cancelled
CI / build (windows, windows-2025) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:31:13 +08:00

254 lines
6.5 KiB
Go

package playlist
import (
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
"github.com/dhowden/tag"
)
func TestTrackMeta(t *testing.T) {
t.Run("nil map returns empty", func(t *testing.T) {
tr := Track{Title: "Test"}
if got := tr.Meta("navidrome.id"); got != "" {
t.Errorf("Meta on nil map = %q, want empty", got)
}
})
t.Run("existing key", func(t *testing.T) {
tr := Track{
Title: "Test",
ProviderMeta: map[string]string{"navidrome.id": "abc123"},
}
if got := tr.Meta("navidrome.id"); got != "abc123" {
t.Errorf("Meta = %q, want %q", got, "abc123")
}
})
t.Run("missing key", func(t *testing.T) {
tr := Track{
Title: "Test",
ProviderMeta: map[string]string{"navidrome.id": "abc123"},
}
if got := tr.Meta("jellyfin.id"); got != "" {
t.Errorf("Meta = %q, want empty", got)
}
})
}
func TestTrackDisplayName(t *testing.T) {
tests := []struct {
name string
track Track
want string
}{
{
name: "artist and title",
track: Track{Artist: "Radiohead", Title: "Creep"},
want: "Radiohead - Creep",
},
{
name: "title only",
track: Track{Title: "Unknown Song"},
want: "Unknown Song",
},
{
name: "empty",
track: Track{},
want: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.track.DisplayName(); got != tt.want {
t.Errorf("DisplayName() = %q, want %q", got, tt.want)
}
})
}
}
func TestTrackIsLive(t *testing.T) {
t.Run("realtime", func(t *testing.T) {
tr := Track{Realtime: true}
if !tr.IsLive() {
t.Error("IsLive() = false, want true")
}
})
t.Run("not realtime", func(t *testing.T) {
tr := Track{Realtime: false}
if tr.IsLive() {
t.Error("IsLive() = true, want false")
}
})
}
func TestFileURL(t *testing.T) {
got := fileURL(filepath.Join("tmp", "cover art.jpg"))
if !strings.HasPrefix(got, "file:///") {
t.Fatalf("fileURL = %q, want file URL", got)
}
if runtime.GOOS != "windows" && !strings.Contains(got, "cover%20art.jpg") {
t.Fatalf("fileURL = %q, want escaped spaces", got)
}
}
func TestCacheAlbumArtUsesContentHash(t *testing.T) {
home := t.TempDir()
t.Setenv("HOME", home)
first := cacheAlbumArt(&tag.Picture{Ext: "jpeg", Data: []byte("same cover")})
second := cacheAlbumArt(&tag.Picture{Ext: "jpg", Data: []byte("same cover")})
if first == "" || first != second {
t.Fatalf("cacheAlbumArt URLs = %q and %q, want same non-empty URL", first, second)
}
matches, err := filepath.Glob(filepath.Join(home, ".local", "share", "cliamp", albumArtCacheDir, "*"))
if err != nil {
t.Fatalf("Glob: %v", err)
}
if len(matches) != 1 {
t.Fatalf("cached files = %d, want 1: %v", len(matches), matches)
}
}
func TestCleanupAlbumArtCacheKeepsCurrentFile(t *testing.T) {
dir := t.TempDir()
oldPath := filepath.Join(dir, "old.jpg")
keepPath := filepath.Join(dir, "keep.jpg")
if err := os.WriteFile(oldPath, []byte(strings.Repeat("o", 80)), 0o644); err != nil {
t.Fatalf("WriteFile old: %v", err)
}
if err := os.WriteFile(keepPath, []byte(strings.Repeat("k", 80)), 0o644); err != nil {
t.Fatalf("WriteFile keep: %v", err)
}
oldTime := time.Now().Add(-time.Hour)
if err := os.Chtimes(oldPath, oldTime, oldTime); err != nil {
t.Fatalf("Chtimes old: %v", err)
}
cleanupAlbumArtCache(dir, 100, keepPath)
if _, err := os.Stat(oldPath); !os.IsNotExist(err) {
t.Fatalf("old cache file still exists or stat failed: %v", err)
}
if _, err := os.Stat(keepPath); err != nil {
t.Fatalf("current cache file was removed: %v", err)
}
}
func TestRefreshEmbeddedMetadataPreservesSavedFieldsWhenUnreadable(t *testing.T) {
track := Track{
Path: filepath.Join(t.TempDir(), "missing.mp3"),
Title: "Saved title",
Artist: "Saved artist",
EmbeddedLyrics: "old lyrics",
AlbumArtURL: "file:///old.jpg",
}
got := RefreshEmbeddedMetadata(track)
if got.EmbeddedLyrics != track.EmbeddedLyrics || got.AlbumArtURL != track.AlbumArtURL {
t.Fatalf("embedded metadata changed: got %q, %q want %q, %q", got.EmbeddedLyrics, got.AlbumArtURL, track.EmbeddedLyrics, track.AlbumArtURL)
}
if got.Title != track.Title || got.Artist != track.Artist {
t.Fatalf("non-embedded fields changed: got %+v want title/artist from %+v", got, track)
}
}
func TestRefreshEmbeddedMetadataClearsSavedFieldsWhenTagsUnreadable(t *testing.T) {
path := filepath.Join(t.TempDir(), "empty.mp3")
if err := os.WriteFile(path, nil, 0o644); err != nil {
t.Fatalf("WriteFile: %v", err)
}
track := Track{
Path: path,
Title: "Saved title",
EmbeddedLyrics: "old lyrics",
AlbumArtURL: "file:///old.jpg",
}
got := RefreshEmbeddedMetadata(track)
if got.EmbeddedLyrics != "" || got.AlbumArtURL != "" {
t.Fatalf("embedded metadata = %q, %q; want empty refreshed fields", got.EmbeddedLyrics, got.AlbumArtURL)
}
if got.Title != track.Title {
t.Fatalf("title changed: got %q want %q", got.Title, track.Title)
}
}
func TestTrackFromURL(t *testing.T) {
tests := []struct {
name string
url string
wantTtl string
stream bool
}{
{
name: "with filename",
url: "https://example.com/music/song.mp3",
wantTtl: "song",
stream: true,
},
{
name: "stream path fallback to hostname",
url: "https://radio.example.com/stream",
wantTtl: "radio.example.com",
stream: true,
},
{
name: "rest path fallback to hostname",
url: "https://api.example.com/rest",
wantTtl: "api.example.com",
stream: true,
},
{
name: "query params ignored",
url: "https://example.com/song.mp3?token=abc",
wantTtl: "song",
stream: true,
},
{
name: "root path fallback to hostname",
url: "https://radio.example.com/",
wantTtl: "radio.example.com",
stream: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tr := TrackFromPath(tt.url)
if tr.Title != tt.wantTtl {
t.Errorf("Title = %q, want %q", tr.Title, tt.wantTtl)
}
if tr.Stream != tt.stream {
t.Errorf("Stream = %v, want %v", tr.Stream, tt.stream)
}
if tr.Path != tt.url {
t.Errorf("Path = %q, want %q", tr.Path, tt.url)
}
})
}
}
func TestRepeatModeString(t *testing.T) {
tests := []struct {
mode RepeatMode
want string
}{
{RepeatOff, "Off"},
{RepeatAll, "All"},
{RepeatOne, "One"},
{RepeatMode(99), "Off"}, // unknown defaults to "Off"
}
for _, tt := range tests {
t.Run(tt.want, func(t *testing.T) {
if got := tt.mode.String(); got != tt.want {
t.Errorf("String() = %q, want %q", got, tt.want)
}
})
}
}