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 }