public async void TestTimeout() { var cachedToken = "cachedToken"; var req = new HttpRequestMessage(); req.RequestUri = new Uri("http://example.com"); var config = new ForgeConfiguration() { ClientId = "ClientId", ClientSecret = "ClientSecret" }; var sink = new Mock <HttpMessageHandler>(MockBehavior.Strict); sink.Protected().As <HttpMessageInvoker>().Setup(o => o.SendAsync(It.Is <HttpRequestMessage>(r => r.RequestUri == req.RequestUri && r.Headers.Authorization.Parameter == cachedToken), It.IsAny <CancellationToken>())) .ReturnsAsync(new HttpResponseMessage() { StatusCode = System.Net.HttpStatusCode.GatewayTimeout }, TimeSpan.FromSeconds(12)); var fh = new TweakableForgeHandler(Options.Create(config)) { InnerHandler = sink.Object }; var scope = "somescope"; fh.TokenCache.Add(scope, $"Bearer {cachedToken}", TimeSpan.FromSeconds(10)); var invoker = new HttpMessageInvoker(fh); req.Properties.Add(ForgeConfiguration.ScopeKey, scope); await Assert.ThrowsAsync <Polly.Timeout.TimeoutRejectedException>(async() => await invoker.SendAsync(req, new CancellationToken())); sink.VerifyAll(); }
public async void TestRefreshExpiredTokenByOneThreadOnly() { var newToken = "newToken"; var cachedToken = "cachedToken"; var requestUri = new Uri("http://example.com"); var config = new ForgeConfiguration() { ClientId = "ClientId", ClientSecret = "ClientSecret" }; var sink = new Mock <HttpMessageHandler>(MockBehavior.Strict); sink.Protected().As <HttpMessageInvoker>().Setup(o => o.SendAsync(It.Is <HttpRequestMessage>(r => r.RequestUri == config.AuthenticationAddress), It.IsAny <CancellationToken>())) // some artifical delay to ensure that the other thread will attempt to enter the critical section .ReturnsAsync(new HttpResponseMessage() { Content = new StringContent(JsonConvert.SerializeObject(new Dictionary <string, string> { { "token_type", "Bearer" }, { "access_token", newToken }, { "expires_in", "3" } })), StatusCode = System.Net.HttpStatusCode.OK }, TweakableForgeHandler.DefaultTimeout / 2 ); sink.Protected().As <HttpMessageInvoker>().Setup(o => o.SendAsync(It.Is <HttpRequestMessage>(r => r.RequestUri == requestUri && r.Headers.Authorization.Parameter == newToken), It.IsAny <CancellationToken>())) .ReturnsAsync(new HttpResponseMessage() { StatusCode = System.Net.HttpStatusCode.OK }); var fh = new TweakableForgeHandler(Options.Create(config)) { InnerHandler = sink.Object }; var scope = "somescope"; //we have token but it is expired already fh.TokenCache.Add(scope, $"Bearer {cachedToken}", TimeSpan.FromSeconds(0)); //launch 2 threads to make parallel requests Func <Task> lambda = async() => { var req = new HttpRequestMessage(); req.RequestUri = requestUri; var invoker = new HttpMessageInvoker(fh); req.Options.Set(ForgeConfiguration.ScopeKey, scope); await invoker.SendAsync(req, CancellationToken.None); }; await Task.WhenAll(lambda(), lambda()); // We expect exactly one auth call sink.Protected().As <HttpMessageInvoker>().Verify(o => o.SendAsync(It.Is <HttpRequestMessage>(r => r.RequestUri == config.AuthenticationAddress), It.IsAny <CancellationToken>()), Times.Once()); sink.VerifyAll(); }
public async void TestCorrectNumberOfRetries() { var cachedToken = "cachedToken"; var req = new HttpRequestMessage(); req.RequestUri = new Uri("http://example.com"); var config = new ForgeConfiguration() { ClientId = "ClientId", ClientSecret = "ClientSecret" }; var gatewayTimeout = new HttpResponseMessage() { StatusCode = System.Net.HttpStatusCode.GatewayTimeout }; var tooManyRequests = new HttpResponseMessage { StatusCode = (System.Net.HttpStatusCode) 429 }; tooManyRequests.Headers.RetryAfter = new System.Net.Http.Headers.RetryConditionHeaderValue(TimeSpan.FromSeconds(2)); var sink = new Mock <HttpMessageHandler>(MockBehavior.Strict); sink.Protected().As <HttpMessageInvoker>().SetupSequence(o => o.SendAsync(It.Is <HttpRequestMessage>(r => r.RequestUri == req.RequestUri && r.Headers.Authorization.Parameter == cachedToken), It.IsAny <CancellationToken>())) .ReturnsAsync(tooManyRequests) .ReturnsAsync(tooManyRequests) .ReturnsAsync(tooManyRequests) .ThrowsAsync(new HttpRequestException()) .ReturnsAsync(gatewayTimeout) .ReturnsAsync(gatewayTimeout); var fh = new TweakableForgeHandler(Options.Create(config)) { InnerHandler = sink.Object }; var scope = "somescope"; fh.TokenCache.Add(scope, $"Bearer {cachedToken}", TimeSpan.FromSeconds(10)); var invoker = new HttpMessageInvoker(fh); req.Options.Set(ForgeConfiguration.ScopeKey, scope); var resp = await invoker.SendAsync(req, CancellationToken.None); Assert.Equal(System.Net.HttpStatusCode.GatewayTimeout, resp.StatusCode); // We retry 5 times so expect 6 calls sink.Protected().As <HttpMessageInvoker>().Verify(o => o.SendAsync(It.IsAny <HttpRequestMessage>(), It.IsAny <CancellationToken>()), Times.Exactly(6)); sink.VerifyAll(); }
public async void TestRetryOnceOnAuthenticationFailure() { var newToken = "newToken"; var cachedToken = "cachedToken"; var req = new HttpRequestMessage(); req.RequestUri = new Uri("http://example.com"); var config = new ForgeConfiguration() { ClientId = "ClientId", ClientSecret = "ClientSecret" }; var sink = new Mock <HttpMessageHandler>(MockBehavior.Strict); sink.Protected().As <HttpMessageInvoker>().Setup(o => o.SendAsync(It.Is <HttpRequestMessage>(r => r.RequestUri == req.RequestUri && r.Headers.Authorization.Parameter == cachedToken), It.IsAny <CancellationToken>())) .ReturnsAsync(new HttpResponseMessage() { StatusCode = System.Net.HttpStatusCode.Unauthorized, RequestMessage = req }); sink.Protected().As <HttpMessageInvoker>().Setup(o => o.SendAsync(It.Is <HttpRequestMessage>(r => r.RequestUri == config.AuthenticationAddress), It.IsAny <CancellationToken>())) .ReturnsAsync(new HttpResponseMessage() { Content = new StringContent(JsonConvert.SerializeObject(new Dictionary <string, string> { { "token_type", "Bearer" }, { "access_token", newToken }, { "expires_in", "3" } })), StatusCode = System.Net.HttpStatusCode.OK }); sink.Protected().As <HttpMessageInvoker>().Setup(o => o.SendAsync(It.Is <HttpRequestMessage>(r => r.RequestUri == req.RequestUri && r.Headers.Authorization.Parameter == newToken), It.IsAny <CancellationToken>())) .ReturnsAsync(new HttpResponseMessage() { StatusCode = System.Net.HttpStatusCode.OK }); var fh = new TweakableForgeHandler(Options.Create(config)) { InnerHandler = sink.Object }; var scope = "somescope"; //we have token but it bad for some reason (maybe revoked) fh.TokenCache.Add(scope, $"Bearer {cachedToken}", TimeSpan.FromSeconds(300)); var invoker = new HttpMessageInvoker(fh); req.Options.Set(ForgeConfiguration.ScopeKey, scope); await invoker.SendAsync(req, CancellationToken.None); sink.VerifyAll(); }
/// <summary> /// Create all required components for custom timeout validation. /// </summary> /// <param name="allowedTimeInSec">Allowed time in seconds.</param> /// <param name="responseTime">Actual response time.</param> /// <returns> /// Tuple with: /// * mock to validate after tests are complete. /// * functor to perform mocked HTTP request/response operation. /// </returns> private (Mock <HttpMessageHandler> sink, Func <Task <HttpResponseMessage> > requestSender) GetReady(int allowedTimeInSec, TimeSpan responseTime) { var req = RequestWithTimeout(allowedTimeInSec); var sink = MakeSink(req, responseTime); var fh = new TweakableForgeHandler(Options.Create(_forgeConfig)) { InnerHandler = sink.Object }; fh.TokenCache.Add(Scope, $"Bearer {CachedToken}", TimeSpan.FromSeconds(10)); var invoker = new HttpMessageInvoker(fh); return(sink, () => invoker.SendAsync(req, new CancellationToken())); }
public async void TestCircuitBreaker() { var cachedToken = "cachedToken"; var req = new HttpRequestMessage(); req.RequestUri = new Uri("http://example.com"); var config = new ForgeConfiguration() { ClientId = "ClientId", ClientSecret = "ClientSecret" }; var sink = new Mock <HttpMessageHandler>(MockBehavior.Strict); sink.Protected().As <HttpMessageInvoker>().Setup(o => o.SendAsync(It.Is <HttpRequestMessage>(r => r.RequestUri == req.RequestUri && r.Headers.Authorization.Parameter == cachedToken), It.IsAny <CancellationToken>())) .ReturnsAsync(new HttpResponseMessage() { StatusCode = System.Net.HttpStatusCode.InternalServerError }); var fh = new TweakableForgeHandler(Options.Create(config)) { InnerHandler = sink.Object }; var scope = "somescope"; fh.TokenCache.Add(scope, $"Bearer {cachedToken}", TimeSpan.FromSeconds(10)); var invoker = new HttpMessageInvoker(fh); req.Options.Set(ForgeConfiguration.ScopeKey, scope); // We tolerate 3 failures before we break the circuit for (int i = 0; i < 3; i++) { var resp = await invoker.SendAsync(req, CancellationToken.None); Assert.Equal(System.Net.HttpStatusCode.InternalServerError, resp.StatusCode); } await Assert.ThrowsAsync <Polly.CircuitBreaker.BrokenCircuitException <HttpResponseMessage> >(async() => await invoker.SendAsync(req, CancellationToken.None)); sink.Protected().As <HttpMessageInvoker>().Verify(o => o.SendAsync(It.IsAny <HttpRequestMessage>(), It.IsAny <CancellationToken>()), Times.Exactly(3)); sink.VerifyAll(); }
public async void TestUseGoodToken() { var cachedToken = "cachedToken"; var req = new HttpRequestMessage(); req.RequestUri = new Uri("http://example.com"); var config = new ForgeConfiguration() { ClientId = "ClientId", ClientSecret = "ClientSecret" }; var sink = new Mock <HttpMessageHandler>(MockBehavior.Strict); sink.Protected().As <HttpMessageInvoker>().Setup(o => o.SendAsync(It.Is <HttpRequestMessage>(r => r.RequestUri == req.RequestUri && r.Headers.Authorization.Parameter == cachedToken), It.IsAny <CancellationToken>())) .ReturnsAsync(new HttpResponseMessage() { StatusCode = System.Net.HttpStatusCode.OK }); var fh = new TweakableForgeHandler(Options.Create(config)) { InnerHandler = sink.Object }; var scope = "somescope"; fh.TokenCache.Add(scope, $"Bearer {cachedToken}", TimeSpan.FromSeconds(10)); var invoker = new HttpMessageInvoker(fh); req.Options.Set(ForgeConfiguration.ScopeKey, scope); var resp = await invoker.SendAsync(req, CancellationToken.None); Assert.Equal(System.Net.HttpStatusCode.OK, resp.StatusCode); // We expect exactly one network call sink.Protected().As <HttpMessageInvoker>().Verify(o => o.SendAsync(It.IsAny <HttpRequestMessage>(), It.IsAny <CancellationToken>()), Times.Once()); sink.VerifyAll(); }
public async void TestNoRefreshOnClientProvidedToken() { var token = "blabla"; var req = new HttpRequestMessage(); req.RequestUri = new Uri("http://example.com"); var config = new ForgeConfiguration() { ClientId = "ClientId", ClientSecret = "ClientSecret" }; var sink = new Mock <HttpMessageHandler>(MockBehavior.Strict); sink.Protected().As <HttpMessageInvoker>().Setup(o => o.SendAsync(It.Is <HttpRequestMessage>(r => r.RequestUri == req.RequestUri && r.Headers.Authorization.Parameter == token), It.IsAny <CancellationToken>())) .ReturnsAsync(new HttpResponseMessage() { StatusCode = System.Net.HttpStatusCode.Unauthorized }); var fh = new TweakableForgeHandler(Options.Create(config)) { InnerHandler = sink.Object }; var scope = "somescope"; var invoker = new HttpMessageInvoker(fh); req.Options.Set(ForgeConfiguration.ScopeKey, scope); req.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); var resp = await invoker.SendAsync(req, CancellationToken.None); Assert.Equal(System.Net.HttpStatusCode.Unauthorized, resp.StatusCode); // We expect exactly one network call sink.Protected().As <HttpMessageInvoker>().Verify(o => o.SendAsync(It.IsAny <HttpRequestMessage>(), It.IsAny <CancellationToken>()), Times.Once()); sink.VerifyAll(); }