From 570631b67fbf8a1609cfabaeda9a2c32d1abadd5 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 24 Sep 2024 16:12:49 -0500 Subject: [PATCH] More unit tests --- .secrets.baseline | 4 +- examples/cmd/iam_demo.go | 9 ++--- session/rest.go | 4 +- session/rest_test.go | 62 ++++++++++++++++++++++++++++++- session/session.go | 10 +++-- session/session_test.go | 80 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 153 insertions(+), 16 deletions(-) diff --git a/.secrets.baseline b/.secrets.baseline index 7b880d9..8e106fb 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -3,7 +3,7 @@ "files": "go.sum|^.secrets.baseline$", "lines": null }, - "generated_at": "2024-09-20T19:18:35Z", + "generated_at": "2024-09-24T20:48:27Z", "plugins_used": [ { "name": "AWSKeyDetector" @@ -242,7 +242,7 @@ "hashed_secret": "6f667d3e9627f5549ffeb1055ff294c34430b837", "is_secret": false, "is_verified": false, - "line_number": 194, + "line_number": 193, "type": "Secret Keyword", "verified_result": null } diff --git a/examples/cmd/iam_demo.go b/examples/cmd/iam_demo.go index 319308c..ab01786 100644 --- a/examples/cmd/iam_demo.go +++ b/examples/cmd/iam_demo.go @@ -2,8 +2,8 @@ package cmd import ( "fmt" - "time" "github.com/spf13/cobra" + "time" "github.com/softlayer/softlayer-go/services" "github.com/softlayer/softlayer-go/session" @@ -26,10 +26,10 @@ func RunIamCmd(cmd *cobra.Command, args []string) error { // Sets up the session with authentication headers. sess := &session.Session{ - Endpoint: session.DefaultEndpoint, - IAMToken: "Bearer TOKEN", + Endpoint: session.DefaultEndpoint, + IAMToken: "Bearer TOKEN", IAMRefreshToken: "REFRESH TOKEN", - Debug: true, + Debug: true, } // creates a reference to the service object (SoftLayer_Account) @@ -37,7 +37,6 @@ func RunIamCmd(cmd *cobra.Command, args []string) error { // Sets the mask, filter, result limit, and then makes the API call SoftLayer_Account::getHardware() - for { account, err := service.Mask(objectMask).GetObject() if err != nil { diff --git a/session/rest.go b/session/rest.go index 1088cc1..cc4e388 100644 --- a/session/rest.go +++ b/session/rest.go @@ -35,7 +35,6 @@ import ( type RestTransport struct{} - // DoRequest - Implementation of the TransportHandler interface for handling // calls to the REST endpoint. func (r *RestTransport) DoRequest(sess *Session, service string, method string, args []interface{}, options *sl.Options, pResult interface{}) error { @@ -228,6 +227,7 @@ func makeHTTPRequest( } else { url = url + session.Endpoint } + // fmt.Printf("Calling %s/%s", strings.TrimRight(url, "/"), path) url = fmt.Sprintf("%s/%s", strings.TrimRight(url, "/"), path) req, err := http.NewRequest(requestType, url, bytes.NewBuffer(requestBody)) if err != nil { @@ -348,5 +348,3 @@ func findResponseError(code int, resp []byte) error { } return nil } - - diff --git a/session/rest_test.go b/session/rest_test.go index 64cf053..3d312f4 100644 --- a/session/rest_test.go +++ b/session/rest_test.go @@ -18,10 +18,10 @@ package session import ( "errors" - "testing" - "fmt" + "net/http" "reflect" + "testing" "github.com/jarcoal/httpmock" "github.com/softlayer/softlayer-go/datatypes" @@ -320,6 +320,64 @@ func TestRest(t *testing.T) { } } +// Tests refreshing a IAM token if it is expired. +func TestRestReauth(t *testing.T) { + // setup session and mock environment + s = New() + s.Endpoint = restEndpoint + s.IAMToken = "Bearer TestToken" + s.IAMRefreshToken = "TestTokenRefresh" + //s.Debug = true + httpmock.Activate() + defer httpmock.DeactivateAndReset() + fmt.Printf("Test [Rest Reauthentication]: ") + slOptions := &sl.Options{} + slResults := &datatypes.Account{} + + httpmock.RegisterResponder("GET", fmt.Sprintf("%s/SoftLayer_Account.json", restEndpoint), + func(req *http.Request) (*http.Response, error) { + if s.IAMToken == "Bearer TestToken" { + return httpmock.NewStringResponse( + 500, + `{"code":"SoftLayer_Exception_Account_Authentication_AccessTokenValidation", "error": "Expired Token"}`, + ), nil + } else { + return httpmock.NewStringResponse( + 200, + `{"id":1234,"companyName":"test"}`, + ), nil + } + }) + httpmock.RegisterResponder("POST", IBMCLOUDIAMENDPOINT, + httpmock.NewStringResponder(200, `{"access_token": "NewToken123", "refresh_token":"NewRefreshToken123", "token_type":"Bearer"}`), + ) + err := s.DoRequest("SoftLayer_Account", "getObject", nil, slOptions, slResults) + if err != nil { + t.Errorf("Testing Error: %v\n", err.Error()) + } + + if s.IAMToken != "Bearer NewToken123" { + t.Errorf("(IAMToken) %s != 'Bearer NewToken123', Refresh Failed.", s.IAMToken) + } + if s.IAMRefreshToken != "NewRefreshToken123" { + t.Errorf("(IAMRefreshToken) %s != 'NewRefreshToken123', Refresh Failed.", s.IAMRefreshToken) + } + if httpmock.GetTotalCallCount() != 3 { + t.Errorf("Call Count = %d, expected 3", httpmock.GetTotalCallCount()) + } + callInfo := httpmock.GetCallCountInfo() + fmt.Printf("%v\n", callInfo) + iamUrl := "POST https://iam.cloud.ibm.com/identity/token" + slUrl := "GET https://api.softlayer.com/rest/v3/SoftLayer_Account.json" + if callInfo[iamUrl] != 1 { + t.Errorf("%s called %d times, expected 1", iamUrl, callInfo[iamUrl]) + } + if callInfo[slUrl] != 2 { + t.Errorf("%s called %d times, expected 1", slUrl, callInfo[slUrl]) + } + teardown() +} + func setup(tc testcase) { httpmock.RegisterResponder( httpMethod(tc.method, tc.args), diff --git a/session/session.go b/session/session.go index 6addf9d..593f1c2 100644 --- a/session/session.go +++ b/session/session.go @@ -19,7 +19,10 @@ package session import ( "context" + "encoding/base64" + "encoding/json" "fmt" + "io/ioutil" "log" "math/rand" "net" @@ -29,10 +32,6 @@ import ( "os/user" "strings" "time" - "encoding/base64" - "encoding/json" - "io/ioutil" - "github.com/softlayer/softlayer-go/config" "github.com/softlayer/softlayer-go/sl" @@ -349,6 +348,7 @@ func (r *Session) RefreshToken() error { reqPayload.Add("refresh_token", r.IAMRefreshToken) req, err := http.NewRequest("POST", IBMCLOUDIAMENDPOINT, strings.NewReader(reqPayload.Encode())) + if err != nil { return err } @@ -366,6 +366,7 @@ func (r *Session) RefreshToken() error { defer resp.Body.Close() responseBody, err := ioutil.ReadAll(resp.Body) + if err != nil { return err } @@ -384,6 +385,7 @@ func (r *Session) RefreshToken() error { if err != nil { return err } + r.IAMToken = fmt.Sprintf("%s %s", token.TokenType, token.AccessToken) r.IAMRefreshToken = token.RefreshToken return nil diff --git a/session/session_test.go b/session/session_test.go index d18cd48..d79ab8a 100644 --- a/session/session_test.go +++ b/session/session_test.go @@ -1,6 +1,9 @@ package session import ( + "fmt" + "github.com/jarcoal/httpmock" + "github.com/softlayer/softlayer-go/sl" "os" "strings" "testing" @@ -96,3 +99,80 @@ func TestSetRetries(t *testing.T) { t.Errorf("Session.Retries (%v) != newVariable (%v)", s.Retries, newVariable) } } + +func TestNeedsRefresh(t *testing.T) { + testError := sl.Error{ + StatusCode: 500, + Exception: "SoftLayer_Exception_Account_Authentication_AccessTokenValidation", + } + if !NeedsRefresh(testError) { + t.Errorf("NeedsRefresh() failed to detect refresh error") + } + testError = sl.Error{ + StatusCode: 500, + Exception: "SoftLayer_Exception_Public", + } + if NeedsRefresh(testError) { + t.Errorf("NeedsRefresh() failed to properly error check") + } +} + +// Tests refreshing a IAM token if it is expired. +func TestRefreshToken(t *testing.T) { + // setup session and mock environment + s = New() + s.Endpoint = restEndpoint + s.IAMToken = "Bearer TestToken" + s.IAMRefreshToken = "TestTokenRefresh" + //s.Debug = true + httpmock.Activate() + defer httpmock.DeactivateAndReset() + fmt.Printf("TestRefreshToken [Happy Path]: ") + expectedError := "" + // Happy Path + httpmock.RegisterResponder("POST", IBMCLOUDIAMENDPOINT, + httpmock.NewStringResponder(200, `{"access_token": "NewToken123", "refresh_token":"NewRefreshToken123", "token_type":"Bearer"}`), + ) + err := s.RefreshToken() + if err != nil { + t.Errorf("Testing Error: %v\n", err.Error()) + } + + if s.IAMToken != "Bearer NewToken123" { + t.Errorf("(IAMToken) %s != 'Bearer NewToken123', Refresh Failed.", s.IAMToken) + } + if s.IAMRefreshToken != "NewRefreshToken123" { + t.Errorf("(IAMRefreshToken) %s != 'NewRefreshToken123', Refresh Failed.", s.IAMRefreshToken) + } + httpmock.Reset() + + // Error returned from IAM API + fmt.Printf("TestRefreshToken [API error]: ") + httpmock.RegisterResponder("POST", IBMCLOUDIAMENDPOINT, + httpmock.NewStringResponder(400, `{"errormessage": "Some Error", "errorcode":"400"}`), + ) + err = s.RefreshToken() + if err == nil { + t.Errorf("Expected an error, none returned\n") + } + expectedError = "400: Some Error " + if err.Error() != expectedError { + t.Errorf("Expected |%s| == %s", err.Error(), expectedError) + } + httpmock.Reset() + + // Junk returned from IAM API + fmt.Printf("TestRefreshToken [Bad Response]: ") + httpmock.RegisterResponder("POST", IBMCLOUDIAMENDPOINT, + httpmock.NewStringResponder(200, ""), + ) + err = s.RefreshToken() + if err == nil { + t.Errorf("Expected an error, none returned\n") + } + expectedError = "unexpected end of JSON input" + if err.Error() != expectedError { + t.Errorf("Expected %s == %s", err.Error(), expectedError) + } + httpmock.Reset() +}