package main import ( "errors" "os" "path/filepath" "strings" "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 TestParseLastFMArgsRejectsNonLastFMURL(t *testing.T) { _, err := parseLastFMArgs([]string{"https://example.com/user/x/playlists/123"}, "qobuz", "") if err == nil || !strings.Contains(strings.ToLower(err.Error()), "last.fm") { t.Fatalf("expected last.fm url validation error, got %v", err) } } func TestIsValidLastFMPlaylistURL(t *testing.T) { if !isValidLastFMPlaylistURL("https://www.last.fm/user/x/playlists/123") { t.Fatalf("expected canonical last.fm playlist url to be valid") } if !isValidLastFMPlaylistURL("http://last.fm/user/x/playlists/123") { t.Fatalf("expected http last.fm playlist url to be valid") } if isValidLastFMPlaylistURL("ftp://last.fm/user/x/playlists/123") { t.Fatalf("expected non-http scheme to be invalid") } if isValidLastFMPlaylistURL("https://example.com/user/x/playlists/123") { t.Fatalf("expected non-last.fm host to be invalid") } } 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 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]) } } func TestExtractLastFMTracksFromMirrorMarkdownLowercasePlayTrack(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 |` _, tracks := extractLastFMTracksFromMirrorMarkdown(md) if len(tracks) != 1 { t.Fatalf("tracks len = %d, want 1", len(tracks)) } } func TestParseSearchArgsAllowsFirstAndOutputFileButCallerCanReject(t *testing.T) { opts, err := parseSearchArgs([]string{"q", "--first", "--output-file", "/tmp/out.json"}, 20) if err != nil { t.Fatalf("parseSearchArgs() error = %v", err) } if !opts.first || opts.outputFile == "" { t.Fatalf("expected first=true and output file set, got %+v", opts) } } func TestParseSearchArgsRejectsUnknownOption(t *testing.T) { _, err := parseSearchArgs([]string{"query", "--bogus"}, 20) if err == nil || !strings.Contains(err.Error(), "unknown option") { t.Fatalf("expected unknown option error, got %v", err) } } func TestParseSearchArgsSupportsDoubleDashTerminator(t *testing.T) { opts, err := parseSearchArgs([]string{"--limit", "10", "--", "--weird", "track"}, 20) if err != nil { t.Fatalf("parseSearchArgs() error = %v", err) } if opts.limit != 10 { t.Fatalf("limit = %d, want 10", opts.limit) } if opts.query != "--weird track" { t.Fatalf("query = %q, want %q", opts.query, "--weird track") } } func TestWriteSearchResultsToFileCreatesParentDirectory(t *testing.T) { tmp := t.TempDir() out := filepath.Join(tmp, "nested", "search", "results.json") results := []searchResult{{ID: "1", Title: "Dreams"}} if err := writeSearchResultsToFile("qobuz", "track", results, out); err != nil { t.Fatalf("writeSearchResultsToFile() error = %v", err) } if _, err := os.Stat(out); err != nil { t.Fatalf("expected output file, stat error = %v", err) } } func TestErrorWithActionableHintForSSL(t *testing.T) { err := errors.New("x509: certificate signed by unknown authority") msg := errorWithActionableHint(err, globalOptions{}) if msg == err.Error() { t.Fatalf("expected ssl hint in message") } } func TestErrorWithActionableHintNoHintWhenDisabled(t *testing.T) { err := errors.New("tls handshake failure") msg := errorWithActionableHint(err, globalOptions{noSSLVerify: true}) if msg != err.Error() { t.Fatalf("unexpected hint when noSSLVerify set") } }