public async Task IfValidateAuthenticationStateAsyncReturnsUnrelatedCancelledTask_TreatAsFailure()
        {
            // Arrange
            var validationTcs = new TaskCompletionSource <bool>();
            var authenticationStateChangedCount = 0;

            using var provider = new TestRevalidatingServerAuthenticationStateProvider(
                      TimeSpan.FromMilliseconds(50));
            provider.NextValidationResult = validationTcs.Task;
            provider.SetAuthenticationState(CreateAuthenticationStateTask("test user"));
            provider.AuthenticationStateChanged += _ => { authenticationStateChangedCount++; };

            // Be waiting for the first ValidateAuthenticationStateAsync to complete
            await provider.NextValidateAuthenticationStateAsyncCall;
            var firstRevalidationCall = provider.RevalidationCallLog.Single();

            Assert.Equal(0, authenticationStateChangedCount);

            // Act: ValidateAuthenticationStateAsync returns cancelled task, but the cancellation
            // is unrelated to the CT we supplied
            validationTcs.TrySetCanceled(new CancellationTokenSource().Token);

            // Assert: Since we didn't ask for that operation to be cancelled, this is treated as
            // a failure to validate, so we force a logout
            Assert.Equal(1, authenticationStateChangedCount);
            var newAuthState = await provider.GetAuthenticationStateAsync();

            Assert.False(newAuthState.User.Identity.IsAuthenticated);
            Assert.Null(newAuthState.User.Identity.Name);
        }
        public void AcceptsAndReturnsAuthStateFromHost()
        {
            // Arrange
            using var provider = new TestRevalidatingServerAuthenticationStateProvider(TimeSpan.MaxValue);

            // Act/Assert: Host can supply a value
            var hostAuthStateTask = (new TaskCompletionSource <AuthenticationState>()).Task;

            provider.SetAuthenticationState(hostAuthStateTask);
            Assert.Same(hostAuthStateTask, provider.GetAuthenticationStateAsync());

            // Act/Assert: Host can supply a changed value
            var hostAuthStateTask2 = (new TaskCompletionSource <AuthenticationState>()).Task;

            provider.SetAuthenticationState(hostAuthStateTask2);
            Assert.Same(hostAuthStateTask2, provider.GetAuthenticationStateAsync());
        }
        public async Task IfValidateAuthenticationStateAsyncReturnsTrue_ContinuesRevalidating()
        {
            // Arrange
            using var provider = new TestRevalidatingServerAuthenticationStateProvider(
                      TimeSpan.FromMilliseconds(50));
            provider.SetAuthenticationState(CreateAuthenticationStateTask("test user"));
            provider.NextValidationResult = Task.FromResult(true);
            var didNotifyAuthenticationStateChanged = false;

            provider.AuthenticationStateChanged += _ => { didNotifyAuthenticationStateChanged = true; };

            // Act
            for (var i = 0; i < 10; i++)
            {
                await provider.NextValidateAuthenticationStateAsyncCall;
            }

            // Assert
            Assert.Equal(10, provider.RevalidationCallLog.Count);
            Assert.False(didNotifyAuthenticationStateChanged);
            Assert.Equal("test user", (await provider.GetAuthenticationStateAsync()).User.Identity.Name);
        }
        public async Task SuppliesCancellationTokenThatSignalsWhenRevalidationLoopIsBeingDiscarded()
        {
            // Arrange
            var validationTcs = new TaskCompletionSource <bool>();
            var authenticationStateChangedCount = 0;

            using var provider = new TestRevalidatingServerAuthenticationStateProvider(
                      TimeSpan.FromMilliseconds(50));
            provider.NextValidationResult = validationTcs.Task;
            provider.SetAuthenticationState(CreateAuthenticationStateTask("test user"));
            provider.AuthenticationStateChanged += _ => { authenticationStateChangedCount++; };

            // Act/Assert 1: token isn't cancelled initially
            await provider.NextValidateAuthenticationStateAsyncCall;
            var firstRevalidationCall = provider.RevalidationCallLog.Single();

            Assert.False(firstRevalidationCall.CancellationToken.IsCancellationRequested);
            Assert.Equal(0, authenticationStateChangedCount);

            // Have the task throw a TCE to show this doesn't get treated as a failure
            firstRevalidationCall.CancellationToken.Register(() => validationTcs.TrySetCanceled(firstRevalidationCall.CancellationToken));

            // Act/Assert 2: token is cancelled when the loop is superseded
            provider.NextValidationResult = Task.FromResult(true);
            provider.SetAuthenticationState(CreateAuthenticationStateTask("different user"));
            Assert.True(firstRevalidationCall.CancellationToken.IsCancellationRequested);

            // Since we asked for that operation to be cancelled, we don't treat it as a failure and
            // don't force a logout
            Assert.Equal(1, authenticationStateChangedCount);
            Assert.Equal("different user", (await provider.GetAuthenticationStateAsync()).User.Identity.Name);

            // Subsequent revalidation can complete successfully
            await provider.NextValidateAuthenticationStateAsyncCall;

            Assert.Collection(provider.RevalidationCallLog.Skip(1),
                              call => Assert.Equal("different user", call.AuthenticationState.User.Identity.Name));
        }