258 lines
6.6 KiB
Go
258 lines
6.6 KiB
Go
package resolve
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestCollectAudioFilesSingleFile(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "song.mp3")
|
|
if err := os.WriteFile(path, []byte(""), 0o644); err != nil {
|
|
t.Fatalf("WriteFile: %v", err)
|
|
}
|
|
|
|
got, err := CollectAudioFiles(path)
|
|
if err != nil {
|
|
t.Fatalf("CollectAudioFiles: %v", err)
|
|
}
|
|
if len(got) != 1 || got[0] != path {
|
|
t.Errorf("got %v, want [%s]", got, path)
|
|
}
|
|
}
|
|
|
|
func TestCollectAudioFilesUnsupportedExt(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "notes.txt")
|
|
if err := os.WriteFile(path, []byte(""), 0o644); err != nil {
|
|
t.Fatalf("WriteFile: %v", err)
|
|
}
|
|
|
|
got, err := CollectAudioFiles(path)
|
|
if err != nil {
|
|
t.Fatalf("CollectAudioFiles: %v", err)
|
|
}
|
|
if got != nil {
|
|
t.Errorf("non-audio file should return nil, got %v", got)
|
|
}
|
|
}
|
|
|
|
func TestCollectAudioFilesDirectoryWalk(t *testing.T) {
|
|
dir := t.TempDir()
|
|
paths := []string{
|
|
filepath.Join(dir, "a.mp3"),
|
|
filepath.Join(dir, "nested", "b.flac"),
|
|
filepath.Join(dir, "README.txt"), // ignored
|
|
filepath.Join(dir, "nested", "c.wav"),
|
|
}
|
|
for _, p := range paths {
|
|
if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
|
|
t.Fatalf("MkdirAll: %v", err)
|
|
}
|
|
if err := os.WriteFile(p, []byte(""), 0o644); err != nil {
|
|
t.Fatalf("WriteFile: %v", err)
|
|
}
|
|
}
|
|
|
|
got, err := CollectAudioFiles(dir)
|
|
if err != nil {
|
|
t.Fatalf("CollectAudioFiles: %v", err)
|
|
}
|
|
// Expect 3 audio files, sorted.
|
|
if len(got) != 3 {
|
|
t.Fatalf("got %d files, want 3 — paths=%v", len(got), got)
|
|
}
|
|
// slices.IsSorted confirms deterministic order.
|
|
if !slices.IsSorted(got) {
|
|
t.Errorf("files not sorted: %v", got)
|
|
}
|
|
// README should not be in the result.
|
|
for _, p := range got {
|
|
if strings.HasSuffix(p, "README.txt") {
|
|
t.Errorf("README included in audio files: %v", got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCollectAudioFilesMissing(t *testing.T) {
|
|
_, err := CollectAudioFiles(filepath.Join(t.TempDir(), "nonexistent"))
|
|
if err == nil {
|
|
t.Error("CollectAudioFiles on missing path should error")
|
|
}
|
|
}
|
|
|
|
func TestArgsLocalFile(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "song.mp3")
|
|
if err := os.WriteFile(path, []byte(""), 0o644); err != nil {
|
|
t.Fatalf("WriteFile: %v", err)
|
|
}
|
|
|
|
got, err := Args([]string{path})
|
|
if err != nil {
|
|
t.Fatalf("Args: %v", err)
|
|
}
|
|
if len(got.Tracks) != 1 {
|
|
t.Fatalf("got %d tracks, want 1", len(got.Tracks))
|
|
}
|
|
if got.Tracks[0].Path != path {
|
|
t.Errorf("Path = %q, want %q", got.Tracks[0].Path, path)
|
|
}
|
|
if len(got.Pending) != 0 {
|
|
t.Errorf("Pending = %v, want empty", got.Pending)
|
|
}
|
|
}
|
|
|
|
func TestArgsLocalDirectory(t *testing.T) {
|
|
dir := t.TempDir()
|
|
files := []string{"a.mp3", "b.flac", "c.ogg"}
|
|
for _, f := range files {
|
|
p := filepath.Join(dir, f)
|
|
if err := os.WriteFile(p, []byte(""), 0o644); err != nil {
|
|
t.Fatalf("WriteFile: %v", err)
|
|
}
|
|
}
|
|
|
|
got, err := Args([]string{dir})
|
|
if err != nil {
|
|
t.Fatalf("Args: %v", err)
|
|
}
|
|
if len(got.Tracks) != 3 {
|
|
t.Fatalf("got %d tracks, want 3", len(got.Tracks))
|
|
}
|
|
// Results preserve on-disk sort order (CollectAudioFiles sorts).
|
|
names := make([]string, len(got.Tracks))
|
|
for i, tr := range got.Tracks {
|
|
names[i] = filepath.Base(tr.Path)
|
|
}
|
|
if !slices.IsSorted(names) {
|
|
t.Errorf("tracks not sorted: %v", names)
|
|
}
|
|
}
|
|
|
|
func TestArgsRemoteFeedURLGoesToPending(t *testing.T) {
|
|
// .xml URL is recognised as a feed.
|
|
feedURL := "https://example.com/podcast.xml"
|
|
got, err := Args([]string{feedURL})
|
|
if err != nil {
|
|
t.Fatalf("Args: %v", err)
|
|
}
|
|
if len(got.Pending) != 1 || got.Pending[0] != feedURL {
|
|
t.Errorf("Pending = %v, want [%s]", got.Pending, feedURL)
|
|
}
|
|
if len(got.Tracks) != 0 {
|
|
t.Errorf("Tracks should be empty, got %+v", got.Tracks)
|
|
}
|
|
}
|
|
|
|
func TestArgsRemoteAudioURLBecomesTrack(t *testing.T) {
|
|
audioURL := "https://stream.example.com/song.mp3"
|
|
got, err := Args([]string{audioURL})
|
|
if err != nil {
|
|
t.Fatalf("Args: %v", err)
|
|
}
|
|
if len(got.Tracks) != 1 {
|
|
t.Fatalf("got %d tracks, want 1", len(got.Tracks))
|
|
}
|
|
if got.Tracks[0].Path != audioURL {
|
|
t.Errorf("Path = %q, want %q", got.Tracks[0].Path, audioURL)
|
|
}
|
|
}
|
|
|
|
func TestArgsLocalM3U(t *testing.T) {
|
|
dir := t.TempDir()
|
|
// Write a local m3u that points at a dummy audio file.
|
|
audio := filepath.Join(dir, "song.mp3")
|
|
if err := os.WriteFile(audio, []byte(""), 0o644); err != nil {
|
|
t.Fatalf("WriteFile: %v", err)
|
|
}
|
|
m3u := filepath.Join(dir, "pl.m3u")
|
|
content := `#EXTM3U
|
|
#EXTINF:120,My Song
|
|
song.mp3
|
|
`
|
|
if err := os.WriteFile(m3u, []byte(content), 0o644); err != nil {
|
|
t.Fatalf("WriteFile: %v", err)
|
|
}
|
|
|
|
got, err := Args([]string{m3u})
|
|
if err != nil {
|
|
t.Fatalf("Args: %v", err)
|
|
}
|
|
if len(got.Tracks) != 1 {
|
|
t.Fatalf("got %d tracks, want 1", len(got.Tracks))
|
|
}
|
|
if got.Tracks[0].Title != "My Song" {
|
|
t.Errorf("Title = %q, want My Song", got.Tracks[0].Title)
|
|
}
|
|
}
|
|
|
|
func TestArgsLocalPLS(t *testing.T) {
|
|
dir := t.TempDir()
|
|
audio := filepath.Join(dir, "song.mp3")
|
|
if err := os.WriteFile(audio, []byte(""), 0o644); err != nil {
|
|
t.Fatalf("WriteFile: %v", err)
|
|
}
|
|
pls := filepath.Join(dir, "pl.pls")
|
|
content := "[playlist]\nFile1=" + audio + "\nTitle1=Track 1\nLength1=60\nNumberOfEntries=1\nVersion=2\n"
|
|
if err := os.WriteFile(pls, []byte(content), 0o644); err != nil {
|
|
t.Fatalf("WriteFile: %v", err)
|
|
}
|
|
|
|
got, err := Args([]string{pls})
|
|
if err != nil {
|
|
t.Fatalf("Args: %v", err)
|
|
}
|
|
if len(got.Tracks) != 1 {
|
|
t.Fatalf("got %d tracks, want 1", len(got.Tracks))
|
|
}
|
|
if got.Tracks[0].Title != "Track 1" {
|
|
t.Errorf("Title = %q, want Track 1", got.Tracks[0].Title)
|
|
}
|
|
}
|
|
|
|
func TestArgsMissingFileErrors(t *testing.T) {
|
|
// CollectAudioFiles stat's the path and Args wraps the os.Stat failure
|
|
// into "scanning X: ..." — missing paths are surfaced, not swallowed.
|
|
path := filepath.Join(t.TempDir(), "ghost.mp3")
|
|
_, err := Args([]string{path})
|
|
if err == nil {
|
|
t.Error("Args for missing file should error")
|
|
}
|
|
if !strings.Contains(err.Error(), "scanning") {
|
|
t.Errorf("error = %q, want to mention 'scanning'", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestScanTracksEmpty(t *testing.T) {
|
|
got := scanTracks(nil)
|
|
if got != nil {
|
|
t.Errorf("scanTracks(nil) = %v, want nil", got)
|
|
}
|
|
got = scanTracks([]string{})
|
|
if got != nil {
|
|
t.Errorf("scanTracks(empty) = %v, want nil", got)
|
|
}
|
|
}
|
|
|
|
func TestScanTracksPreservesOrder(t *testing.T) {
|
|
dir := t.TempDir()
|
|
paths := make([]string, 12) // more than workers = 8
|
|
for i := range paths {
|
|
paths[i] = filepath.Join(dir, "a.mp3")
|
|
}
|
|
|
|
tracks := scanTracks(paths)
|
|
if len(tracks) != len(paths) {
|
|
t.Fatalf("len = %d, want %d", len(tracks), len(paths))
|
|
}
|
|
for i, tr := range tracks {
|
|
if tr.Path != paths[i] {
|
|
t.Errorf("tracks[%d].Path = %q, want %q", i, tr.Path, paths[i])
|
|
}
|
|
}
|
|
}
|