GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Push — master ( fe6920...0b968c )
by
unknown
06:56
created

der_TryRefreshOauthToken_WithCallback   A

Complexity

Conditions 4

Size

Total Lines 35
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 26
nop 1
dl 0
loc 35
rs 9.256
c 0
b 0
f 0
1
package providers
2
3
import (
4
	"errors"
5
	"testing"
6
	"time"
7
8
	httputil "github.com/aliyun/credentials-go/credentials/internal/http"
9
	"github.com/stretchr/testify/assert"
10
)
11
12
func TestNewOAuthCredentialsProvider(t *testing.T) {
13
14
	_, err := NewOAuthCredentialsProviderBuilder().Build()
15
	assert.NotNil(t, err)
16
	assert.Equal(t, "the ClientId is empty", err.Error())
17
18
	_, err = NewOAuthCredentialsProviderBuilder().WithClientId("clientId").Build()
19
	assert.NotNil(t, err)
20
	assert.Equal(t, "the url for sign-in is empty", err.Error())
21
22
	_, err = NewOAuthCredentialsProviderBuilder().
23
		WithClientId("clientId").
24
		WithSignInUrl("https://oauth.aliyun.com").
25
		Build()
26
	assert.NotNil(t, err)
27
	assert.Equal(t, "OAuth access token is empty or expired, please re-login with cli", err.Error())
28
29
	// Test valid OAuth provider
30
	p, err := NewOAuthCredentialsProviderBuilder().
31
		WithClientId("clientId").
32
		WithSignInUrl("https://oauth.aliyun.com").
33
		WithRefreshToken("refreshToken").
34
		WithAccessToken("accessToken").
35
		WithAccessTokenExpire(time.Now().Unix() + 1000).
36
		Build()
37
	assert.Nil(t, err)
38
	assert.Equal(t, "clientId", p.clientId)
39
	assert.Equal(t, "https://oauth.aliyun.com", p.signInUrl)
40
	assert.Equal(t, "refreshToken", p.refreshToken)
41
	assert.Equal(t, "accessToken", p.accessToken)
42
}
43
44
func TestOAuthCredentialsProvider_getCredentials(t *testing.T) {
45
	originHttpDo := httpDo
46
	defer func() { httpDo = originHttpDo }()
47
48
	p, err := NewOAuthCredentialsProviderBuilder().
49
		WithClientId("clientId").
50
		WithSignInUrl("https://oauth.aliyun.com").
51
		WithRefreshToken("refreshToken").
52
		WithAccessToken("token").
53
		WithAccessTokenExpire(time.Now().Unix() + 1000).
54
		Build()
55
	assert.Nil(t, err)
56
57
	// case 1: mock new http request failed
58
	httpDo = func(req *httputil.Request) (res *httputil.Response, err error) {
59
		err = errors.New("mock server error")
60
		return
61
	}
62
	_, err = p.getCredentials()
63
	assert.NotNil(t, err)
64
	assert.Equal(t, "mock server error", err.Error())
65
66
	// case 2: 4xx error
67
	httpDo = func(req *httputil.Request) (res *httputil.Response, err error) {
68
		res = &httputil.Response{
69
			StatusCode: 400,
70
			Body:       []byte("4xx error"),
71
		}
72
		return
73
	}
74
	_, err = p.getCredentials()
75
	assert.NotNil(t, err)
76
	assert.Equal(t, "get session token from OAuth failed: 4xx error", err.Error())
77
78
	// case 3: invalid json
79
	httpDo = func(req *httputil.Request) (res *httputil.Response, err error) {
80
		res = &httputil.Response{
81
			StatusCode: 200,
82
			Body:       []byte("invalid json"),
83
		}
84
		return
85
	}
86
	_, err = p.getCredentials()
87
	assert.NotNil(t, err)
88
	assert.Equal(t, "get session token from OAuth failed, json.Unmarshal fail: invalid character 'i' looking for beginning of value", err.Error())
89
90
	// case 4: empty access key id
91
	httpDo = func(req *httputil.Request) (res *httputil.Response, err error) {
92
		res = &httputil.Response{
93
			StatusCode: 200,
94
			Body:       []byte(`{"accessKeyId":"","accessKeySecret":"sk","securityToken":"token","expiration":"2021-10-20T04:27:09Z"}`),
95
		}
96
		return
97
	}
98
	_, err = p.getCredentials()
99
	assert.NotNil(t, err)
100
	assert.Contains(t, err.Error(), "refresh session token err, fail to get credentials from OAuth")
101
102
	// case 5: empty access key secret
103
	httpDo = func(req *httputil.Request) (res *httputil.Response, err error) {
104
		res = &httputil.Response{
105
			StatusCode: 200,
106
			Body:       []byte(`{"accessKeyId":"ak","accessKeySecret":"","securityToken":"token","expiration":"2021-10-20T04:27:09Z"}`),
107
		}
108
		return
109
	}
110
	_, err = p.getCredentials()
111
	assert.NotNil(t, err)
112
	assert.Contains(t, err.Error(), "refresh session token err, fail to get credentials from OAuth")
113
114
	// case 6: empty security token
115
	httpDo = func(req *httputil.Request) (res *httputil.Response, err error) {
116
		res = &httputil.Response{
117
			StatusCode: 200,
118
			Body:       []byte(`{"accessKeyId":"ak","accessKeySecret":"sk","securityToken":"","expiration":"2021-10-20T04:27:09Z"}`),
119
		}
120
		return
121
	}
122
	_, err = p.getCredentials()
123
	assert.NotNil(t, err)
124
	assert.Contains(t, err.Error(), "refresh session token err, fail to get credentials from OAuth")
125
126
	// case 7: mock ok value
127
	httpDo = func(req *httputil.Request) (res *httputil.Response, err error) {
128
		res = &httputil.Response{
129
			StatusCode: 200,
130
			Body:       []byte(`{"accessKeyId":"ak","accessKeySecret":"sk","securityToken":"token","expiration":"2021-10-20T04:27:09Z","requestId":"123"}`),
131
		}
132
		return
133
	}
134
	creds, err := p.getCredentials()
135
	assert.Nil(t, err)
136
	assert.Equal(t, "ak", creds.AccessKeyId)
137
	assert.Equal(t, "sk", creds.AccessKeySecret)
138
	assert.Equal(t, "token", creds.SecurityToken)
139
	assert.Equal(t, "2021-10-20T04:27:09Z", creds.Expiration)
140
141
	// needUpdateCredential
142
	assert.True(t, p.needUpdateCredential())
143
	p.expirationTimestamp = time.Now().Unix()
144
	assert.True(t, p.needUpdateCredential())
145
146
	p.expirationTimestamp = time.Now().Unix() + 300
147
	assert.False(t, p.needUpdateCredential())
148
}
149
150
func TestOAuthCredentialsProviderGetCredentials(t *testing.T) {
151
152
	p, err := NewOAuthCredentialsProviderBuilder().
153
		WithClientId("clientId").
154
		WithSignInUrl("https://oauth.aliyun.com").
155
		WithRefreshToken("refreshToken").
156
		WithAccessToken("token").
157
		WithAccessTokenExpire(time.Now().Unix() + 1000).
158
		WithHttpOptions(&HttpOptions{
159
			ConnectTimeout: 10000,
160
		}).
161
		Build()
162
163
	assert.Nil(t, err)
164
	assert.Equal(t, 10000, p.httpOptions.ConnectTimeout)
165
	_, err = p.GetCredentials()
166
	assert.NotNil(t, err)
167
	assert.Contains(t, err.Error(), "get session token from OAuth failed")
168
169
	originHttpDo := httpDo
170
	defer func() { httpDo = originHttpDo }()
171
172
	// case 1: mock new http request failed
173
	httpDo = func(req *httputil.Request) (res *httputil.Response, err error) {
174
		err = errors.New("mock server error")
175
		return
176
	}
177
	_, err = p.GetCredentials()
178
	assert.NotNil(t, err)
179
	assert.Equal(t, "mock server error", err.Error())
180
181
	// case 2: get invalid expiration
182
	httpDo = func(req *httputil.Request) (res *httputil.Response, err error) {
183
		res = &httputil.Response{
184
			StatusCode: 200,
185
			Body:       []byte(`{"accessKeyId":"akid","accessKeySecret":"aksecret","securityToken":"ststoken","expiration":"invalidexpiration"}`),
186
		}
187
		return
188
	}
189
	_, err = p.GetCredentials()
190
	assert.NotNil(t, err)
191
	assert.Equal(t, "parsing time \"invalidexpiration\" as \"2006-01-02T15:04:05Z\": cannot parse \"invalidexpiration\" as \"2006\"", err.Error())
192
193
	// case 3: happy result
194
	httpDo = func(req *httputil.Request) (res *httputil.Response, err error) {
195
		res = &httputil.Response{
196
			StatusCode: 200,
197
			Body:       []byte(`{"accessKeyId":"akid","accessKeySecret":"aksecret","securityToken":"ststoken","expiration":"2021-10-20T04:27:09Z"}`),
198
		}
199
		return
200
	}
201
	cc, err := p.GetCredentials()
202
	assert.Nil(t, err)
203
	assert.Equal(t, "akid", cc.AccessKeyId)
204
	assert.Equal(t, "aksecret", cc.AccessKeySecret)
205
	assert.Equal(t, "ststoken", cc.SecurityToken)
206
	assert.Equal(t, "oauth", cc.ProviderName)
207
	assert.True(t, p.needUpdateCredential())
208
}
209
210
func TestOAuthCredentialsProviderGetCredentialsWithHttpOptions(t *testing.T) {
211
	p, err := NewOAuthCredentialsProviderBuilder().
212
		WithClientId("clientId").
213
		WithSignInUrl("https://oauth.aliyun.com").
214
		WithRefreshToken("refreshToken").
215
		WithAccessToken("token").
216
		WithAccessTokenExpire(time.Now().Unix() + 1000).
217
		WithHttpOptions(&HttpOptions{
218
			ConnectTimeout: 1000,
219
			ReadTimeout:    1000,
220
			Proxy:          "localhost:3999",
221
		}).
222
		Build()
223
224
	assert.Nil(t, err)
225
	_, err = p.GetCredentials()
226
	assert.NotNil(t, err)
227
	assert.Contains(t, err.Error(), "proxyconnect tcp:")
228
}
229
230
func TestOAuthCredentialsProviderGetProviderName(t *testing.T) {
231
	p, err := NewOAuthCredentialsProviderBuilder().
232
		WithClientId("clientId").
233
		WithSignInUrl("https://oauth.aliyun.com").
234
		WithRefreshToken("refreshToken").
235
		WithAccessToken("token").
236
		WithAccessTokenExpire(time.Now().Unix() + 1000).
237
		Build()
238
	assert.Nil(t, err)
239
	assert.Equal(t, "oauth", p.GetProviderName())
240
}
241
242
func TestOAuthCredentialsProviderWithHttpOptions(t *testing.T) {
243
	httpOptions := &HttpOptions{
244
		ConnectTimeout: 5000,
245
		ReadTimeout:    8000,
246
		Proxy:          "http://proxy.example.com:8080",
247
	}
248
249
	p, err := NewOAuthCredentialsProviderBuilder().
250
		WithClientId("clientId").
251
		WithSignInUrl("https://oauth.aliyun.com").
252
		WithRefreshToken("refreshToken").
253
		WithAccessToken("token").
254
		WithAccessTokenExpire(time.Now().Unix() + 1000).
255
		WithHttpOptions(httpOptions).
256
		Build()
257
258
	assert.Nil(t, err)
259
	assert.Equal(t, httpOptions, p.httpOptions)
260
	assert.Equal(t, 5000, p.httpOptions.ConnectTimeout)
261
	assert.Equal(t, 8000, p.httpOptions.ReadTimeout)
262
	assert.Equal(t, "http://proxy.example.com:8080", p.httpOptions.Proxy)
263
}
264
265
func TestOAuthCredentialsProviderCredentialCaching(t *testing.T) {
266
	originHttpDo := httpDo
267
	defer func() { httpDo = originHttpDo }()
268
269
	p, err := NewOAuthCredentialsProviderBuilder().
270
		WithClientId("clientId").
271
		WithSignInUrl("https://oauth.aliyun.com").
272
		WithRefreshToken("refreshToken").
273
		WithAccessToken("token").
274
		WithAccessTokenExpire(time.Now().Unix() + 1000).
275
		Build()
276
	assert.Nil(t, err)
277
278
	// Mock successful response
279
	httpDo = func(req *httputil.Request) (res *httputil.Response, err error) {
280
		res = &httputil.Response{
281
			StatusCode: 200,
282
			Body:       []byte(`{"accessKeyId":"akid","accessKeySecret":"aksecret","securityToken":"ststoken","expiration":"2021-10-20T04:27:09Z"}`),
283
		}
284
		return
285
	}
286
287
	// First call should make HTTP request
288
	cc1, err := p.GetCredentials()
289
	assert.Nil(t, err)
290
	assert.Equal(t, "akid", cc1.AccessKeyId)
291
292
	// Second call should use cached credentials (no new HTTP request)
293
	cc2, err := p.GetCredentials()
294
	assert.Nil(t, err)
295
	assert.Equal(t, "akid", cc2.AccessKeyId)
296
	assert.Equal(t, cc1.AccessKeyId, cc2.AccessKeyId)
297
}
298
299
func TestOAuthCredentialsProviderNeedUpdateCredential(t *testing.T) {
300
	p, err := NewOAuthCredentialsProviderBuilder().
301
		WithClientId("clientId").
302
		WithSignInUrl("https://oauth.aliyun.com").
303
		WithRefreshToken("refreshToken").
304
		WithAccessToken("token").
305
		WithAccessTokenExpire(time.Now().Unix() + 1000).
306
		Build()
307
	assert.Nil(t, err)
308
309
	// Initially should need update
310
	assert.True(t, p.needUpdateCredential())
311
312
	// Set expiration far in the future
313
	p.expirationTimestamp = time.Now().Unix() + 3600 // 1 hour
314
	assert.False(t, p.needUpdateCredential())
315
316
	// Set expiration close to now (within 180 seconds)
317
	p.expirationTimestamp = time.Now().Unix() + 100
318
	assert.True(t, p.needUpdateCredential())
319
320
	// Set expiration in the past
321
	p.expirationTimestamp = time.Now().Unix() - 100
322
	assert.True(t, p.needUpdateCredential())
323
}
324
325
func TestOAuthCredentialsProviderTryRefreshOauthToken(t *testing.T) {
326
	originHttpDo := httpDo
327
	defer func() { httpDo = originHttpDo }()
328
329
	p, err := NewOAuthCredentialsProviderBuilder().
330
		WithClientId("clientId").
331
		WithSignInUrl("https://oauth.aliyun.com").
332
		WithRefreshToken("refreshToken").
333
		WithAccessToken("accessToken").
334
		WithAccessTokenExpire(time.Now().Unix() + 1000).
335
		Build()
336
	assert.Nil(t, err)
337
338
	// Test successful token refresh
339
	httpDo = func(req *httputil.Request) (res *httputil.Response, err error) {
340
		res = &httputil.Response{
341
			StatusCode: 200,
342
			Body:       []byte(`{"access_token":"new_access_token","refresh_token":"new_refresh_token","expires_in":3600,"token_type":"Bearer"}`),
343
		}
344
		return
345
	}
346
347
	err = p.tryRefreshOauthToken()
348
	assert.Nil(t, err)
349
	assert.Equal(t, "new_access_token", p.accessToken)
350
	assert.Equal(t, "new_refresh_token", p.refreshToken)
351
	assert.True(t, p.accessTokenExpire > time.Now().Unix())
352
353
	// Test refresh token failure - HTTP error
354
	httpDo = func(req *httputil.Request) (res *httputil.Response, err error) {
355
		err = errors.New("network error")
356
		return
357
	}
358
359
	err = p.tryRefreshOauthToken()
360
	assert.NotNil(t, err)
361
	assert.Equal(t, "network error", err.Error())
362
363
	// Test refresh token failure - non-200 status
364
	httpDo = func(req *httputil.Request) (res *httputil.Response, err error) {
365
		res = &httputil.Response{
366
			StatusCode: 400,
367
			Body:       []byte("bad request"),
368
		}
369
		return
370
	}
371
372
	err = p.tryRefreshOauthToken()
373
	assert.NotNil(t, err)
374
	assert.Contains(t, err.Error(), "failed to refresh token, status code: 400")
375
376
	// Test refresh token failure - invalid JSON
377
	httpDo = func(req *httputil.Request) (res *httputil.Response, err error) {
378
		res = &httputil.Response{
379
			StatusCode: 200,
380
			Body:       []byte("invalid json"),
381
		}
382
		return
383
	}
384
385
	err = p.tryRefreshOauthToken()
386
	assert.NotNil(t, err)
387
	assert.Contains(t, err.Error(), "get refresh token from OAuth failed, json.Unmarshal fail")
388
389
	// Test refresh token failure - empty tokens
390
	httpDo = func(req *httputil.Request) (res *httputil.Response, err error) {
391
		res = &httputil.Response{
392
			StatusCode: 200,
393
			Body:       []byte(`{"access_token":"","refresh_token":"","expires_in":3600,"token_type":"Bearer"}`),
394
		}
395
		return
396
	}
397
398
	err = p.tryRefreshOauthToken()
399
	assert.NotNil(t, err)
400
	assert.Contains(t, err.Error(), "failed to refresh token from OAuth")
401
}
402
403
func TestOAuthCredentialsProviderTokenRefreshIntegration(t *testing.T) {
404
	originHttpDo := httpDo
405
	defer func() { httpDo = originHttpDo }()
406
407
	// Test case where access token is expired and needs refresh
408
	p, err := NewOAuthCredentialsProviderBuilder().
409
		WithClientId("clientId").
410
		WithSignInUrl("https://oauth.aliyun.com").
411
		WithRefreshToken("refreshToken").
412
		WithAccessToken("expiredToken").
413
		WithAccessTokenExpire(time.Now().Unix() - 1000). // expired token
414
		Build()
415
	assert.Nil(t, err)
416
417
	// Mock refresh token response
418
	refreshTokenCallCount := 0
419
	httpDo = func(req *httputil.Request) (res *httputil.Response, err error) {
420
		if req.Path == "/v1/token" {
421
			refreshTokenCallCount++
422
			res = &httputil.Response{
423
				StatusCode: 200,
424
				Body:       []byte(`{"access_token":"new_access_token","refresh_token":"new_refresh_token","expires_in":3600,"token_type":"Bearer"}`),
425
			}
426
			return
427
		}
428
		// Mock credentials exchange response
429
		res = &httputil.Response{
430
			StatusCode: 200,
431
			Body:       []byte(`{"accessKeyId":"akid","accessKeySecret":"aksecret","securityToken":"ststoken","expiration":"2021-10-20T04:27:09Z"}`),
432
		}
433
		return
434
	}
435
436
	cc, err := p.GetCredentials()
437
	assert.Nil(t, err)
438
	assert.Equal(t, "akid", cc.AccessKeyId)
439
	assert.Equal(t, "aksecret", cc.AccessKeySecret)
440
	assert.Equal(t, "ststoken", cc.SecurityToken)
441
	assert.Equal(t, "oauth", cc.ProviderName)
442
	assert.Equal(t, 1, refreshTokenCallCount) // Should have called refresh token once
443
}
444
445
func TestOAuthCredentialsProvider_TokenUpdateCallback(t *testing.T) {
446
	// Test OAuth provider with token update callback
447
	callbackCalled := false
448
	callbackData := struct {
449
		refreshToken      string
450
		accessToken       string
451
		accessKey         string
452
		secret            string
453
		securityToken     string
454
		accessTokenExpire int64
455
		stsExpire         int64
456
	}{}
457
458
	callback := func(refreshToken, accessToken, accessKey, secret, securityToken string, accessTokenExpire, stsExpire int64) error {
459
		callbackCalled = true
460
		callbackData.refreshToken = refreshToken
461
		callbackData.accessToken = accessToken
462
		callbackData.accessKey = accessKey
463
		callbackData.secret = secret
464
		callbackData.securityToken = securityToken
465
		callbackData.accessTokenExpire = accessTokenExpire
466
		callbackData.stsExpire = stsExpire
467
		return nil
468
	}
469
470
	p, err := NewOAuthCredentialsProviderBuilder().
471
		WithClientId("clientId").
472
		WithSignInUrl("https://oauth.aliyun.com").
473
		WithRefreshToken("refreshToken").
474
		WithAccessToken("accessToken").
475
		WithAccessTokenExpire(time.Now().Unix() + 1000).
476
		WithTokenUpdateCallback(callback).
477
		Build()
478
	assert.Nil(t, err)
479
	assert.NotNil(t, p.tokenUpdateCallback)
480
481
	// Test callback is called during token refresh
482
	originHttpDo := httpDo
483
	defer func() { httpDo = originHttpDo }()
484
485
	httpDo = func(req *httputil.Request) (res *httputil.Response, err error) {
486
		if req.Path == "/v1/token" {
487
			res = &httputil.Response{
488
				StatusCode: 200,
489
				Body:       []byte(`{"access_token":"new_access_token","refresh_token":"new_refresh_token","expires_in":3600,"token_type":"Bearer"}`),
490
			}
491
			return
492
		}
493
		// Mock credentials exchange response
494
		res = &httputil.Response{
495
			StatusCode: 200,
496
			Body:       []byte(`{"accessKeyId":"akid","accessKeySecret":"aksecret","securityToken":"ststoken","expiration":"2021-10-20T04:27:09Z"}`),
497
		}
498
		return
499
	}
500
501
	// Set expired token to trigger refresh
502
	p.accessTokenExpire = time.Now().Unix() - 1000
503
504
	_, err = p.GetCredentials()
505
	assert.Nil(t, err)
506
	assert.True(t, callbackCalled)
507
	assert.Equal(t, "new_refresh_token", callbackData.refreshToken)
508
	assert.Equal(t, "new_access_token", callbackData.accessToken)
509
	assert.Equal(t, "akid", callbackData.accessKey)
510
	assert.Equal(t, "aksecret", callbackData.secret)
511
	assert.Equal(t, "ststoken", callbackData.securityToken)
512
}
513
514
func TestOAuthCredentialsProvider_TokenUpdateCallback_Error(t *testing.T) {
515
	// Test OAuth provider with token update callback that returns error
516
	callback := func(refreshToken, accessToken, accessKey, secret, securityToken string, accessTokenExpire, stsExpire int64) error {
517
		return errors.New("callback error")
518
	}
519
520
	p, err := NewOAuthCredentialsProviderBuilder().
521
		WithClientId("clientId").
522
		WithSignInUrl("https://oauth.aliyun.com").
523
		WithRefreshToken("refreshToken").
524
		WithAccessToken("accessToken").
525
		WithAccessTokenExpire(time.Now().Unix() + 1000).
526
		WithTokenUpdateCallback(callback).
527
		Build()
528
	assert.Nil(t, err)
529
530
	originHttpDo := httpDo
531
	defer func() { httpDo = originHttpDo }()
532
533
	httpDo = func(req *httputil.Request) (res *httputil.Response, err error) {
534
		if req.Path == "/v1/token" {
535
			res = &httputil.Response{
536
				StatusCode: 200,
537
				Body:       []byte(`{"access_token":"new_access_token","refresh_token":"new_refresh_token","expires_in":3600,"token_type":"Bearer"}`),
538
			}
539
			return
540
		}
541
		// Mock credentials exchange response
542
		res = &httputil.Response{
543
			StatusCode: 200,
544
			Body:       []byte(`{"accessKeyId":"akid","accessKeySecret":"aksecret","securityToken":"ststoken","expiration":"2021-10-20T04:27:09Z"}`),
545
		}
546
		return
547
	}
548
549
	// Set expired token to trigger refresh
550
	p.accessTokenExpire = time.Now().Unix() - 1000
551
552
	// Should still succeed even if callback fails (callback error is logged but not returned)
553
	_, err = p.GetCredentials()
554
	assert.Nil(t, err)
555
}
556
557
func TestOAuthCredentialsProvider_WithoutTokenUpdateCallback(t *testing.T) {
558
	// Test OAuth provider without token update callback
559
	p, err := NewOAuthCredentialsProviderBuilder().
560
		WithClientId("clientId").
561
		WithSignInUrl("https://oauth.aliyun.com").
562
		WithRefreshToken("refreshToken").
563
		WithAccessToken("accessToken").
564
		WithAccessTokenExpire(time.Now().Unix() + 1000).
565
		Build()
566
	assert.Nil(t, err)
567
	assert.Nil(t, p.tokenUpdateCallback)
568
569
	originHttpDo := httpDo
570
	defer func() { httpDo = originHttpDo }()
571
572
	httpDo = func(req *httputil.Request) (res *httputil.Response, err error) {
573
		if req.Path == "/v1/token" {
574
			res = &httputil.Response{
575
				StatusCode: 200,
576
				Body:       []byte(`{"access_token":"new_access_token","refresh_token":"new_refresh_token","expires_in":3600,"token_type":"Bearer"}`),
577
			}
578
			return
579
		}
580
		// Mock credentials exchange response
581
		res = &httputil.Response{
582
			StatusCode: 200,
583
			Body:       []byte(`{"accessKeyId":"akid","accessKeySecret":"aksecret","securityToken":"ststoken","expiration":"2021-10-20T04:27:09Z"}`),
584
		}
585
		return
586
	}
587
588
	// Set expired token to trigger refresh
589
	p.accessTokenExpire = time.Now().Unix() - 1000
590
591
	// Should succeed without callback
592
	_, err = p.GetCredentials()
593
	assert.Nil(t, err)
594
}
595
596
func TestOAuthCredentialsProvider_TryRefreshOauthToken_WithCallback(t *testing.T) {
597
	originHttpDo := httpDo
598
	defer func() { httpDo = originHttpDo }()
599
600
	callbackCalled := false
601
	callback := func(refreshToken, accessToken, accessKey, secret, securityToken string, accessTokenExpire, stsExpire int64) error {
602
		callbackCalled = true
603
		return nil
604
	}
605
606
	p, err := NewOAuthCredentialsProviderBuilder().
607
		WithClientId("clientId").
608
		WithSignInUrl("https://oauth.aliyun.com").
609
		WithRefreshToken("refreshToken").
610
		WithAccessToken("accessToken").
611
		WithAccessTokenExpire(time.Now().Unix() + 1000).
612
		WithTokenUpdateCallback(callback).
613
		Build()
614
	assert.Nil(t, err)
615
616
	// Test successful token refresh
617
	httpDo = func(req *httputil.Request) (res *httputil.Response, err error) {
618
		res = &httputil.Response{
619
			StatusCode: 200,
620
			Body:       []byte(`{"access_token":"new_access_token","refresh_token":"new_refresh_token","expires_in":3600,"token_type":"Bearer"}`),
621
		}
622
		return
623
	}
624
625
	err = p.tryRefreshOauthToken()
626
	assert.Nil(t, err)
627
	// 注意:tryRefreshOauthToken 本身不会调用回调函数,回调函数是在 GetCredentials 中调用的
628
	assert.False(t, callbackCalled) // 这里应该是 false,因为 tryRefreshOauthToken 不调用回调
629
	assert.Equal(t, "new_access_token", p.accessToken)
630
	assert.Equal(t, "new_refresh_token", p.refreshToken)
631
}
632
633
func TestOAuthCredentialsProvider_GetCredentials_WithExpiredToken(t *testing.T) {
634
	originHttpDo := httpDo
635
	defer func() { httpDo = originHttpDo }()
636
637
	p, err := NewOAuthCredentialsProviderBuilder().
638
		WithClientId("clientId").
639
		WithSignInUrl("https://oauth.aliyun.com").
640
		WithRefreshToken("refreshToken").
641
		WithAccessToken("accessToken").
642
		WithAccessTokenExpire(time.Now().Unix() - 1000). // expired token
643
		Build()
644
	assert.Nil(t, err)
645
646
	// Mock refresh token response
647
	httpDo = func(req *httputil.Request) (res *httputil.Response, err error) {
648
		if req.Path == "/v1/token" {
649
			res = &httputil.Response{
650
				StatusCode: 200,
651
				Body:       []byte(`{"access_token":"new_access_token","refresh_token":"new_refresh_token","expires_in":3600,"token_type":"Bearer"}`),
652
			}
653
			return
654
		}
655
		// Mock credentials exchange response
656
		res = &httputil.Response{
657
			StatusCode: 200,
658
			Body:       []byte(`{"accessKeyId":"akid","accessKeySecret":"aksecret","securityToken":"ststoken","expiration":"2021-10-20T04:27:09Z"}`),
659
		}
660
		return
661
	}
662
663
	cc, err := p.GetCredentials()
664
	assert.Nil(t, err)
665
	assert.Equal(t, "akid", cc.AccessKeyId)
666
	assert.Equal(t, "aksecret", cc.AccessKeySecret)
667
	assert.Equal(t, "ststoken", cc.SecurityToken)
668
	assert.Equal(t, "oauth", cc.ProviderName)
669
}
670
671
func TestOAuthCredentialsProvider_GetCredentials_WithEmptyAccessToken(t *testing.T) {
672
	originHttpDo := httpDo
673
	defer func() { httpDo = originHttpDo }()
674
675
	p, err := NewOAuthCredentialsProviderBuilder().
676
		WithClientId("clientId").
677
		WithSignInUrl("https://oauth.aliyun.com").
678
		WithRefreshToken("refreshToken").
679
		WithAccessToken(""). // empty access token
680
		WithAccessTokenExpire(time.Now().Unix() + 1000).
681
		Build()
682
	assert.Nil(t, err)
683
684
	// Mock refresh token response
685
	httpDo = func(req *httputil.Request) (res *httputil.Response, err error) {
686
		if req.Path == "/v1/token" {
687
			res = &httputil.Response{
688
				StatusCode: 200,
689
				Body:       []byte(`{"access_token":"new_access_token","refresh_token":"new_refresh_token","expires_in":3600,"token_type":"Bearer"}`),
690
			}
691
			return
692
		}
693
		// Mock credentials exchange response
694
		res = &httputil.Response{
695
			StatusCode: 200,
696
			Body:       []byte(`{"accessKeyId":"akid","accessKeySecret":"aksecret","securityToken":"ststoken","expiration":"2021-10-20T04:27:09Z"}`),
697
		}
698
		return
699
	}
700
701
	cc, err := p.GetCredentials()
702
	assert.Nil(t, err)
703
	assert.Equal(t, "akid", cc.AccessKeyId)
704
	assert.Equal(t, "aksecret", cc.AccessKeySecret)
705
	assert.Equal(t, "ststoken", cc.SecurityToken)
706
	assert.Equal(t, "oauth", cc.ProviderName)
707
}
708
709
func TestOAuthCredentialsProvider_GetCredentials_WithZeroAccessTokenExpire(t *testing.T) {
710
	originHttpDo := httpDo
711
	defer func() { httpDo = originHttpDo }()
712
713
	p, err := NewOAuthCredentialsProviderBuilder().
714
		WithClientId("clientId").
715
		WithSignInUrl("https://oauth.aliyun.com").
716
		WithRefreshToken("refreshToken").
717
		WithAccessToken("accessToken").
718
		WithAccessTokenExpire(0). // zero expire time
719
		Build()
720
	assert.Nil(t, err)
721
722
	// Mock refresh token response
723
	httpDo = func(req *httputil.Request) (res *httputil.Response, err error) {
724
		if req.Path == "/v1/token" {
725
			res = &httputil.Response{
726
				StatusCode: 200,
727
				Body:       []byte(`{"access_token":"new_access_token","refresh_token":"new_refresh_token","expires_in":3600,"token_type":"Bearer"}`),
728
			}
729
			return
730
		}
731
		// Mock credentials exchange response
732
		res = &httputil.Response{
733
			StatusCode: 200,
734
			Body:       []byte(`{"accessKeyId":"akid","accessKeySecret":"aksecret","securityToken":"ststoken","expiration":"2021-10-20T04:27:09Z"}`),
735
		}
736
		return
737
	}
738
739
	cc, err := p.GetCredentials()
740
	assert.Nil(t, err)
741
	assert.Equal(t, "akid", cc.AccessKeyId)
742
	assert.Equal(t, "aksecret", cc.AccessKeySecret)
743
	assert.Equal(t, "ststoken", cc.SecurityToken)
744
	assert.Equal(t, "oauth", cc.ProviderName)
745
}
746
747
func TestOAuthCredentialsProvider_GetCredentials_WithNearExpiredToken(t *testing.T) {
748
	originHttpDo := httpDo
749
	defer func() { httpDo = originHttpDo }()
750
751
	p, err := NewOAuthCredentialsProviderBuilder().
752
		WithClientId("clientId").
753
		WithSignInUrl("https://oauth.aliyun.com").
754
		WithRefreshToken("refreshToken").
755
		WithAccessToken("accessToken").
756
		WithAccessTokenExpire(time.Now().Unix() + 100). // near expired (within 180 seconds)
757
		Build()
758
	assert.Nil(t, err)
759
760
	// Mock refresh token response
761
	httpDo = func(req *httputil.Request) (res *httputil.Response, err error) {
762
		if req.Path == "/v1/token" {
763
			res = &httputil.Response{
764
				StatusCode: 200,
765
				Body:       []byte(`{"access_token":"new_access_token","refresh_token":"new_refresh_token","expires_in":3600,"token_type":"Bearer"}`),
766
			}
767
			return
768
		}
769
		// Mock credentials exchange response
770
		res = &httputil.Response{
771
			StatusCode: 200,
772
			Body:       []byte(`{"accessKeyId":"akid","accessKeySecret":"aksecret","securityToken":"ststoken","expiration":"2021-10-20T04:27:09Z"}`),
773
		}
774
		return
775
	}
776
777
	cc, err := p.GetCredentials()
778
	assert.Nil(t, err)
779
	assert.Equal(t, "akid", cc.AccessKeyId)
780
	assert.Equal(t, "aksecret", cc.AccessKeySecret)
781
	assert.Equal(t, "ststoken", cc.SecurityToken)
782
	assert.Equal(t, "oauth", cc.ProviderName)
783
}
784
785
func TestOAuthCredentialsProvider_TryRefreshOauthToken_InvalidURL(t *testing.T) {
786
	p, err := NewOAuthCredentialsProviderBuilder().
787
		WithClientId("clientId").
788
		WithSignInUrl("invalid-url"). // invalid URL
789
		WithRefreshToken("refreshToken").
790
		WithAccessToken("accessToken").
791
		WithAccessTokenExpire(time.Now().Unix() + 1000).
792
		Build()
793
	assert.Nil(t, err)
794
795
	err = p.tryRefreshOauthToken()
796
	assert.NotNil(t, err)
797
	assert.Contains(t, err.Error(), "parse")
798
}
799
800
func TestOAuthCredentialsProvider_TryRefreshOauthToken_NetworkError(t *testing.T) {
801
	originHttpDo := httpDo
802
	defer func() { httpDo = originHttpDo }()
803
804
	p, err := NewOAuthCredentialsProviderBuilder().
805
		WithClientId("clientId").
806
		WithSignInUrl("https://oauth.aliyun.com").
807
		WithRefreshToken("refreshToken").
808
		WithAccessToken("accessToken").
809
		WithAccessTokenExpire(time.Now().Unix() + 1000).
810
		Build()
811
	assert.Nil(t, err)
812
813
	// Mock network error
814
	httpDo = func(req *httputil.Request) (res *httputil.Response, err error) {
815
		return nil, errors.New("network error")
816
	}
817
818
	err = p.tryRefreshOauthToken()
819
	assert.NotNil(t, err)
820
	assert.Equal(t, "network error", err.Error())
821
}
822
823
func TestOAuthCredentialsProvider_TryRefreshOauthToken_Non200Status(t *testing.T) {
824
	originHttpDo := httpDo
825
	defer func() { httpDo = originHttpDo }()
826
827
	p, err := NewOAuthCredentialsProviderBuilder().
828
		WithClientId("clientId").
829
		WithSignInUrl("https://oauth.aliyun.com").
830
		WithRefreshToken("refreshToken").
831
		WithAccessToken("accessToken").
832
		WithAccessTokenExpire(time.Now().Unix() + 1000).
833
		Build()
834
	assert.Nil(t, err)
835
836
	// Mock non-200 status
837
	httpDo = func(req *httputil.Request) (res *httputil.Response, err error) {
838
		res = &httputil.Response{
839
			StatusCode: 401,
840
			Body:       []byte("unauthorized"),
841
		}
842
		return
843
	}
844
845
	err = p.tryRefreshOauthToken()
846
	assert.NotNil(t, err)
847
	assert.Contains(t, err.Error(), "failed to refresh token, status code: 401")
848
}
849
850
func TestOAuthCredentialsProvider_TryRefreshOauthToken_InvalidJSON(t *testing.T) {
851
	originHttpDo := httpDo
852
	defer func() { httpDo = originHttpDo }()
853
854
	p, err := NewOAuthCredentialsProviderBuilder().
855
		WithClientId("clientId").
856
		WithSignInUrl("https://oauth.aliyun.com").
857
		WithRefreshToken("refreshToken").
858
		WithAccessToken("accessToken").
859
		WithAccessTokenExpire(time.Now().Unix() + 1000).
860
		Build()
861
	assert.Nil(t, err)
862
863
	// Mock invalid JSON response
864
	httpDo = func(req *httputil.Request) (res *httputil.Response, err error) {
865
		res = &httputil.Response{
866
			StatusCode: 200,
867
			Body:       []byte("invalid json"),
868
		}
869
		return
870
	}
871
872
	err = p.tryRefreshOauthToken()
873
	assert.NotNil(t, err)
874
	assert.Contains(t, err.Error(), "get refresh token from OAuth failed, json.Unmarshal fail")
875
}
876
877
func TestOAuthCredentialsProvider_TryRefreshOauthToken_EmptyTokens(t *testing.T) {
878
	originHttpDo := httpDo
879
	defer func() { httpDo = originHttpDo }()
880
881
	p, err := NewOAuthCredentialsProviderBuilder().
882
		WithClientId("clientId").
883
		WithSignInUrl("https://oauth.aliyun.com").
884
		WithRefreshToken("refreshToken").
885
		WithAccessToken("accessToken").
886
		WithAccessTokenExpire(time.Now().Unix() + 1000).
887
		Build()
888
	assert.Nil(t, err)
889
890
	// Mock empty tokens response
891
	httpDo = func(req *httputil.Request) (res *httputil.Response, err error) {
892
		res = &httputil.Response{
893
			StatusCode: 200,
894
			Body:       []byte(`{"access_token":"","refresh_token":"","expires_in":3600,"token_type":"Bearer"}`),
895
		}
896
		return
897
	}
898
899
	err = p.tryRefreshOauthToken()
900
	assert.NotNil(t, err)
901
	assert.Contains(t, err.Error(), "failed to refresh token from OAuth")
902
}
903
904
func TestOAuthCredentialsProvider_NeedUpdateCredential_EdgeCases(t *testing.T) {
905
	p, err := NewOAuthCredentialsProviderBuilder().
906
		WithClientId("clientId").
907
		WithSignInUrl("https://oauth.aliyun.com").
908
		WithRefreshToken("refreshToken").
909
		WithAccessToken("accessToken").
910
		WithAccessTokenExpire(time.Now().Unix() + 1000).
911
		Build()
912
	assert.Nil(t, err)
913
914
	// Test with zero expiration timestamp
915
	p.expirationTimestamp = 0
916
	assert.True(t, p.needUpdateCredential())
917
918
	// Test with expiration exactly 180 seconds from now
919
	p.expirationTimestamp = time.Now().Unix() + 180
920
	assert.True(t, p.needUpdateCredential())
921
922
	// Test with expiration 181 seconds from now
923
	p.expirationTimestamp = time.Now().Unix() + 181
924
	assert.False(t, p.needUpdateCredential())
925
}
926
927
func TestOAuthCredentialsProvider_HttpOptions_EdgeCases(t *testing.T) {
928
	// Test with zero timeouts
929
	p, err := NewOAuthCredentialsProviderBuilder().
930
		WithClientId("clientId").
931
		WithSignInUrl("https://oauth.aliyun.com").
932
		WithRefreshToken("refreshToken").
933
		WithAccessToken("accessToken").
934
		WithAccessTokenExpire(time.Now().Unix() + 1000).
935
		WithHttpOptions(&HttpOptions{
936
			ConnectTimeout: 0,
937
			ReadTimeout:    0,
938
		}).
939
		Build()
940
	assert.Nil(t, err)
941
	assert.Equal(t, 0, p.httpOptions.ConnectTimeout)
942
	assert.Equal(t, 0, p.httpOptions.ReadTimeout)
943
944
	// Test with negative timeouts
945
	p, err = NewOAuthCredentialsProviderBuilder().
946
		WithClientId("clientId").
947
		WithSignInUrl("https://oauth.aliyun.com").
948
		WithRefreshToken("refreshToken").
949
		WithAccessToken("accessToken").
950
		WithAccessTokenExpire(time.Now().Unix() + 1000).
951
		WithHttpOptions(&HttpOptions{
952
			ConnectTimeout: -1000,
953
			ReadTimeout:    -2000,
954
		}).
955
		Build()
956
	assert.Nil(t, err)
957
	assert.Equal(t, -1000, p.httpOptions.ConnectTimeout)
958
	assert.Equal(t, -2000, p.httpOptions.ReadTimeout)
959
}
960
961
func TestOAuthCredentialsProvider_GetCredentials_CachedCredentials(t *testing.T) {
962
	originHttpDo := httpDo
963
	defer func() { httpDo = originHttpDo }()
964
965
	p, err := NewOAuthCredentialsProviderBuilder().
966
		WithClientId("clientId").
967
		WithSignInUrl("https://oauth.aliyun.com").
968
		WithRefreshToken("refreshToken").
969
		WithAccessToken("accessToken").
970
		WithAccessTokenExpire(time.Now().Unix() + 1000).
971
		Build()
972
	assert.Nil(t, err)
973
974
	// Mock successful response
975
	httpDo = func(req *httputil.Request) (res *httputil.Response, err error) {
976
		res = &httputil.Response{
977
			StatusCode: 200,
978
			Body:       []byte(`{"accessKeyId":"akid","accessKeySecret":"aksecret","securityToken":"ststoken","expiration":"2021-10-20T04:27:09Z"}`),
979
		}
980
		return
981
	}
982
983
	// First call should make HTTP request
984
	cc1, err := p.GetCredentials()
985
	assert.Nil(t, err)
986
	assert.Equal(t, "akid", cc1.AccessKeyId)
987
988
	// Set expiration far in the future to avoid refresh
989
	p.expirationTimestamp = time.Now().Unix() + 3600
990
991
	// Second call should use cached credentials
992
	cc2, err := p.GetCredentials()
993
	assert.Nil(t, err)
994
	assert.Equal(t, "akid", cc2.AccessKeyId)
995
	assert.Equal(t, cc1.AccessKeyId, cc2.AccessKeyId)
996
	assert.Equal(t, cc1.AccessKeySecret, cc2.AccessKeySecret)
997
	assert.Equal(t, cc1.SecurityToken, cc2.SecurityToken)
998
}
999