package httpclient import ( "context" "encoding/base64" "io" "net/http" "net/http/httptest" "strings" "testing" "time" ) func TestDoWithRedirect_SameHost(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/start": w.Header().Set("Location", "/final") w.WriteHeader(http.StatusFound) case "/final": w.WriteHeader(http.StatusOK) w.Write([]byte("ok")) default: w.WriteHeader(http.StatusNotFound) } })) defer srv.Close() c := New(Config{Timeout: 5 * time.Second, MaxResponseBytes: 1024}) req, err := http.NewRequest(http.MethodGet, srv.URL+"/start", nil) if err != nil { t.Fatalf("new request: %v", err) } resp, err := c.DoWithRedirect(context.Background(), req, 5, "127.0.0.1") if err != nil { t.Fatalf("do with redirect: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Fatalf("status=%d, want 200", resp.StatusCode) } body, _ := io.ReadAll(resp.Body) if string(body) != "ok" { t.Fatalf("body=%q, want ok", string(body)) } } func TestDoWithRedirect_CrossHost_PreservesAuth(t *testing.T) { srv2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(r.Header.Get("Authorization"))) })) defer srv2.Close() srv1 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/start" { w.Header().Set("Location", srv2.URL+"/final") w.WriteHeader(http.StatusTemporaryRedirect) return } w.WriteHeader(http.StatusNotFound) })) defer srv1.Close() c := New(Config{Timeout: 5 * time.Second, MaxResponseBytes: 1024}) req, err := http.NewRequest(http.MethodGet, srv1.URL+"/start", nil) if err != nil { t.Fatalf("new request: %v", err) } req.SetBasicAuth("u", "p") resp, err := c.DoWithRedirect(context.Background(), req, 5, "127.0.0.1") if err != nil { t.Fatalf("do with redirect: %v", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { t.Fatalf("read body: %v", err) } want := "Basic " + base64.StdEncoding.EncodeToString([]byte("u:p")) if string(body) != want { t.Fatalf("body=%q, want %q", string(body), want) } } func TestDoWithRedirect_MaxLimit(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Location", "/loop") w.WriteHeader(http.StatusFound) })) defer srv.Close() c := New(Config{Timeout: 5 * time.Second, MaxResponseBytes: 1024}) req, _ := http.NewRequest(http.MethodGet, srv.URL+"/loop", nil) _, err := c.DoWithRedirect(context.Background(), req, 2, "127.0.0.1") if err == nil { t.Fatal("expected too many redirects error") } if !strings.Contains(err.Error(), "too many redirects") { t.Fatalf("expected too many redirects, got %v", err) } } func TestDoWithRedirect_NoLocation(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusFound) })) defer srv.Close() c := New(Config{Timeout: 5 * time.Second, MaxResponseBytes: 1024}) req, _ := http.NewRequest(http.MethodGet, srv.URL, nil) _, err := c.DoWithRedirect(context.Background(), req, 5, "127.0.0.1") if err == nil { t.Fatal("expected error for missing Location") } if !strings.Contains(err.Error(), "without Location header") { t.Fatalf("expected missing Location error, got %v", err) } } func TestDoWithRedirect_ZeroDisallows(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Location", "/elsewhere") w.WriteHeader(http.StatusFound) })) defer srv.Close() c := New(Config{Timeout: 5 * time.Second, MaxResponseBytes: 1024}) req, _ := http.NewRequest(http.MethodGet, srv.URL, nil) resp, err := c.DoWithRedirect(context.Background(), req, 0, "127.0.0.1") if err != nil { t.Fatalf("do: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusFound { t.Fatalf("status=%d, want 302", resp.StatusCode) } }