public async Task SendAsync_ShouldRaise_SessionExpired_WhenRefreshFailed()
        {
            // Arrange
            _tokenClient.RefreshTokenAsync(Arg.Any <CancellationToken>())
            .Returns(ApiResponseResult <RefreshTokenResponse> .Fail(HttpStatusCode.BadRequest, "Refresh failed"));
            var handler = new UnauthorizedResponseHandler(_tokenClient, _tokenStorage, _userStorage)
            {
                InnerHandler = _innerHandler
            };
            var client = new HttpClient(handler)
            {
                BaseAddress = _baseAddress
            };

            _innerHandler.Expect(HttpMethod.Get, "https://api.protonvpn.ch/logicals")
            .Respond(HttpStatusCode.Unauthorized);

            using (var monitoredSubject = handler.Monitor())
            {
                // Act
                var request = new HttpRequestMessage(HttpMethod.Get, "/logicals");
                await client.SendAsync(request);

                // Assert
                monitoredSubject.Should().Raise(nameof(UnauthorizedResponseHandler.SessionExpired));
                _innerHandler.VerifyNoOutstandingExpectation();
            }
        }
        public async Task SendAsync_ShouldBe_InnerHandlerSendAsync()
        {
            // Arrange
            var handler = new UnauthorizedResponseHandler(_tokenClient, _tokenStorage, _userStorage)
            {
                InnerHandler = _innerHandler
            };
            var client = new HttpClient(handler)
            {
                BaseAddress = _baseAddress
            };

            var response = new HttpResponseMessage(HttpStatusCode.OK);

            _innerHandler.Expect(HttpMethod.Get, "https://api.protonvpn.ch/logicals")
            .Respond(req => response);

            var request = new HttpRequestMessage(HttpMethod.Get, "/logicals");

            // Act
            var result = await client.SendAsync(request);

            // Assert
            result.Should().BeSameAs(response);
            _innerHandler.VerifyNoOutstandingExpectation();
        }
        public async Task SendAsync_ShouldSet_TokenStorage_Tokens()
        {
            // Arrange
            _tokenClient.RefreshTokenAsync(Arg.Any <CancellationToken>())
            .Returns(ApiResponseResult <RefreshTokenResponse> .Ok(
                         new RefreshTokenResponse {
                AccessToken = "New access token", RefreshToken = "New refresh token"
            }));
            var handler = new UnauthorizedResponseHandler(_tokenClient, _tokenStorage, _userStorage)
            {
                InnerHandler = _innerHandler
            };
            var client = new HttpClient(handler)
            {
                BaseAddress = _baseAddress
            };

            _innerHandler.Expect(HttpMethod.Get, "https://api.protonvpn.ch/logicals")
            .Respond(HttpStatusCode.Unauthorized);

            // Act
            var request = new HttpRequestMessage(HttpMethod.Get, "/logicals");
            await client.SendAsync(request);

            // Assert
            _tokenStorage.AccessToken.Should().Be("New access token");
            _tokenStorage.RefreshToken.Should().Be("New refresh token");
        }
        public async Task SendAsync_ShouldRepeatRequest_WithRefreshedAccessToken()
        {
            // Arrange
            _tokenClient.RefreshTokenAsync(Arg.Any <CancellationToken>())
            .Returns(ApiResponseResult <RefreshTokenResponse> .Ok(
                         new RefreshTokenResponse {
                AccessToken = "New access token", RefreshToken = "New refresh token"
            }));
            var handler = new UnauthorizedResponseHandler(_tokenClient, _tokenStorage, _userStorage)
            {
                InnerHandler = _innerHandler
            };
            var client = new HttpClient(handler)
            {
                BaseAddress = _baseAddress
            };

            _innerHandler.Expect(HttpMethod.Get, "https://api.protonvpn.ch/logicals")
            .Respond(HttpStatusCode.Unauthorized);
            _innerHandler.Expect(HttpMethod.Get, "https://api.protonvpn.ch/logicals")
            .WithHeaders("Authorization", "Bearer New access token")
            .Respond(HttpStatusCode.OK);

            // Act
            var request = new HttpRequestMessage(HttpMethod.Get, "/logicals");
            await client.SendAsync(request);

            // Assert
            _innerHandler.VerifyNoOutstandingExpectation();
        }
        public async Task SendAsync_ShouldBe_InnerHandlerSendAsync_WhenRepeatedRequest()
        {
            // Arrange
            UnauthorizedResponseHandler handler = new UnauthorizedResponseHandler(_tokenClient, _tokenStorage, _userStorage, _logger)
            {
                InnerHandler = _innerHandler
            };
            HttpClient client = new HttpClient(handler)
            {
                BaseAddress = _baseAddress
            };

            HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);

            _innerHandler.Expect(HttpMethod.Get, "https://api.protonvpn.ch/logicals")
            .Respond(HttpStatusCode.Unauthorized);
            _innerHandler.Expect(HttpMethod.Get, "https://api.protonvpn.ch/logicals")
            .Respond(req => response);

            // Act
            HttpRequestMessage  request = new HttpRequestMessage(HttpMethod.Get, "/logicals");
            HttpResponseMessage result  = await client.SendAsync(request);

            // Assert
            result.Should().BeSameAs(response);
        }
        public async Task SendAsync_ShouldNotCall_TokenClient_RefreshTokenAsync_WhenTokenClientRefreshTokenAsyncThrowsArgumentNullException()
        {
            // Arrange
            string exceptionMessage         = "The RefreshToken in RefreshTokenData can't be null.";
            ArgumentNullException exception = new ArgumentNullException(exceptionMessage);

            _tokenClient.RefreshTokenAsync(Arg.Any <CancellationToken>())
            .Throws(exception);

            UnauthorizedResponseHandler handler = new UnauthorizedResponseHandler(_tokenClient, _tokenStorage, _userStorage, _logger)
            {
                InnerHandler = _innerHandler
            };
            HttpClient client = new HttpClient(handler)
            {
                BaseAddress = _baseAddress
            };

            _innerHandler.Expect(HttpMethod.Get, "https://api.protonvpn.ch/logicals")
            .Respond(HttpStatusCode.Unauthorized);

            // Act
            HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "/logicals");
            await client.SendAsync(request);

            // Assert
            await _tokenClient.Received(1).RefreshTokenAsync(Arg.Any <CancellationToken>());

            _logger.Received(1).Error($"An error occurred when refreshing the auth token: {exceptionMessage}");
        }
        public async Task SendAsync_ShouldCall_TokenClient_RefreshTokenAsync_WhenUnauthorized()
        {
            // Arrange
            var handler = new UnauthorizedResponseHandler(_tokenClient, _tokenStorage, _userStorage)
            {
                InnerHandler = _innerHandler
            };
            var client = new HttpClient(handler)
            {
                BaseAddress = _baseAddress
            };

            _innerHandler.Expect(HttpMethod.Get, "https://api.protonvpn.ch/logicals")
            .Respond(HttpStatusCode.Unauthorized);

            // Act
            var request = new HttpRequestMessage(HttpMethod.Get, "/logicals");
            await client.SendAsync(request);

            // Assert
            await _tokenClient.Received(1).RefreshTokenAsync(Arg.Any <CancellationToken>());
        }
        public async Task SendAsync_ShouldRetryWithNewToken_WhenRefreshedWhileRequesting()
        {
            // Arrange
            var breakpointHandler = new BreakpointHandler {
                InnerHandler = _innerHandler
            };
            var requestBreakpoint     = breakpointHandler.Breakpoint;
            var breakpointTokenClient = new BreakpointTokenClient(_tokenClient);
            var tokenClientBreakpoint = breakpointTokenClient.Breakpoint;
            var handler = new UnauthorizedResponseHandler(breakpointTokenClient, _tokenStorage, _userStorage)
            {
                InnerHandler = breakpointHandler
            };
            var client = new HttpClient(handler)
            {
                BaseAddress = _baseAddress
            };

            _tokenClient.RefreshTokenAsync(Arg.Any <CancellationToken>())
            .Returns(ApiResponseResult <RefreshTokenResponse> .Ok(
                         new RefreshTokenResponse {
                AccessToken = "New access token", RefreshToken = "New refresh token"
            }));

            var response = new HttpResponseMessage(HttpStatusCode.OK);

            // Act
            var task1 = Task.CompletedTask;
            var task2 = Task.CompletedTask;

            try
            {
                // Sending first request and pausing it
                var request1 = new HttpRequestMessage(HttpMethod.Get, "/vpn");
                task1 = client.SendAsync(request1);
                var request1Hit = await requestBreakpoint.WaitForHit().TimeoutAfter(TestTimeout);

                // Sending second request and pausing it
                var request2 = new HttpRequestMessage(HttpMethod.Get, "/profiles");
                task2 = client.SendAsync(request2);
                var request2Hit = await requestBreakpoint.WaitForHit().TimeoutAfter(TestTimeout);

                // Continue first request and get Unauthorized
                _innerHandler.Expect(HttpMethod.Get, "https://api.protonvpn.ch/vpn")
                .Respond(HttpStatusCode.Unauthorized);
                request1Hit.Continue();

                // First request initiated token refresh
                await tokenClientBreakpoint.WaitForHitAndContinue().TimeoutAfter(TestTimeout);

                // First request retried with new tokens
                request1Hit = await requestBreakpoint.WaitForHit().TimeoutAfter(TestTimeout);

                _innerHandler.Expect(HttpMethod.Get, "https://api.protonvpn.ch/vpn")
                .WithHeaders("Authorization", "Bearer New access token")
                .Respond(req => response);
                request1Hit.Continue();
                await task1.TimeoutAfter(TestTimeout);

                // Second request continues and gets Unauthorized
                _innerHandler.Expect(HttpMethod.Get, "https://api.protonvpn.ch/profiles")
                .Respond(HttpStatusCode.Unauthorized);
                request2Hit.Continue();

                // Second request retried with new access token
                request2Hit = await requestBreakpoint.WaitForHit().TimeoutAfter(TestTimeout);

                _innerHandler.Expect(HttpMethod.Get, "https://api.protonvpn.ch/profiles")
                .WithHeaders("Authorization", "Bearer New access token")
                .Respond(req => response);
                request2Hit.Continue();
            }
            finally
            {
                await task1.TimeoutAfter(TestTimeout);

                await task2.TimeoutAfter(TestTimeout);
            }

            // Assert
            await _tokenClient.Received(1).RefreshTokenAsync(Arg.Any <CancellationToken>());

            _innerHandler.VerifyNoOutstandingExpectation();
        }