mirror of
https://git.sr.ht/~joren/streamrip-go
synced 2026-10-16 13:57:25 +02:00
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>
185 lines
4.3 KiB
Go
185 lines
4.3 KiB
Go
package jsonutil
|
|
|
|
import "testing"
|
|
|
|
func TestStringFromAny(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in any
|
|
want string
|
|
}{
|
|
{"string", "hi", "hi"},
|
|
{"int", 7, "7"},
|
|
{"int64", int64(8), "8"},
|
|
{"float whole", float64(9), "9"},
|
|
{"float fractional", 44.1, "44.1"},
|
|
{"float no trailing zeroes", float64(100), "100"},
|
|
{"nil", nil, ""},
|
|
{"bool unsupported", true, ""},
|
|
{"map unsupported", map[string]any{}, ""},
|
|
}
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if got := StringFromAny(tc.in); got != tc.want {
|
|
t.Fatalf("StringFromAny(%#v) = %q, want %q", tc.in, got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIntFromAny(t *testing.T) {
|
|
tests := []struct {
|
|
in any
|
|
want int
|
|
}{
|
|
{5, 5},
|
|
{int32(6), 6},
|
|
{int64(7), 7},
|
|
{8.9, 8},
|
|
{"10", 10},
|
|
{" 11 ", 11},
|
|
{"not a number", 0},
|
|
{nil, 0},
|
|
{true, 0},
|
|
}
|
|
for _, tc := range tests {
|
|
if got := IntFromAny(tc.in); got != tc.want {
|
|
t.Errorf("IntFromAny(%#v) = %d, want %d", tc.in, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFloatFromAny(t *testing.T) {
|
|
tests := []struct {
|
|
in any
|
|
want float64
|
|
}{
|
|
{1.5, 1.5},
|
|
{2, 2},
|
|
{int64(3), 3},
|
|
{"4.5", 0}, // strings are deliberately not parsed here
|
|
{nil, 0},
|
|
}
|
|
for _, tc := range tests {
|
|
if got := FloatFromAny(tc.in); got != tc.want {
|
|
t.Errorf("FloatFromAny(%#v) = %v, want %v", tc.in, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBoolFromAny(t *testing.T) {
|
|
tests := []struct {
|
|
in any
|
|
want bool
|
|
}{
|
|
{true, true},
|
|
{false, false},
|
|
{"true", true},
|
|
{"TRUE", true},
|
|
{" yes ", true},
|
|
{"1", true},
|
|
{"0", false},
|
|
{"no", false},
|
|
{1, true},
|
|
{0, false},
|
|
{int64(2), true},
|
|
{0.0, false},
|
|
{1.5, true},
|
|
{nil, false},
|
|
}
|
|
for _, tc := range tests {
|
|
if got := BoolFromAny(tc.in); got != tc.want {
|
|
t.Errorf("BoolFromAny(%#v) = %v, want %v", tc.in, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFirstNonEmpty(t *testing.T) {
|
|
if got := FirstNonEmpty("", " ", "found", "later"); got != "found" {
|
|
t.Errorf("got %q", got)
|
|
}
|
|
if got := FirstNonEmpty(" trimmed "); got != "trimmed" {
|
|
t.Errorf("got %q, want trimmed", got)
|
|
}
|
|
if got := FirstNonEmpty("", " "); got != "" {
|
|
t.Errorf("got %q, want empty", got)
|
|
}
|
|
if got := FirstNonEmpty(); got != "" {
|
|
t.Errorf("got %q, want empty", got)
|
|
}
|
|
}
|
|
|
|
func TestNestedMap(t *testing.T) {
|
|
m := map[string]any{"album": map[string]any{"title": "X"}, "n": 1}
|
|
if got := NestedMap(m, "album")["title"]; got != "X" {
|
|
t.Errorf("got %v", got)
|
|
}
|
|
if got := NestedMap(m, "missing"); len(got) != 0 {
|
|
t.Errorf("missing key should give empty map, got %v", got)
|
|
}
|
|
if got := NestedMap(m, "n"); len(got) != 0 {
|
|
t.Errorf("non-map value should give empty map, got %v", got)
|
|
}
|
|
}
|
|
|
|
func TestNestedAnyAndString(t *testing.T) {
|
|
m := map[string]any{
|
|
"album": map[string]any{
|
|
"artist": map[string]any{"name": "Nina Simone", "id": float64(42)},
|
|
},
|
|
}
|
|
if got := NestedString(m, "album", "artist", "name"); got != "Nina Simone" {
|
|
t.Errorf("got %q", got)
|
|
}
|
|
if got := NestedString(m, "album", "artist", "id"); got != "42" {
|
|
t.Errorf("numeric id = %q, want 42", got)
|
|
}
|
|
if got := NestedString(m, "album", "nope", "name"); got != "" {
|
|
t.Errorf("missing path = %q, want empty", got)
|
|
}
|
|
if got := NestedAny(m, "album", "artist", "name"); got != "Nina Simone" {
|
|
t.Errorf("got %v", got)
|
|
}
|
|
if got := NestedAny(m); got == nil {
|
|
t.Error("no keys should return the map itself")
|
|
}
|
|
// Traversing through a non-map must not panic.
|
|
if got := NestedAny(m, "album", "artist", "name", "deeper"); got != nil {
|
|
t.Errorf("over-deep path = %v, want nil", got)
|
|
}
|
|
}
|
|
|
|
func TestTitleCase(t *testing.T) {
|
|
tests := []struct{ in, want string }{
|
|
{"qobuz", "Qobuz"},
|
|
{"Tidal", "Tidal"},
|
|
{"", ""},
|
|
{"1st", "1st"},
|
|
{"ä", "ä"}, // only ASCII is uppercased
|
|
}
|
|
for _, tc := range tests {
|
|
if got := TitleCase(tc.in); got != tc.want {
|
|
t.Errorf("TitleCase(%q) = %q, want %q", tc.in, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDisplayArtistNames(t *testing.T) {
|
|
tests := []struct {
|
|
in []string
|
|
want string
|
|
}{
|
|
{nil, ""},
|
|
{[]string{}, ""},
|
|
{[]string{"A"}, "A"},
|
|
{[]string{"A", "B"}, "A & B"},
|
|
{[]string{"A", "B", "C"}, "A, B & C"},
|
|
{[]string{"A", "B", "C", "D"}, "A, B, C & D"},
|
|
}
|
|
for _, tc := range tests {
|
|
if got := DisplayArtistNames(tc.in); got != tc.want {
|
|
t.Errorf("DisplayArtistNames(%v) = %q, want %q", tc.in, got, tc.want)
|
|
}
|
|
}
|
|
}
|