github.com/anthropics/anthropic-sdk-go@v1.70.1/internal/auth/middleware_test.go (about) 1 package auth 2 3 import ( 4 "bytes" 5 "context" 6 "errors" 7 "io" 8 "log" 9 "net/http" 10 "net/http/httptest" 11 "net/url" 12 "os" 13 "strings" 14 "testing" 15 16 "github.com/anthropics/anthropic-sdk-go/internal/requestconfig" 17 ) 18 19 func configFor(t *testing.T, base string) *requestconfig.RequestConfig { 20 t.Helper() 21 u, err := url.Parse(base) 22 if err != nil { 23 t.Fatal(err) 24 } 25 return &requestconfig.RequestConfig{BaseURL: u} 26 } 27 28 // TestMiddleware_401WithUnreplayableBodyWarns verifies that the 401-retry path 29 // emits a one-shot warning when it has to give up because the request body 30 // cannot be replayed (no GetBody). Today this returns the 401 silently. 31 func TestMiddleware_401WithUnreplayableBodyWarns(t *testing.T) { 32 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 33 w.WriteHeader(http.StatusUnauthorized) 34 w.Write([]byte(`{}`)) 35 })) 36 defer server.Close() 37 38 provider := func(_ context.Context, _ string, _ func(*http.Request) (*http.Response, error)) (*AccessToken, error) { 39 return &AccessToken{Token: "tok-1"}, nil 40 } 41 cache := NewTokenCache(provider, http.DefaultClient.Do) 42 mw := NewMiddleware(cache, configFor(t, server.URL)) 43 44 req, _ := http.NewRequestWithContext(context.Background(), http.MethodPost, server.URL, io.NopCloser(strings.NewReader("body"))) 45 req.GetBody = nil // unreplayable 46 47 resetWarnOnceForTest(t) 48 var buf syncBuffer 49 log.SetOutput(&buf) 50 defer log.SetOutput(os.Stderr) 51 52 resp, err := mw(req, http.DefaultClient.Do) 53 if err != nil { 54 t.Fatalf("expected nil error, got %v", err) 55 } 56 if resp.StatusCode != http.StatusUnauthorized { 57 t.Fatalf("expected 401, got %d", resp.StatusCode) 58 } 59 resp.Body.Close() 60 61 if !strings.Contains(buf.String(), "cannot be replayed") { 62 t.Fatalf("expected warning about unreplayable body, got: %q", buf.String()) 63 } 64 } 65 66 // TestMiddleware_401ForceTokenErrorWarns verifies that when ForceToken itself 67 // returns an error during the 401 retry, the middleware logs the underlying 68 // cause instead of silently returning the original 401. 69 func TestMiddleware_401ForceTokenErrorWarns(t *testing.T) { 70 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 71 w.WriteHeader(http.StatusUnauthorized) 72 w.Write([]byte(`{}`)) 73 })) 74 defer server.Close() 75 76 var callCount int 77 forceErr := errors.New("token endpoint unreachable at http://elsewhere") 78 provider := func(_ context.Context, _ string, _ func(*http.Request) (*http.Response, error)) (*AccessToken, error) { 79 callCount++ 80 if callCount == 1 { 81 return &AccessToken{Token: "tok-1"}, nil 82 } 83 return nil, forceErr 84 } 85 cache := NewTokenCache(provider, http.DefaultClient.Do) 86 mw := NewMiddleware(cache, configFor(t, server.URL)) 87 88 req, _ := http.NewRequestWithContext(context.Background(), http.MethodPost, server.URL, bytes.NewReader([]byte("body"))) 89 req.GetBody = func() (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader([]byte("body"))), nil } 90 91 resetWarnOnceForTest(t) 92 var buf syncBuffer 93 log.SetOutput(&buf) 94 defer log.SetOutput(os.Stderr) 95 96 resp, err := mw(req, http.DefaultClient.Do) 97 if err != nil { 98 t.Fatalf("expected nil error from middleware so caller gets the original 401, got %v", err) 99 } 100 if resp.StatusCode != http.StatusUnauthorized { 101 t.Fatalf("expected 401, got %d", resp.StatusCode) 102 } 103 resp.Body.Close() 104 105 out := buf.String() 106 if !strings.Contains(out, "token endpoint unreachable") { 107 t.Fatalf("expected warning to include underlying error, got: %q", out) 108 } 109 } 110 111 func TestMiddleware_ExchangesAtConfiguredBaseURL(t *testing.T) { 112 var providerBaseURLs []string 113 provider := func(_ context.Context, baseURL string, _ func(*http.Request) (*http.Response, error)) (*AccessToken, error) { 114 providerBaseURLs = append(providerBaseURLs, baseURL) 115 return &AccessToken{Token: "tok-1"}, nil 116 } 117 next := func(req *http.Request) (*http.Response, error) { 118 return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody, Request: req}, nil 119 } 120 mw := NewMiddleware(NewTokenCache(provider, nil), configFor(t, "https://api.example.test/prefix/")) 121 122 req, _ := http.NewRequest(http.MethodGet, "https://API.example.test/prefix/v1/models", nil) 123 if _, err := mw(req, next); err != nil { 124 t.Fatal(err) 125 } 126 if got := req.Header.Get("Authorization"); got != "Bearer tok-1" { 127 t.Fatalf("same-origin request got Authorization %q, want bearer", got) 128 } 129 if len(providerBaseURLs) != 1 || providerBaseURLs[0] != "https://api.example.test" { 130 t.Fatalf("provider saw base URLs %q, want just the configured origin", providerBaseURLs) 131 } 132 133 for _, foreign := range []string{ 134 "https://attacker.example.test/v1/models", 135 "https://api.example.test:8443/v1/models", 136 "http://api.example.test/v1/models", 137 } { 138 req, _ := http.NewRequest(http.MethodGet, foreign, nil) 139 if _, err := mw(req, next); err != nil { 140 t.Fatalf("%s: %v", foreign, err) 141 } 142 if got := req.Header.Get("Authorization"); got != "" { 143 t.Errorf("%s: Authorization %q attached to a foreign origin", foreign, got) 144 } 145 if got := req.Header.Get("anthropic-beta"); got != "" { 146 t.Errorf("%s: anthropic-beta %q attached to a foreign origin", foreign, got) 147 } 148 } 149 if len(providerBaseURLs) != 1 { 150 t.Fatalf("provider was re-invoked for foreign origins: %q", providerBaseURLs) 151 } 152 } 153 154 func TestMiddleware_UnresolvedBaseURLFails(t *testing.T) { 155 provider := func(_ context.Context, _ string, _ func(*http.Request) (*http.Response, error)) (*AccessToken, error) { 156 t.Fatal("provider must not be invoked without a configured base URL") 157 return nil, nil 158 } 159 mw := NewMiddleware(NewTokenCache(provider, nil), &requestconfig.RequestConfig{}) 160 req, _ := http.NewRequest(http.MethodGet, "https://api.example.test/v1/models", nil) 161 _, err := mw(req, func(*http.Request) (*http.Response, error) { 162 t.Fatal("request must not be sent") 163 return nil, nil 164 }) 165 if err == nil || !strings.Contains(err.Error(), "base URL is not set") { 166 t.Fatalf("got %v, want base URL error", err) 167 } 168 }