public async Task CancelDuringRefresh() { var clock = new MockClock(new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Utc)); var logger = new NullLogger(); int refreshCalled = 0; int refreshCompleted = 0; var refreshCts = new CancellationTokenSource(); TokenRefreshManager trm = null; trm = new TokenRefreshManager(async ct => { Interlocked.Increment(ref refreshCalled); await Task.Delay(TimeSpan.FromSeconds(60), refreshCts.Token); // Will never get here Interlocked.Increment(ref refreshCompleted); return(false); }, clock, logger); var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(10)); await Assert.ThrowsAsync <TaskCanceledException>(() => trm.GetAccessTokenForRequestAsync(cts.Token)); refreshCts.Cancel(); // Non-deterministic if the refresh function gets called by the time this test ends. // And it's either be called never, or retried n times by the token refresh manager. Assert.InRange(Interlocked.Add(ref refreshCalled, 0), 0, TokenRefreshManager.RefreshTimeouts.Length); Assert.Equal(0, Interlocked.Add(ref refreshCompleted, 0)); }
public async Task UnobservedException() { // An unobserved exception used to happen when the token is soft expired so that // a refresh token task is started but not inmediately observed and it fails. // See https://github.com/googleapis/google-api-dotnet-client/issues/2021 string exceptionMessage = "While testing for unobserved exceptions the refresh task failed."; int unobservedCount = 0; TaskScheduler.UnobservedTaskException += (sender, e) => { if (e.Exception.InnerExceptions.Any(ex => ex.Message == exceptionMessage)) { Interlocked.Increment(ref unobservedCount); e.SetObserved(); } }; var refreshCompletionSource = new TaskCompletionSource <bool>(); var clock = new MockClock(new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Utc)); var logger = new NullLogger(); var softExpiredToken = new TokenResponse { AccessToken = clock.UtcNow.ToString("O"), ExpiresInSeconds = TokenResponse.TokenRefreshTimeWindowSeconds, IssuedUtc = clock.UtcNow }; TokenRefreshManager trm = new TokenRefreshManager(ThrowsWhenRefreshing, clock, logger) { // The initial token is soft expired. Token = softExpiredToken }; // Since the token is only soft expired, we will get it, but a refresh task // is still started. var token = await trm.GetAccessTokenForRequestAsync(default);
public async Task RetriesTimeout_Async() { var clock = new MockClock(new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Utc)); var logger = new NullLogger(); int refreshCallCount = 0; int refreshCompleted = 0; TokenRefreshManager trm = null; trm = new TokenRefreshManager(async ct => { Interlocked.Increment(ref refreshCallCount); Assert.True(ct.CanBeCanceled); var token = new CancellationTokenSource(TimeSpan.FromMilliseconds(10)).Token; await Task.Delay(TimeSpan.FromSeconds(10), token); // Will never get here Interlocked.Increment(ref refreshCompleted); return(false); }, clock, logger); var ex = await Assert.ThrowsAsync <InvalidOperationException>(() => trm.GetAccessTokenForRequestAsync(CancellationToken.None)); Assert.Contains("timeout, timeout, timeout", ex.Message); Assert.Equal(TokenRefreshManager.RefreshTimeouts.Length, refreshCallCount); Assert.Equal(0, Interlocked.Add(ref refreshCompleted, 0)); }
public async Task RetriesTimeout_Sync() { var clock = new MockClock { UtcNow = new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Utc) }; var logger = new NullLogger(); int refreshCallCount = 0; int refreshCompleted = 0; TokenRefreshManager trm = null; trm = new TokenRefreshManager(ct => { Interlocked.Increment(ref refreshCallCount); Assert.True(ct.CanBeCanceled); new CancellationToken(true).ThrowIfCancellationRequested(); // Will never get here Interlocked.Increment(ref refreshCompleted); return(Task.FromResult(false)); }, clock, logger); var ex = await Assert.ThrowsAsync <InvalidOperationException>(() => trm.GetAccessTokenForRequestAsync(CancellationToken.None)); Assert.Contains("timeout, timeout, timeout", ex.Message); Assert.Equal(TokenRefreshManager.RefreshTimeouts.Length, refreshCallCount); Assert.Equal(0, Interlocked.Add(ref refreshCompleted, 0)); }
public async Task ExpiredToken() { var clock = new MockClock(new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Utc)); var logger = new NullLogger(); int refreshFnCount = 0; string accessToken = null; TokenRefreshManager trm = null; trm = new TokenRefreshManager(ct => { trm.Token = new TokenResponse { AccessToken = accessToken, ExpiresInSeconds = 60 * 60, IssuedUtc = clock.UtcNow }; Interlocked.Increment(ref refreshFnCount); return(Task.FromResult(true)); }, clock, logger); accessToken = "AccessToken1"; var accessToken1 = await trm.GetAccessTokenForRequestAsync(CancellationToken.None); clock.UtcNow += TimeSpan.FromHours(24); accessToken = "AccessToken2"; var accessToken2 = await trm.GetAccessTokenForRequestAsync(CancellationToken.None); Assert.Equal(2, Interlocked.Add(ref refreshFnCount, 0)); Assert.Equal("AccessToken1", accessToken1); Assert.Equal("AccessToken2", accessToken2); }
public async Task FetchesAccessToken() { MockClock clock = new MockClock(new DateTime(2020, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)); TokenRefreshManager refreshManager = null; refreshManager = new TokenRefreshManager(RefreshTokenAsync, clock, new NullLogger()); OidcToken token = new OidcToken(refreshManager); Assert.Equal("very_fake_access_token", await token.GetAccessTokenAsync(default));
public async Task MultipleSoftExpiredTokensConcurrentRefreshes( [CombinatorialValues(1, 2, 3, 6, 11)] int concurrentRefreshCount, [CombinatorialValues(3, 5, 10)] int refreshIterations) { // Test multiple refreshes concurrently and sequentially, // where the sequential refreshes are after the previous token has completely expired. var clock = new MockClock { UtcNow = new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Utc) }; var logger = new NullLogger(); TaskCompletionSource <int> delayTask = null; string accessToken = null; TokenRefreshManager trm = null; trm = new TokenRefreshManager(async ct => { await delayTask.Task; trm.Token = new TokenResponse { AccessToken = Interlocked.Exchange(ref accessToken, accessToken), ExpiresInSeconds = TokenResponse.TokenRefreshTimeWindowSeconds + 1, IssuedUtc = clock.UtcNow }; return(true); }, clock, logger); for (int iteration = 0; iteration < refreshIterations; iteration++) { delayTask = new TaskCompletionSource <int>(); Interlocked.Exchange(ref accessToken, $"AccessToken{iteration}"); var tokenTasks = new Task <string> [concurrentRefreshCount]; for (int i = 0; i < concurrentRefreshCount; i++) { tokenTasks[i] = trm.GetAccessTokenForRequestAsync(CancellationToken.None); } delayTask.SetResult(0); var tokens = await Task.WhenAll(tokenTasks); // It's non-deterministic if the refresh will return the previous or just-retrieved token var expectedAccessTokenA = $"AccessToken{iteration}"; var expectedAccessTokenB = $"AccessToken{iteration - 1}"; foreach (var token in tokens) { Assert.True(token == expectedAccessTokenA || token == expectedAccessTokenB); } clock.UtcNow += TimeSpan.FromSeconds(TokenResponse.TokenRefreshTimeWindowSeconds - TokenResponse.TokenHardExpiryTimeWindowSeconds); } }
public async Task MultipleSoftExpiredTokensConcurrentRefreshes( [CombinatorialValues(1, 2, 3, 6, 11)] int concurrentRefreshCount, [CombinatorialValues(3, 5, 10)] int refreshIterations) { // Test multiple refreshes concurrently and sequentially, // where the sequential refreshes are after the previous token has completely expired. var clock = new MockClock { UtcNow = new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Utc) }; var logger = new NullLogger(); TaskCompletionSource <int> delayTask = null; TokenRefreshManager trm = null; trm = new TokenRefreshManager(async ct => { await Interlocked.CompareExchange(ref delayTask, null, null).Task; trm.Token = new TokenResponse { AccessToken = clock.UtcNow.ToString("O"), ExpiresInSeconds = TokenResponse.TokenRefreshTimeWindowSeconds + 1, IssuedUtc = clock.UtcNow }; return(true); }, clock, logger); HashSet <string> distinctTokens = new HashSet <string>(); for (int iteration = 0; iteration < refreshIterations; iteration++) { clock.UtcNow += TimeSpan.FromSeconds(TokenResponse.TokenRefreshTimeWindowSeconds - TokenResponse.TokenHardExpiryTimeWindowSeconds); Interlocked.Exchange(ref delayTask, new TaskCompletionSource <int>()); var tokenTasks = new Task <string> [concurrentRefreshCount]; for (int i = 0; i < concurrentRefreshCount; i++) { tokenTasks[i] = trm.GetAccessTokenForRequestAsync(CancellationToken.None); } Interlocked.CompareExchange(ref delayTask, null, null).SetResult(0); var tokens = await Task.WhenAll(tokenTasks); // Check all tokens are the same foreach (var token in tokens) { Assert.Equal(tokens[0], token); } distinctTokens.Add(tokens[0]); } Assert.InRange(distinctTokens.Count, refreshIterations / 2, refreshIterations); }
public async Task MultipleHardExpiredTokensConcurrentRefreshes( [CombinatorialValues(1, 2, 3, 6, 11)] int concurrentRefreshCount) { // Test multiple refreshes concurrently and sequentially, // where the sequential refreshes are after the previous token has completely expired. var clock = new MockClock { UtcNow = new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Utc) }; var logger = new NullLogger(); int refreshFnCount = 0; TaskCompletionSource <int> delayTask = null; string accessToken = null; TokenRefreshManager trm = null; trm = new TokenRefreshManager(async ct => { await delayTask.Task; trm.Token = new TokenResponse { AccessToken = Interlocked.Exchange(ref accessToken, accessToken), ExpiresInSeconds = TokenResponse.TokenRefreshTimeWindowSeconds + 1, IssuedUtc = clock.UtcNow }; Interlocked.Increment(ref refreshFnCount); return(true); }, clock, logger); for (int iteration = 0; iteration < 5; iteration++) { delayTask = new TaskCompletionSource <int>(); Interlocked.Exchange(ref accessToken, $"AccessToken{iteration}"); var tokenTasks = new Task <string> [concurrentRefreshCount]; for (int i = 0; i < concurrentRefreshCount; i++) { tokenTasks[i] = trm.GetAccessTokenForRequestAsync(CancellationToken.None); } delayTask.SetResult(0); var tokens = await Task.WhenAll(tokenTasks); foreach (var token in tokens) { Assert.Equal(accessToken, token); } clock.UtcNow += TimeSpan.FromHours(24); } Assert.Equal(5, Interlocked.Add(ref refreshFnCount, 0)); }
public async Task SingleTokenConcurrentRefresh( [CombinatorialValues(1, 2, 3, 6, 11)] int concurrentRefreshCount) { // Test multiple refreshes concurrently and sequentially, // where all refreshes return the same token. var clock = new MockClock { UtcNow = new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Utc) }; var logger = new NullLogger(); int refreshFnCount = 0; TaskCompletionSource <int> delayTask = null; TokenRefreshManager trm = null; trm = new TokenRefreshManager(async ct => { await delayTask.Task; trm.Token = new TokenResponse { AccessToken = "AccessToken1", ExpiresInSeconds = TokenResponse.TokenRefreshTimeWindowSeconds * 2 + 1, IssuedUtc = clock.UtcNow }; Interlocked.Increment(ref refreshFnCount); return(true); }, clock, logger); for (int iteration = 0; iteration < 3; iteration++) { delayTask = new TaskCompletionSource <int>(); var tokenTasks = new Task <string> [concurrentRefreshCount]; for (int i = 0; i < concurrentRefreshCount; i++) { tokenTasks[i] = trm.GetAccessTokenForRequestAsync(CancellationToken.None); } delayTask.SetResult(0); var tokens = await Task.WhenAll(tokenTasks); foreach (var token in tokens) { Assert.Equal("AccessToken1", token); } clock.UtcNow += TimeSpan.FromMinutes(1); } Assert.Equal(1, refreshFnCount); }
public async Task RetriesError_Sync() { var clock = new MockClock(new DateTime(2010, 1, 1, 0, 0, 0, DateTimeKind.Utc)); var logger = new NullLogger(); int refreshCallCount = 0; int refreshCompleted = 0; TokenRefreshManager trm = null; trm = new TokenRefreshManager(ct => { Interlocked.Increment(ref refreshCallCount); return(Task.FromResult(false)); }, clock, logger); var ex = await Assert.ThrowsAsync <InvalidOperationException>(() => trm.GetAccessTokenForRequestAsync(CancellationToken.None)); Assert.Contains("refresh error, refresh error, refresh error", ex.Message); Assert.Equal(TokenRefreshManager.RefreshTimeouts.Length, refreshCallCount); Assert.Equal(0, Interlocked.Add(ref refreshCompleted, 0)); }