refactor: delete dead code, dedupe helpers, add tests for untested packages

Dead code
  Removed, each verified to have no non-test caller: promptSearchSelection
  (77 lines, superseded by promptSearchSelectionMenu), AddPlaylistByTrackIDs,
  download.NewWithVerifySSL, qobuz.Client.GetTrackMetadata,
  tidal.Client.trackSupportsAtmos (test-only, and the comment above
  GetDownloadable already documented its removal from the live path) and its
  now-orphaned test, yandex.canonicalTrackRequestID (a no-op returning its
  trimmed input; the call site uses strings.TrimSpace directly) and the unused
  defaultEstimatedKbps constant.

Config keys that did nothing
  qobuz.download_booklets and misc.check_for_updates were declared, defaulted
  and documented in config.toml.example but read by no code. The whole
  [youtube] block went too: there is no YouTube provider. go-toml ignores
  unknown keys, verified, so existing user configs still load.

Duplicated helpers
  tidal.stringify and yandex.firstNonEmpty were byte-identical copies of
  jsonutil.StringFromAny and jsonutil.FirstNonEmpty. displayArtistNames
  existed three times, byte-identical, in internal/app, provider/yandex and
  provider/beatport; it now lives in jsonutil. The CLI's normalizeCodec
  delegates to config.NormalizeCodec so the flag and the config file are
  checked by the same rules.

Tests for packages that had none
  internal/jsonutil (0% -> 100%) and internal/netutil (0% -> 78%), the latter
  covering redactURL, which is a security control, and the zero-timeout
  contract that internal/download and the qobuz body copy depend on.
  internal/config rose to 82% with coverage of the new Validate(), the
  0600 permissions, CloneConfigData not sharing slices, and a concurrent
  UpdateTokens test that exercises the mutex under -race.

Test suite is ~85x faster
  Provider tests built clients with the default 60 requests/minute, so the
  rate limiter slept 1s per HTTP call: deezer took 31s, tidal 11s, qobuz 8s.
  Each provider test package now builds its config through a testConfigData
  helper that disables throttling. Whole suite: ~60s -> 0.7s, and 2.8s
  under -race.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
JorenandClaude Opus 5 committed 2026-10-03 17:05:55 +02:00
1 parent 201aa5daa7
commit b3b6a2eb1d
19 files changed
+701 -392

No files matched your search

