From 95ffd4064c67c274173395cc9eb8040642351b01 Mon Sep 17 00:00:00 2001 From: Jose Antonio Insua Date: Mon, 1 Apr 2024 20:47:06 +0200 Subject: [PATCH 1/3] Forward Reply-After header in Response --- ims/client.go | 5 ++++- ims/error.go | 10 ++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/ims/client.go b/ims/client.go index 814c821..46d2226 100644 --- a/ims/client.go +++ b/ims/client.go @@ -76,7 +76,8 @@ type Response struct { // The raw body of the HTTP response. Body []byte // The value of the X-Debug-Id header. - XDebugID string + XDebugID string + RetryAfter string } func (c *Client) do(req *http.Request) (*Response, error) { @@ -93,10 +94,12 @@ func (c *Client) do(req *http.Request) (*Response, error) { // X-Debug-Id is the header used by IMS to track requests. xdebugid := res.Header.Get("x-debug-id") + retryAfter := res.Header.Get("Retry-After") return &Response{ StatusCode: res.StatusCode, Body: data, XDebugID: xdebugid, + RetryAfter: retryAfter, }, nil } diff --git a/ims/error.go b/ims/error.go index f7b73a1..2e97f5b 100644 --- a/ims/error.go +++ b/ims/error.go @@ -26,6 +26,16 @@ type Error struct { } func (e *Error) Error() string { + if e.RetryAfter != "" { + return fmt.Sprintf( + "error response: statusCode=%d, errorCode='%s', errorMessage='%s', x-debug-id='%s', retry-after='%s'", + e.StatusCode, + e.ErrorCode, + e.ErrorMessage, + e.XDebugID, + e.RetryAfter, + ) + } return fmt.Sprintf( "error response: statusCode=%d, errorCode='%s', errorMessage='%s', x-debug-id='%s'", e.StatusCode, From 2f586fbd5a9d36535533726a05b10d610513b192 Mon Sep 17 00:00:00 2001 From: Jose Antonio Insua Date: Mon, 1 Apr 2024 21:39:41 +0200 Subject: [PATCH 2/3] Test retry-after and x-debug-id header parsing --- ims/cluster_exchange_token_test.go | 60 ++++++++++++++++++++++++++++++ ims/get_profile_test.go | 53 ++++++++++++++++++++++++++ 2 files changed, 113 insertions(+) diff --git a/ims/cluster_exchange_token_test.go b/ims/cluster_exchange_token_test.go index a7ad13a..df6357a 100644 --- a/ims/cluster_exchange_token_test.go +++ b/ims/cluster_exchange_token_test.go @@ -141,6 +141,66 @@ func TestClusterExchangeError(t *testing.T) { } } +func TestClusterExchangeTooManyRequests(t *testing.T) { + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Retry-After", "77") + w.Header().Set("x-debug-id", "banana") + w.WriteHeader(http.StatusTooManyRequests) + + body := struct { + ErrorCode string `json:"error"` + ErrorMessage string `json:"error_description"` + }{ + ErrorCode: "error-code", + ErrorMessage: "error-message", + } + + if err := json.NewEncoder(w).Encode(&body); err != nil { + t.Fatalf("encode response: %v", err) + } + })) + defer s.Close() + + c, err := ims.NewClient(&ims.ClientConfig{ + URL: s.URL, + }) + if err != nil { + t.Fatalf("create client: %v", err) + } + + res, err := c.ClusterExchange(&ims.ClusterExchangeRequest{ + ClientID: "irrelevant", + ClientSecret: "irrelevant", + Scopes: []string{"yolo", "test"}, + UserToken: "old-token", + OrgID: "orgid", + }) + + if res != nil { + t.Fatalf("expected nil response because of error") + } + + imsErr, ok := ims.IsError(err) + if !ok { + t.Fatalf("expected IMS error") + } + if imsErr.StatusCode != http.StatusTooManyRequests { + t.Fatalf("invalid status code: %v", imsErr.StatusCode) + } + if imsErr.ErrorCode != "error-code" { + t.Fatalf("invalid error code: %v", imsErr.ErrorCode) + } + if imsErr.ErrorMessage != "error-message" { + t.Fatalf("invalid error message: %v", imsErr.ErrorMessage) + } + if imsErr.RetryAfter != "77" { + t.Fatalf("invalid retry-after header: %v", imsErr.RetryAfter) + } + if imsErr.XDebugID != "banana" { + t.Fatalf("invalid x-debug-id header: %v", imsErr.RetryAfter) + } +} + func TestClusterExchangeInvalidRequest(t *testing.T) { c, err := ims.NewClient(&ims.ClientConfig{ URL: "http://ims.endpoint", diff --git a/ims/get_profile_test.go b/ims/get_profile_test.go index 274a8fc..54c3b0a 100644 --- a/ims/get_profile_test.go +++ b/ims/get_profile_test.go @@ -75,3 +75,56 @@ func TestGetProfileEmptyErrorResponse(t *testing.T) { t.Fatalf("invalid error type: %v", err) } } + +func TestGetProfileTooManyRequests(t *testing.T) { + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + t.Fatalf("invalid method: %v", r.Method) + } + if v := r.Header.Get("authorization"); v != "Bearer accessToken" { + t.Fatalf("invalid authorization header: %v", v) + } + + w.Header().Set("Retry-After", "77") + w.Header().Set("x-debug-id", "banana") + w.WriteHeader(http.StatusTooManyRequests) + })) + defer s.Close() + + c, err := ims.NewClient(&ims.ClientConfig{ + URL: s.URL, + }) + if err != nil { + t.Fatalf("create client: %v", err) + } + + res, err := c.GetProfile(&ims.GetProfileRequest{ + AccessToken: "accessToken", + }) + + if err == nil { + t.Fatalf("expected error in get profile") + } + + if res != nil { + t.Fatalf("expected nil response because of error") + } + + imsErr, ok := ims.IsError(err) + if !ok { + t.Fatalf("expected IMS error") + } + + if imsErr.StatusCode != http.StatusTooManyRequests { + t.Fatalf("invalid status code: %v", imsErr.StatusCode) + } + + if imsErr.RetryAfter != "77" { + t.Fatalf("invalid retry-after header: %v", imsErr.RetryAfter) + } + + if imsErr.XDebugID != "banana" { + t.Fatalf("invalid x-debug-id header: %v", imsErr.XDebugID) + } + +} From f30a3f3af11381f1aea7ae73b678814e91d54d9a Mon Sep 17 00:00:00 2001 From: Jose Antonio Insua Date: Tue, 2 Apr 2024 13:59:35 +0200 Subject: [PATCH 3/3] in-passing dependency update --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 10cbf39..414cac4 100644 --- a/go.mod +++ b/go.mod @@ -12,4 +12,4 @@ module github.com/adobe/ims-go go 1.18 -require github.com/golang-jwt/jwt/v5 v5.0.0 +require github.com/golang-jwt/jwt/v5 v5.2.1 diff --git a/go.sum b/go.sum index fcfb224..10bcec6 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,4 @@ github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=