github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/pkg/xsuaa/xsuaa_test.go (about)

     1  //go:build unit
     2  // +build unit
     3  
     4  package xsuaa
     5  
     6  import (
     7  	"encoding/base64"
     8  	"github.com/jarcoal/httpmock"
     9  	"github.com/stretchr/testify/assert"
    10  	"github.com/stretchr/testify/require"
    11  	"net/http"
    12  	"net/http/httptest"
    13  	"testing"
    14  	"time"
    15  )
    16  
    17  func TestXSUAA_GetBearerToken(t *testing.T) {
    18  	type (
    19  		fields struct {
    20  			ClientID     string
    21  			ClientSecret string
    22  		}
    23  		want struct {
    24  			authToken AuthToken
    25  			errRegex  string
    26  		}
    27  		response struct {
    28  			statusCode int
    29  			bodyText   string
    30  		}
    31  	)
    32  	tests := []struct {
    33  		name         string
    34  		fields       fields
    35  		oauthUrlPath string
    36  		want         want
    37  		response     response
    38  	}{
    39  		{
    40  			name: "Straight forward",
    41  			fields: fields{
    42  				ClientID:     "myClientID",
    43  				ClientSecret: "secret",
    44  			},
    45  			want: want{
    46  				authToken: AuthToken{
    47  					TokenType:   "bearer",
    48  					AccessToken: "1234",
    49  					ExpiresIn:   9876,
    50  				}},
    51  			response: response{
    52  				bodyText: `{"access_token": "1234", "expires_in": 9876, "token_type": "bearer"}`,
    53  			},
    54  		},
    55  		{
    56  			name: "No expiring duration",
    57  			fields: fields{
    58  				ClientID:     "myClientID",
    59  				ClientSecret: "secret",
    60  			},
    61  			want: want{
    62  				authToken: AuthToken{
    63  					TokenType:   "bearer",
    64  					AccessToken: "1234",
    65  				}},
    66  			response: response{
    67  				bodyText: `{"access_token": "1234", "token_type": "bearer"}`,
    68  			},
    69  		},
    70  		{
    71  			name: "OAuth Url with path",
    72  			fields: fields{
    73  				ClientID:     "myClientID",
    74  				ClientSecret: "secret",
    75  			},
    76  			oauthUrlPath: "/oauth/token?grant_type=client_credentials",
    77  			want: want{
    78  				authToken: AuthToken{
    79  					TokenType:   "bearer",
    80  					AccessToken: "1234",
    81  					ExpiresIn:   9876,
    82  				}},
    83  			response: response{
    84  				bodyText: `{"access_token": "1234", "expires_in": 9876, "token_type": "bearer"}`,
    85  			},
    86  		},
    87  		{
    88  			name: "No token type",
    89  			fields: fields{
    90  				ClientID:     "myClientID",
    91  				ClientSecret: "secret",
    92  			},
    93  			want: want{
    94  				authToken: AuthToken{
    95  					TokenType:   "bearer",
    96  					AccessToken: "1234",
    97  					ExpiresIn:   9876,
    98  				}},
    99  			response: response{
   100  				bodyText: `{"access_token": "1234", "expires_in": 9876}`,
   101  			},
   102  		},
   103  		{
   104  			name: "HTTP error",
   105  			fields: fields{
   106  				ClientID:     "myClientID",
   107  				ClientSecret: "secret",
   108  			},
   109  			want: want{errRegex: `fetching an access token failed: HTTP GET request to .*/oauth/token\?grant_type=client_credentials&response_type=token ` +
   110  				`failed: expected response code 200, got '401', response body: '{"error": "unauthorized"}'`},
   111  			response: response{
   112  				statusCode: 401,
   113  				bodyText:   `{"error": "unauthorized"}`,
   114  			},
   115  		},
   116  		{
   117  			name: "Wrong response code",
   118  			want: want{errRegex: `expected response code 200, got '201', response body: '{"success": "created"}'`},
   119  			response: response{
   120  				statusCode: 201,
   121  				bodyText:   `{"success": "created"}`,
   122  			},
   123  		},
   124  		{
   125  			name: "No 'access_token' field in json response",
   126  			want: want{errRegex: `expected authToken field 'access_token' in json response: got response body: '{"authToken": "1234"}'`},
   127  			response: response{
   128  				bodyText: `{"authToken": "1234"}`,
   129  			},
   130  		},
   131  	}
   132  	for _, tt := range tests {
   133  		t.Run(tt.name, func(t *testing.T) {
   134  			var requestedUrlPath string
   135  			var requestedAuthHeader string
   136  			// Start a local HTTP server
   137  			server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
   138  				requestedUrlPath = req.URL.String()
   139  				if tt.response.statusCode != 0 {
   140  					rw.WriteHeader(tt.response.statusCode)
   141  				}
   142  				requestedAuthHeader = req.Header.Get(authHeaderKey)
   143  				rw.Write([]byte(tt.response.bodyText))
   144  			}))
   145  			// Close the server when test finishes
   146  			defer server.Close()
   147  
   148  			oauthUrl := server.URL + tt.oauthUrlPath
   149  			x := &XSUAA{
   150  				OAuthURL:     oauthUrl,
   151  				ClientID:     tt.fields.ClientID,
   152  				ClientSecret: tt.fields.ClientSecret,
   153  			}
   154  			gotToken, err := x.GetBearerToken()
   155  			if tt.want.errRegex != "" {
   156  				require.Error(t, err, "Error expected")
   157  				assert.Regexp(t, tt.want.errRegex, err.Error())
   158  				return
   159  			}
   160  			require.NoError(t, err, "No error expected")
   161  			assert.Equal(t, tt.want.authToken.TokenType, gotToken.TokenType, "Did not receive expected token type.")
   162  			assert.Equal(t, tt.want.authToken.AccessToken, gotToken.AccessToken, "Did not receive expected access token.")
   163  			if tt.want.authToken.ExpiresIn == 0 {
   164  				assert.Equal(t, time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC),
   165  					gotToken.ExpiresAt, "ExpiresAt should be date zero")
   166  			} else {
   167  				assert.NotEqual(t, time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC),
   168  					gotToken.ExpiresAt, "ExpiresAt should be proper date")
   169  			}
   170  			wantUrlPath := "/oauth/token?grant_type=client_credentials&response_type=token"
   171  			assert.Equal(t, wantUrlPath, requestedUrlPath)
   172  			wantAuth := tt.fields.ClientID + ":" + tt.fields.ClientSecret
   173  			assert.Equal(t, "Basic "+base64.StdEncoding.EncodeToString([]byte(wantAuth)), requestedAuthHeader)
   174  		})
   175  	}
   176  }
   177  
   178  func Test_readResponseBody(t *testing.T) {
   179  	tests := []struct {
   180  		name        string
   181  		response    *http.Response
   182  		want        []byte
   183  		wantErrText string
   184  	}{
   185  		{
   186  			name:     "Straight forward",
   187  			response: httpmock.NewStringResponse(200, "test string"),
   188  			want:     []byte("test string"),
   189  		},
   190  		{
   191  			name:        "No response error",
   192  			wantErrText: "did not retrieve an HTTP response",
   193  		},
   194  	}
   195  	for _, tt := range tests {
   196  		t.Run(tt.name, func(t *testing.T) {
   197  			got, err := readResponseBody(tt.response)
   198  			if tt.wantErrText != "" {
   199  				require.Error(t, err, "Error expected")
   200  				assert.EqualError(t, err, tt.wantErrText, "Error is not equal")
   201  				return
   202  			}
   203  			require.NoError(t, err, "No error expected")
   204  			assert.Equal(t, tt.want, got, "Did not receive expected body")
   205  		})
   206  	}
   207  }
   208  
   209  func TestXSUAA_SetAuthHeaderIfNotPresent(t *testing.T) {
   210  	type (
   211  		fields struct {
   212  			ClientID        string
   213  			ClientSecret    string
   214  			CachedAuthToken AuthToken
   215  		}
   216  		args struct {
   217  			authHeader string
   218  		}
   219  		want struct {
   220  			token    string
   221  			errRegex string
   222  		}
   223  		response struct {
   224  			statusCode int
   225  			bodyText   string
   226  		}
   227  	)
   228  	tests := []struct {
   229  		name     string
   230  		fields   fields
   231  		args     args
   232  		want     want
   233  		response response
   234  	}{
   235  		{
   236  			name: "Straight forward",
   237  			fields: fields{
   238  				ClientID:     "myClientID",
   239  				ClientSecret: "secret",
   240  			},
   241  			want: want{token: "bearer 1234"},
   242  			response: response{
   243  				bodyText: `{"access_token": "1234", "expires_in": 9876, "token_type": "bearer"}`,
   244  			},
   245  		},
   246  		{
   247  			name: "Error case",
   248  			fields: fields{
   249  				ClientID:     "myClientID",
   250  				ClientSecret: "secret",
   251  			},
   252  			want: want{errRegex: `fetching an access token failed: HTTP GET request to .*/oauth/token\?grant_type=client_credentials&response_type=token ` +
   253  				`failed: expected response code 200, got '401', response body: '{"error": "unauthorized"}'`},
   254  			response: response{
   255  				statusCode: 401,
   256  				bodyText:   `{"error": "unauthorized"}`,
   257  			},
   258  		},
   259  		{
   260  			name: "Missing field parameter",
   261  			fields: fields{
   262  				ClientID: "myClientID",
   263  			},
   264  			want: want{errRegex: `OAuthURL, ClientID and ClientSecret have to be set on the xsuaa instance`},
   265  			response: response{
   266  				statusCode: 401,
   267  				bodyText:   `{"error": "unauthorized"}`,
   268  			},
   269  		},
   270  		{
   271  			name: "Different token type",
   272  			fields: fields{
   273  				ClientID:     "myClientID",
   274  				ClientSecret: "secret",
   275  			},
   276  			want: want{token: "jwt 1234"},
   277  			response: response{
   278  				bodyText: `{"access_token": "1234", "expires_in": 9876, "token_type": "jwt"}`,
   279  			},
   280  		},
   281  		{
   282  			name: "Auth authHeader already set",
   283  			fields: fields{
   284  				ClientID:     "myClientID",
   285  				ClientSecret: "secret",
   286  			},
   287  			args: args{authHeader: "basic eW91aGF2ZXRvb211Y2g6dGltZQ=="},
   288  			want: want{token: "basic eW91aGF2ZXRvb211Y2g6dGltZQ=="},
   289  			response: response{
   290  				bodyText: `{"access_token": "1234", "expires_in": 9876, "token_type": "jwt"}`,
   291  			},
   292  		},
   293  		{
   294  			name: "Valid token skips getting a new one",
   295  			fields: fields{
   296  				ClientID:     "myClientID",
   297  				ClientSecret: "secret",
   298  				CachedAuthToken: AuthToken{
   299  					TokenType:   "bearer",
   300  					AccessToken: "4321",
   301  					ExpiresAt:   time.Now().Add(43200 * time.Second),
   302  				},
   303  			},
   304  			want: want{token: "bearer 4321"},
   305  		},
   306  		{
   307  			name: "Token about to expire",
   308  			fields: fields{
   309  				ClientID:     "myClientID",
   310  				ClientSecret: "secret",
   311  				CachedAuthToken: AuthToken{
   312  					TokenType:   "junk",
   313  					AccessToken: "4321",
   314  					ExpiresAt:   time.Now().Add(100 * time.Second),
   315  				},
   316  			},
   317  			want: want{token: "bearer 1234"},
   318  			response: response{
   319  				bodyText: `{"access_token": "1234", "expires_in": 9876, "token_type": "bearer"}`,
   320  			},
   321  		},
   322  	}
   323  	for _, tt := range tests {
   324  		t.Run(tt.name, func(t *testing.T) {
   325  			// Start a local HTTP server
   326  			server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
   327  				if tt.response.statusCode != 0 {
   328  					rw.WriteHeader(tt.response.statusCode)
   329  				}
   330  				rw.Write([]byte(tt.response.bodyText))
   331  			}))
   332  			// Close the server when test finishes
   333  			defer server.Close()
   334  
   335  			x := &XSUAA{
   336  				OAuthURL:        server.URL,
   337  				ClientID:        tt.fields.ClientID,
   338  				ClientSecret:    tt.fields.ClientSecret,
   339  				CachedAuthToken: tt.fields.CachedAuthToken,
   340  			}
   341  			header := make(http.Header)
   342  			if len(tt.args.authHeader) > 0 {
   343  				header.Add(authHeaderKey, tt.args.authHeader)
   344  			}
   345  			err := x.SetAuthHeaderIfNotPresent(&header)
   346  			if tt.want.errRegex != "" {
   347  				require.Error(t, err, "Error expected")
   348  				assert.Regexp(t, tt.want.errRegex, err.Error(), "")
   349  				return
   350  			}
   351  			require.NoError(t, err, "No error expected")
   352  			assert.Equal(t, tt.want.token, header.Get("Authorization"))
   353  		})
   354  	}
   355  }
   356  
   357  func Test_setExpireTime(t *testing.T) {
   358  	t.Run("Straight forward", func(t *testing.T) {
   359  		dummyTime := time.Date(2022, 1, 1, 12, 0, 0, 0, time.UTC)
   360  		got := setExpireTime(dummyTime, time.Duration(43200))
   361  		want := time.Date(2022, 1, 2, 0, 0, 0, 0, time.UTC)
   362  		assert.Equal(t, got, want, "Time should have increased by 12 hours")
   363  	})
   364  }