+11 -42
View File
@@ -36,7 +36,6 @@ const (
requestAttempts = 3
desktopWindowsSignKey = "kzqU4XhfCaY6B6JTHODeq5"
legacyMP3SignSalt = "XGRlBW9FXlekgbPrRHuSiA"
defaultEstimatedKbps = 50000
)
var ErrMissingYandexToken = errors.New("missing yandex access_token")
@@ -180,7 +179,7 @@ func (c *Client) Close() error {
}
func (c *Client) getTrackMetadata(ctx context.Context, item string) (map[string]any, error) {
trackRef := canonicalTrackRequestID(item)
trackRef := strings.TrimSpace(item)
form := url.Values{}
form.Set("trackIds", trackRef)
form.Set("withMixData", "true")
@@ -577,7 +576,7 @@ func normalizeTrack(raw map[string]any, fallbackID string, artistSeparator strin
trackID := canonicalTrackRefFromRaw(raw, fallbackID)
albumRaw := firstAlbum(raw)
artistNames := artistNames(raw)
artistName := displayArtistNames(artistNames)
artistName := jsonutil.DisplayArtistNames(artistNames)
albumArtist := joinAlbumArtists(albumRaw, artistSeparator)
if albumArtist == "" {
albumArtist = artistName
@@ -596,7 +595,7 @@ func normalizeTrack(raw map[string]any, fallbackID string, artistSeparator strin
"tracks_count": trackTotal,
"media_number": discNumber,
"numberOfVolumes": discTotal,
"release_date": firstNonEmpty(jsonutil.StringFromAny(raw["releaseDate"]), jsonutil.StringFromAny(albumRaw["releaseDate"])),
"release_date": jsonutil.FirstNonEmpty(jsonutil.StringFromAny(raw["releaseDate"]), jsonutil.StringFromAny(albumRaw["releaseDate"])),
"parental_warning": isExplicit(raw),
"explicit": isExplicit(raw),
"source_track_id": jsonutil.StringFromAny(raw["realId"]),
@@ -684,9 +683,9 @@ func normalizePlaylist(raw map[string]any) map[string]any {
}
}
owner, _ := raw["owner"].(map[string]any)
ownerName := firstNonEmpty(jsonutil.StringFromAny(owner["name"]), jsonutil.StringFromAny(owner["login"]))
ownerName := jsonutil.FirstNonEmpty(jsonutil.StringFromAny(owner["name"]), jsonutil.StringFromAny(owner["login"]))
return map[string]any{
"id": firstNonEmpty(jsonutil.StringFromAny(raw["playlistUuid"]), jsonutil.StringFromAny(raw["kind"])),
"id": jsonutil.FirstNonEmpty(jsonutil.StringFromAny(raw["playlistUuid"]), jsonutil.StringFromAny(raw["kind"])),
"name": jsonutil.StringFromAny(raw["title"]),
"title": jsonutil.StringFromAny(raw["title"]),
"description": jsonutil.StringFromAny(raw["description"]),
@@ -738,8 +737,8 @@ func normalizeArtistSearchItem(raw map[string]any) map[string]any {
func normalizePlaylistSearchItem(raw map[string]any) map[string]any {
owner, _ := raw["owner"].(map[string]any)
ownerID := firstNonEmpty(jsonutil.StringFromAny(owner["uid"]), jsonutil.StringFromAny(raw["uid"]))
ownerName := firstNonEmpty(jsonutil.StringFromAny(owner["name"]), jsonutil.StringFromAny(owner["login"]))
ownerID := jsonutil.FirstNonEmpty(jsonutil.StringFromAny(owner["uid"]), jsonutil.StringFromAny(raw["uid"]))
ownerName := jsonutil.FirstNonEmpty(jsonutil.StringFromAny(owner["name"]), jsonutil.StringFromAny(owner["login"]))
return map[string]any{
"id": ownerID + ":" + jsonutil.StringFromAny(raw["kind"]),
"title": jsonutil.StringFromAny(raw["title"]),
@@ -783,7 +782,7 @@ func firstArtistID(raw map[string]any) string {
}
func joinArtists(raw map[string]any, artistSeparator string) string {
return displayArtistNames(artistNames(raw))
return jsonutil.DisplayArtistNames(artistNames(raw))
}
func artistNames(raw map[string]any) []string {
@@ -805,7 +804,7 @@ func joinAlbumArtists(albumRaw map[string]any, artistSeparator string) string {
if len(albumRaw) == 0 {
return ""
}
return displayArtistNames(albumArtistNames(albumRaw))
return jsonutil.DisplayArtistNames(albumArtistNames(albumRaw))
}
func albumArtistNames(albumRaw map[string]any) []string {
@@ -823,19 +822,6 @@ func albumArtistNames(albumRaw map[string]any) []string {
return parts
}
func displayArtistNames(names []string) string {
switch len(names) {
case 0:
return ""
case 1:
return names[0]
case 2:
return names[0] + " & " + names[1]
default:
return strings.Join(names[:len(names)-1], ", ") + " & " + names[len(names)-1]
}
}
func (c *Client) artistSeparator() string {
if c.cfg.Session.Metadata.ArtistSeparator == "" {
return "; "
@@ -852,19 +838,11 @@ func trackNumbers(albumRaw map[string]any) (int, int) {
return trackNumber, firstPositiveInt(jsonutil.IntFromAny(position["volume"]), 1)
}
func canonicalTrackRequestID(item string) string {
item = strings.TrimSpace(item)
if item == "" {
return item
}
return item
}
func canonicalTrackRefFromRaw(raw map[string]any, fallback string) string {
if strings.Contains(strings.TrimSpace(fallback), ":") {
return strings.TrimSpace(fallback)
}
trackID := firstNonEmpty(jsonutil.StringFromAny(raw["realId"]), jsonutil.StringFromAny(raw["id"]))
trackID := jsonutil.FirstNonEmpty(jsonutil.StringFromAny(raw["realId"]), jsonutil.StringFromAny(raw["id"]))
albumID := jsonutil.StringFromAny(firstAlbum(raw)["id"])
if trackID == "" {
trackID = strings.TrimSpace(fallback)
@@ -931,7 +909,7 @@ func imageMapFromTrack(raw map[string]any, albumRaw map[string]any) map[string]a
}
func imageMapFromURI(raw map[string]any) map[string]any {
uri := firstNonEmpty(jsonutil.StringFromAny(raw["ogImage"]), jsonutil.StringFromAny(raw["coverUri"]))
uri := jsonutil.FirstNonEmpty(jsonutil.StringFromAny(raw["ogImage"]), jsonutil.StringFromAny(raw["coverUri"]))
if uri == "" {
if cover, ok := raw["cover"].(map[string]any); ok {
uri = jsonutil.StringFromAny(cover["uri"])
@@ -1049,12 +1027,3 @@ func firstPositiveInt(vals ...int) int {
}
return 0
}
func firstNonEmpty(vals ...string) string {
for _, v := range vals {
if strings.TrimSpace(v) != "" {
return strings.TrimSpace(v)
}
}
return ""
}
+12 -9
View File
@@ -60,7 +60,7 @@ func TestGetDownloadableUsesModernGetFileInfo(t *testing.T) {
}))
defer ts.Close()
d := config.DefaultConfigData()
d := testConfigData()
d.Downloads.RequestsPerMinute = 0
d.Yandex.AccessToken = "token"
c := New(&config.Config{File: d, Session: d})
@@ -132,7 +132,7 @@ func TestGetMetadataTrackUsesModernTracksEndpoint(t *testing.T) {
}))
defer ts.Close()
d := config.DefaultConfigData()
d := testConfigData()
d.Downloads.RequestsPerMinute = 0
d.Yandex.AccessToken = "token"
c := New(&config.Config{File: d, Session: d})
@@ -167,13 +167,6 @@ func TestGetMetadataTrackUsesModernTracksEndpoint(t *testing.T) {
}
}
func TestDisplayArtistNamesUsesCreditPunctuation(t *testing.T) {
got := displayArtistNames([]string{"A", "B", "C"})
if got != "A, B & C" {
t.Fatalf("displayArtistNames() = %q", got)
}
}
func TestLegacyDirectURLBuildsPlayableMP3URL(t *testing.T) {
url, err := legacyDirectURL(&legacyDownloadInfoXML{
Host: "example.test",
@@ -189,3 +182,13 @@ func TestLegacyDirectURLBuildsPlayableMP3URL(t *testing.T) {
t.Fatalf("legacyDirectURL() = %q, want %q", url, want)
}
}
// testConfigData returns defaults with request throttling disabled.
//
// At the default 60 requests/minute the rate limiter sleeps 1s per request,
// which accounted for most of this suite's runtime (deezer alone was 31s).
func testConfigData() config.ConfigData {
d := config.DefaultConfigData()
d.Downloads.RequestsPerMinute = -1
return d
}