package main import "testing" func TestParseFileInputJSONItems(t *testing.T) { content := []byte(`[ {"source":"qobuz","media_type":"album","id":"0066991040005"}, {"source":"tidal","media_type":"track","id":3083287} ]`) items, urls, repeated, jsonInput, err := parseFileInput(content) if err != nil { t.Fatalf("parseFileInput() error = %v", err) } if !jsonInput { t.Fatalf("jsonInput = false, want true") } if len(urls) != 0 { t.Fatalf("urls len = %d, want 0", len(urls)) } if repeated != 0 { t.Fatalf("repeated = %d, want 0", repeated) } if len(items) != 2 { t.Fatalf("items len = %d, want 2", len(items)) } if items[0].Source != "qobuz" || items[0].MediaType != "album" || items[0].ID != "0066991040005" { t.Fatalf("unexpected first item: %+v", items[0]) } if items[1].Source != "tidal" || items[1].MediaType != "track" || items[1].ID != "3083287" { t.Fatalf("unexpected second item: %+v", items[1]) } } func TestParseFileInputTextURLsDedupes(t *testing.T) { content := []byte("https://tidal.com/browse/track/3083287\nhttps://tidal.com/browse/track/3083287\nhttps://www.qobuz.com/fr-fr/album/example/0066991040005\n") items, urls, repeated, jsonInput, err := parseFileInput(content) if err != nil { t.Fatalf("parseFileInput() error = %v", err) } if jsonInput { t.Fatalf("jsonInput = true, want false") } if len(items) != 0 { t.Fatalf("items len = %d, want 0", len(items)) } if repeated != 1 { t.Fatalf("repeated = %d, want 1", repeated) } if len(urls) != 2 { t.Fatalf("urls len = %d, want 2", len(urls)) } } func TestParseFileInputRejectsInvalidJSONShape(t *testing.T) { content := []byte(`{"source":"qobuz","media_type":"track","id":"1"}`) _, _, _, jsonInput, err := parseFileInput(content) if err == nil { t.Fatalf("expected error for non-array json") } if !jsonInput { t.Fatalf("jsonInput = false, want true") } } func TestParseLastFMArgsDefaults(t *testing.T) { opts, err := parseLastFMArgs([]string{"https://www.last.fm/user/x/playlists/123"}, "qobuz", "") if err != nil { t.Fatalf("parseLastFMArgs() error = %v", err) } if opts.Source != "qobuz" { t.Fatalf("source = %q, want qobuz", opts.Source) } if opts.PlaylistURL == "" { t.Fatalf("playlist url should not be empty") } } func TestParseLastFMArgsOptions(t *testing.T) { opts, err := parseLastFMArgs([]string{"--source", "tidal", "--fallback-source", "qobuz", "https://www.last.fm/user/x/playlists/123"}, "qobuz", "") if err != nil { t.Fatalf("parseLastFMArgs() error = %v", err) } if opts.Source != "tidal" || opts.FallbackSource != "qobuz" { t.Fatalf("unexpected options: %+v", opts) } } func TestExtractLastFMPlaylistInfoAndPairs(t *testing.T) { html := `

Road & Rain

` title, total, err := extractLastFMPlaylistInfo(html) if err != nil { t.Fatalf("extractLastFMPlaylistInfo() error = %v", err) } if title != "Road & Rain" { t.Fatalf("title = %q, want %q", title, "Road & Rain") } if total != 2 { t.Fatalf("total = %d, want 2", total) } pairs := extractLastFMTitleArtistPairs(html) if len(pairs) != 2 { t.Fatalf("pairs len = %d, want 2", len(pairs)) } if pairs[0].Title != "Dreams" || pairs[0].Artist != "Fleetwood Mac" { t.Fatalf("unexpected first pair: %+v", pairs[0]) } } func TestParseGlobalArgsNoDBBeforeCommand(t *testing.T) { opts, err := parseGlobalArgs([]string{"-ndb", "url", "https://play.qobuz.com/album/0004228000522"}) if err != nil { t.Fatalf("parseGlobalArgs() error = %v", err) } if !opts.noDB { t.Fatalf("expected noDB true") } if opts.command != "url" { t.Fatalf("command = %q, want %q", opts.command, "url") } if len(opts.commandArgs) != 1 { t.Fatalf("command args len = %d, want 1", len(opts.commandArgs)) } } func TestParseGlobalArgsAllOfficialFlags(t *testing.T) { opts, err := parseGlobalArgs([]string{ "--config-path", "/tmp/custom.toml", "-f", "/tmp/music", "--no-db", "-q", "3", "-c", "ogg", "--no-progress", "--no-ssl-verify", "-v", "search", "tidal", "track", "dreams", }) if err != nil { t.Fatalf("parseGlobalArgs() error = %v", err) } if opts.configPath != "/tmp/custom.toml" || opts.folder != "/tmp/music" { t.Fatalf("unexpected path/folder opts: %+v", opts) } if !opts.noDB || !opts.qualitySet || opts.quality != 3 || !opts.codecSet || opts.codec != "VORBIS" { t.Fatalf("unexpected quality/codec/db opts: %+v", opts) } if !opts.noProgress || !opts.noSSLVerify || !opts.verbose { t.Fatalf("unexpected boolean opts: %+v", opts) } if opts.command != "search" { t.Fatalf("command = %q, want search", opts.command) } } func TestNormalizeCodecRejectsUnknown(t *testing.T) { if _, err := normalizeCodec("wav"); err == nil { t.Fatalf("expected error for unsupported codec") } } func TestGroupLastFMResolvedTracksBySourcePreservesOrderAndDuplicates(t *testing.T) { resolved := []resolvedLastFMTrack{ {Source: "tidal", ID: "1"}, {Source: "tidal", ID: "1"}, {Source: "qobuz", ID: "2"}, {Source: "tidal", ID: "3"}, {Source: "", ID: "4"}, } groups := groupLastFMResolvedTracksBySource(resolved) if len(groups["tidal"]) != 3 { t.Fatalf("tidal ids len = %d, want 3", len(groups["tidal"])) } if len(groups["qobuz"]) != 1 { t.Fatalf("qobuz ids len = %d, want 1", len(groups["qobuz"])) } if groups["tidal"][0] != "1" || groups["tidal"][1] != "1" || groups["tidal"][2] != "3" { t.Fatalf("unexpected tidal ordering: %+v", groups["tidal"]) } } func TestExtractLastFMTracksFromMirrorMarkdown(t *testing.T) { md := `Title: My Playlist | user playlists | Last.fm | Play | Image | Loved | Name | Artist name | Buy | Options | Duration | | --- | --- | --- | --- | --- | --- | --- | --- | | [Play track](https://x) | [img](https://i) | x | [Song A](https://a) | [Artist A](https://aa) | | | 3:00 | | [Play track](https://x) | [img](https://i) | x | [Song B](https://b) | [Artist B](https://bb) | | | 4:00 |` title, tracks := extractLastFMTracksFromMirrorMarkdown(md) if title != "My Playlist" { t.Fatalf("title = %q, want %q", title, "My Playlist") } if len(tracks) != 2 { t.Fatalf("tracks len = %d, want 2", len(tracks)) } if tracks[0].Title != "Song A" || tracks[0].Artist != "Artist A" { t.Fatalf("unexpected first track: %+v", tracks[0]) } }