package netutil import ( "net/http" "net/http/httptest" "net/url" "strings" "testing" "time" ) func TestRedactURLHidesCredentials(t *testing.T) { tests := []struct { name string raw string contains []string absent []string }{ { name: "user auth token", raw: "https://api.example.com/track?user_auth_token=sekrit&track_id=42", contains: []string{"user_auth_token=REDACTED", "track_id=42"}, absent: []string{"sekrit"}, }, { name: "request signature", raw: "https://api.example.com/x?request_sig=abc123&app_id=9", contains: []string{"request_sig=REDACTED", "app_id=9"}, absent: []string{"abc123"}, }, { name: "several at once", raw: "https://api.example.com/x?password=p&access_token=a&refresh_token=r&id=1", contains: []string{"password=REDACTED", "access_token=REDACTED", "refresh_token=REDACTED", "id=1"}, absent: []string{"password=p", "access_token=a", "refresh_token=r"}, }, { name: "nothing sensitive is left untouched", raw: "https://api.example.com/track/42?limit=10", contains: []string{"limit=10", "/track/42"}, }, { name: "no query string", raw: "https://api.example.com/track/42", contains: []string{"https://api.example.com/track/42"}, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { u, err := url.Parse(tc.raw) if err != nil { t.Fatal(err) } got := redactURL(u) for _, want := range tc.contains { if !strings.Contains(got, want) { t.Errorf("redactURL(%q) = %q, missing %q", tc.raw, got, want) } } for _, bad := range tc.absent { if strings.Contains(got, bad) { t.Errorf("redactURL(%q) = %q, leaked %q", tc.raw, got, bad) } } }) } } func TestRedactURLNil(t *testing.T) { if got := redactURL(nil); got != "" { t.Errorf("redactURL(nil) = %q, want empty", got) } } func TestIsSensitiveParam(t *testing.T) { sensitive := []string{ "user_auth_token", "api_token", "access_token", "refresh_token", "request_sig", "signature", "password", "secret", "token", "code", "auth", "key", } for _, name := range sensitive { if !isSensitiveParam(name) { t.Errorf("isSensitiveParam(%q) = false, want true", name) } } for _, name := range []string{"track_id", "limit", "offset", "app_id", "quality"} { if isSensitiveParam(name) { t.Errorf("isSensitiveParam(%q) = true, want false", name) } } } func TestNewHTTPClientHonoursVerifySSL(t *testing.T) { insecure := NewHTTPClient(5*time.Second, false, 4) tr, ok := insecure.Transport.(*loggingTransport) if !ok { t.Fatalf("transport is %T, want *loggingTransport", insecure.Transport) } base, ok := tr.base.(*http.Transport) if !ok { t.Fatalf("base transport is %T", tr.base) } if !base.TLSClientConfig.InsecureSkipVerify { t.Error("verifySSL=false should set InsecureSkipVerify") } secure := NewHTTPClient(5*time.Second, true, 4) base = secure.Transport.(*loggingTransport).base.(*http.Transport) if base.TLSClientConfig.InsecureSkipVerify { t.Error("verifySSL=true must not skip verification") } if secure.Timeout != 5*time.Second { t.Errorf("Timeout = %v, want 5s", secure.Timeout) } } func TestNewHTTPClientZeroTimeoutMeansNoTimeout(t *testing.T) { // internal/download and the qobuz body copy rely on this: a zero timeout // must not be replaced with a default, or long track downloads abort. c := NewHTTPClient(0, true, 0) if c.Timeout != 0 { t.Errorf("Timeout = %v, want 0", c.Timeout) } } func TestNewHTTPClientDefaultsMaxConns(t *testing.T) { c := NewHTTPClient(time.Second, true, 0) base := c.Transport.(*loggingTransport).base.(*http.Transport) if base.MaxIdleConnsPerHost != defaultMaxConnsPerHost { t.Errorf("MaxIdleConnsPerHost = %d, want %d", base.MaxIdleConnsPerHost, defaultMaxConnsPerHost) } } func TestClientPerformsRequest(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusTeapot) })) defer ts.Close() resp, err := NewHTTPClient(5*time.Second, true, 2).Get(ts.URL + "/x?token=sekrit") if err != nil { t.Fatal(err) } defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusTeapot { t.Errorf("status = %d", resp.StatusCode) } }