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

233 lines
5.7 KiB
Go

package resolve
import (
"path/filepath"
"strings"
"testing"
)
func TestParseM3UBasic(t *testing.T) {
input := `#EXTM3U
#EXTINF:120,Artist - Song One
http://example.com/song1.mp3
#EXTINF:180,Artist - Song Two
http://example.com/song2.mp3
`
entries, err := parseM3U(strings.NewReader(input), "")
if err != nil {
t.Fatalf("parseM3U error: %v", err)
}
if len(entries) != 2 {
t.Fatalf("got %d entries, want 2", len(entries))
}
if entries[0].Title != "Artist - Song One" {
t.Errorf("entry[0].Title = %q, want %q", entries[0].Title, "Artist - Song One")
}
if entries[0].Duration != 120 {
t.Errorf("entry[0].Duration = %d, want 120", entries[0].Duration)
}
if entries[0].Path != "http://example.com/song1.mp3" {
t.Errorf("entry[0].Path = %q", entries[0].Path)
}
if entries[1].Title != "Artist - Song Two" {
t.Errorf("entry[1].Title = %q", entries[1].Title)
}
if entries[1].Duration != 180 {
t.Errorf("entry[1].Duration = %d, want 180", entries[1].Duration)
}
}
func TestParseM3UNoHeader(t *testing.T) {
// M3U without #EXTM3U header should still work
input := `http://example.com/stream1.mp3
http://example.com/stream2.mp3
`
entries, err := parseM3U(strings.NewReader(input), "")
if err != nil {
t.Fatalf("parseM3U error: %v", err)
}
if len(entries) != 2 {
t.Fatalf("got %d entries, want 2", len(entries))
}
if entries[0].Duration != -1 {
t.Errorf("bare entry Duration = %d, want -1", entries[0].Duration)
}
}
func TestParseM3UBOM(t *testing.T) {
// UTF-8 BOM prefix should be stripped
input := "\xef\xbb\xbf#EXTM3U\n#EXTINF:60,Song\nhttp://example.com/song.mp3\n"
entries, err := parseM3U(strings.NewReader(input), "")
if err != nil {
t.Fatalf("parseM3U error: %v", err)
}
if len(entries) != 1 {
t.Fatalf("got %d entries, want 1", len(entries))
}
if entries[0].Title != "Song" {
t.Errorf("Title = %q, want Song", entries[0].Title)
}
}
func TestParseM3USkipsComments(t *testing.T) {
input := `#EXTM3U
# This is a comment
#EXTINF:60,Song
http://example.com/song.mp3
#EXTVLCOPT:some-option
`
entries, err := parseM3U(strings.NewReader(input), "")
if err != nil {
t.Fatalf("parseM3U error: %v", err)
}
if len(entries) != 1 {
t.Fatalf("got %d entries, want 1", len(entries))
}
}
func TestParseM3URelativePaths(t *testing.T) {
input := `#EXTM3U
#EXTINF:60,Song
music/song.mp3
`
entries, err := parseM3U(strings.NewReader(input), "/home/user")
if err != nil {
t.Fatalf("parseM3U error: %v", err)
}
if len(entries) != 1 {
t.Fatalf("got %d entries, want 1", len(entries))
}
if entries[0].Path != "/home/user/music/song.mp3" {
t.Errorf("Path = %q, want /home/user/music/song.mp3", entries[0].Path)
}
}
func TestParseM3UAbsolutePaths(t *testing.T) {
input := "/absolute/path/song.mp3\n"
entries, err := parseM3U(strings.NewReader(input), "/home/user")
if err != nil {
t.Fatalf("parseM3U error: %v", err)
}
if entries[0].Path != "/absolute/path/song.mp3" {
t.Errorf("Path = %q, want /absolute/path/song.mp3", entries[0].Path)
}
}
func TestParseM3UEmpty(t *testing.T) {
entries, err := parseM3U(strings.NewReader(""), "")
if err != nil {
t.Fatalf("parseM3U error: %v", err)
}
if len(entries) != 0 {
t.Fatalf("got %d entries, want 0", len(entries))
}
}
func TestParseM3URadioStream(t *testing.T) {
// Radio stream with -1 duration
input := `#EXTM3U
#EXTINF:-1,Radio Station
http://radio.example.com/stream
`
entries, err := parseM3U(strings.NewReader(input), "")
if err != nil {
t.Fatalf("parseM3U error: %v", err)
}
if len(entries) != 1 {
t.Fatalf("got %d entries, want 1", len(entries))
}
if entries[0].Duration != -1 {
t.Errorf("Duration = %d, want -1", entries[0].Duration)
}
if entries[0].Title != "Radio Station" {
t.Errorf("Title = %q, want Radio Station", entries[0].Title)
}
}
func TestM3UEntryToTrackStream(t *testing.T) {
e := m3uEntry{
Path: "http://radio.example.com/stream",
Title: "Radio Station",
Duration: -1,
}
tr := m3uEntryToTrack(e)
if !tr.Stream {
t.Error("Stream should be true for URL")
}
if !tr.Realtime {
t.Error("Realtime should be true for stream with -1 duration")
}
if tr.DurationSecs != 0 {
t.Errorf("DurationSecs = %d, want 0 (negative clamped)", tr.DurationSecs)
}
if tr.Title != "Radio Station" {
t.Errorf("Title = %q, want Radio Station", tr.Title)
}
}
func TestM3UEntryToTrackFile(t *testing.T) {
e := m3uEntry{
Path: "/home/user/song.mp3",
Title: "My Song",
Duration: 180,
}
tr := m3uEntryToTrack(e)
if tr.Stream {
t.Error("Stream should be false for local file")
}
if tr.Realtime {
t.Error("Realtime should be false for local file")
}
if tr.DurationSecs != 180 {
t.Errorf("DurationSecs = %d, want 180", tr.DurationSecs)
}
}
func TestResolveM3UPathWindows(t *testing.T) {
tests := []struct {
name string
baseDir string
entry string
want string
}{
{
name: "relative backslash",
baseDir: `C:\Music`,
entry: `artist\song.mp3`,
want: filepath.Clean(filepath.Join(`C:\Music`, filepath.FromSlash(`artist\song.mp3`))),
},
{
name: "relative forward slash",
baseDir: `C:\Music`,
entry: `artist/song.mp3`,
want: filepath.Clean(filepath.Join(`C:\Music`, filepath.FromSlash(`artist/song.mp3`))),
},
{
name: "absolute drive-letter",
baseDir: `C:\Music`,
entry: `D:\Other\track.mp3`,
want: filepath.Clean(`D:\Other\track.mp3`),
},
{
name: "absolute UNC path",
baseDir: `\\server\share`,
entry: `sub\file.mp3`,
want: filepath.Clean(filepath.Join(`\\server\share`, filepath.FromSlash(`sub\file.mp3`))),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := resolveM3UPath(tt.baseDir, tt.entry)
if got != tt.want {
t.Fatalf("resolveM3UPath(%q, %q) = %q, want %q", tt.baseDir, tt.entry, got, tt.want)
}
})
}
}