File: AuthenticationMetricsTest.cs
Web Access
Project: src\src\Security\Authentication\test\Microsoft.AspNetCore.Authentication.Test.csproj (Microsoft.AspNetCore.Authentication.Test)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Security.Claims;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.InternalTesting;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Diagnostics.Metrics.Testing;
using Moq;
 
namespace Microsoft.AspNetCore.Authentication;
 
public class AuthenticationMetricsTest
{
    [Fact]
    public async Task Authenticate_Success()
    {
        // Arrange
        var authenticationHandler = new Mock<IAuthenticationHandler>();
        authenticationHandler.Setup(h => h.AuthenticateAsync()).Returns(Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(new ClaimsPrincipal(), "custom"))));
 
        var meterFactory = new TestMeterFactory();
        var httpContext = new DefaultHttpContext();
        var authenticationService = CreateAuthenticationService(authenticationHandler.Object, meterFactory);
        var meter = meterFactory.Meters.Single();
 
        using var authenticationRequestsCollector = new MetricCollector<double>(meterFactory, AuthenticationMetrics.MeterName, "aspnetcore.authentication.authenticate.duration");
 
        // Act
        await authenticationService.AuthenticateAsync(httpContext, scheme: "custom");
 
        // Assert
        Assert.Equal(AuthenticationMetrics.MeterName, meter.Name);
        Assert.Null(meter.Version);
 
        var measurement = Assert.Single(authenticationRequestsCollector.GetMeasurementSnapshot());
        Assert.True(measurement.Value > 0);
        Assert.Equal("custom", (string)measurement.Tags["aspnetcore.authentication.scheme"]);
        Assert.Equal("success", (string)measurement.Tags["aspnetcore.authentication.result"]);
        Assert.False(measurement.Tags.ContainsKey("error.type"));
    }
 
    [Fact]
    public async Task Authenticate_Failure()
    {
        // Arrange
        var authenticationHandler = new Mock<IAuthenticationHandler>();
        authenticationHandler.Setup(h => h.AuthenticateAsync()).Returns(Task.FromResult(AuthenticateResult.Fail("Authentication failed")));
 
        var meterFactory = new TestMeterFactory();
        var httpContext = new DefaultHttpContext();
        var authenticationService = CreateAuthenticationService(authenticationHandler.Object, meterFactory);
        var meter = meterFactory.Meters.Single();
 
        using var authenticationRequestsCollector = new MetricCollector<double>(meterFactory, AuthenticationMetrics.MeterName, "aspnetcore.authentication.authenticate.duration");
 
        // Act
        await authenticationService.AuthenticateAsync(httpContext, scheme: "custom");
 
        // Assert
        Assert.Equal(AuthenticationMetrics.MeterName, meter.Name);
        Assert.Null(meter.Version);
 
        var measurement = Assert.Single(authenticationRequestsCollector.GetMeasurementSnapshot());
        Assert.True(measurement.Value > 0);
        Assert.Equal("custom", (string)measurement.Tags["aspnetcore.authentication.scheme"]);
        Assert.Equal("failure", (string)measurement.Tags["aspnetcore.authentication.result"]);
        Assert.Equal("Microsoft.AspNetCore.Authentication.AuthenticationFailureException", (string)measurement.Tags["error.type"]);
    }
 
    [Fact]
    public async Task Authenticate_NoResult()
    {
        // Arrange
        var authenticationHandler = new Mock<IAuthenticationHandler>();
        authenticationHandler.Setup(h => h.AuthenticateAsync()).Returns(Task.FromResult(AuthenticateResult.NoResult()));
 
        var meterFactory = new TestMeterFactory();
        var httpContext = new DefaultHttpContext();
        var authenticationService = CreateAuthenticationService(authenticationHandler.Object, meterFactory);
        var meter = meterFactory.Meters.Single();
 
        using var authenticationRequestsCollector = new MetricCollector<double>(meterFactory, AuthenticationMetrics.MeterName, "aspnetcore.authentication.authenticate.duration");
 
        // Act
        await authenticationService.AuthenticateAsync(httpContext, scheme: "custom");
 
        // Assert
        Assert.Equal(AuthenticationMetrics.MeterName, meter.Name);
        Assert.Null(meter.Version);
 
        var measurement = Assert.Single(authenticationRequestsCollector.GetMeasurementSnapshot());
        Assert.True(measurement.Value > 0);
        Assert.Equal("custom", (string)measurement.Tags["aspnetcore.authentication.scheme"]);
        Assert.Equal("none", (string)measurement.Tags["aspnetcore.authentication.result"]);
        Assert.False(measurement.Tags.ContainsKey("error.type"));
    }
 
    [Fact]
    public async Task Authenticate_ExceptionThrownInHandler()
    {
        // Arrange
        var authenticationHandler = new Mock<IAuthenticationHandler>();
        authenticationHandler.Setup(h => h.AuthenticateAsync()).Throws(new InvalidOperationException("An error occurred during authentication"));
 
        var meterFactory = new TestMeterFactory();
        var httpContext = new DefaultHttpContext();
        var authenticationService = CreateAuthenticationService(authenticationHandler.Object, meterFactory);
        var meter = meterFactory.Meters.Single();
 
        using var authenticationRequestsCollector = new MetricCollector<double>(meterFactory, AuthenticationMetrics.MeterName, "aspnetcore.authentication.authenticate.duration");
 
        // Act
        var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => authenticationService.AuthenticateAsync(httpContext, scheme: "custom"));
 
        // Assert
        Assert.Equal("An error occurred during authentication", ex.Message);
        Assert.Equal(AuthenticationMetrics.MeterName, meter.Name);
        Assert.Null(meter.Version);
 
        var measurement = Assert.Single(authenticationRequestsCollector.GetMeasurementSnapshot());
        Assert.True(measurement.Value > 0);
        Assert.Equal("custom", (string)measurement.Tags["aspnetcore.authentication.scheme"]);
        Assert.Equal("System.InvalidOperationException", (string)measurement.Tags["error.type"]);
        Assert.False(measurement.Tags.ContainsKey("aspnetcore.authentication.result"));
    }
 
    [Fact]
    public async Task Challenge()
    {
        // Arrange
        var meterFactory = new TestMeterFactory();
        var httpContext = new DefaultHttpContext();
        var authenticationService = CreateAuthenticationService(Mock.Of<IAuthenticationHandler>(), meterFactory);
        var meter = meterFactory.Meters.Single();
 
        using var challengesCollector = new MetricCollector<long>(meterFactory, AuthenticationMetrics.MeterName, "aspnetcore.authentication.challenges");
 
        // Act
        await authenticationService.ChallengeAsync(httpContext, scheme: "custom", properties: null);
 
        // Assert
        Assert.Equal(AuthenticationMetrics.MeterName, meter.Name);
        Assert.Null(meter.Version);
 
        var measurement = Assert.Single(challengesCollector.GetMeasurementSnapshot());
        Assert.Equal(1, measurement.Value);
        Assert.Equal("custom", (string)measurement.Tags["aspnetcore.authentication.scheme"]);
    }
 
    [Fact]
    public async Task Challenge_ExceptionThrownInHandler()
    {
        // Arrange
        var authenticationHandler = new Mock<IAuthenticationHandler>();
        authenticationHandler.Setup(h => h.ChallengeAsync(It.IsAny<AuthenticationProperties>())).Throws(new InvalidOperationException("An error occurred during challenge"));
 
        var meterFactory = new TestMeterFactory();
        var httpContext = new DefaultHttpContext();
        var authenticationService = CreateAuthenticationService(authenticationHandler.Object, meterFactory);
        var meter = meterFactory.Meters.Single();
 
        using var challengesCollector = new MetricCollector<long>(meterFactory, AuthenticationMetrics.MeterName, "aspnetcore.authentication.challenges");
 
        // Act
        var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => authenticationService.ChallengeAsync(httpContext, scheme: "custom", properties: null));
 
        // Assert
        Assert.Equal("An error occurred during challenge", ex.Message);
        Assert.Equal(AuthenticationMetrics.MeterName, meter.Name);
        Assert.Null(meter.Version);
 
        var measurement = Assert.Single(challengesCollector.GetMeasurementSnapshot());
        Assert.Equal(1, measurement.Value);
        Assert.Equal("custom", (string)measurement.Tags["aspnetcore.authentication.scheme"]);
        Assert.Equal("System.InvalidOperationException", (string)measurement.Tags["error.type"]);
    }
 
    [Fact]
    public async Task Forbid()
    {
        // Arrange
        var meterFactory = new TestMeterFactory();
        var httpContext = new DefaultHttpContext();
        var authenticationService = CreateAuthenticationService(Mock.Of<IAuthenticationHandler>(), meterFactory);
        var meter = meterFactory.Meters.Single();
 
        using var forbidsCollector = new MetricCollector<long>(meterFactory, AuthenticationMetrics.MeterName, "aspnetcore.authentication.forbids");
 
        // Act
        await authenticationService.ForbidAsync(httpContext, scheme: "custom", properties: null);
 
        // Assert
        Assert.Equal(AuthenticationMetrics.MeterName, meter.Name);
        Assert.Null(meter.Version);
 
        var measurement = Assert.Single(forbidsCollector.GetMeasurementSnapshot());
        Assert.Equal(1, measurement.Value);
        Assert.Equal("custom", (string)measurement.Tags["aspnetcore.authentication.scheme"]);
    }
 
    [Fact]
    public async Task Forbid_ExceptionThrownInHandler()
    {
        // Arrange
        var authenticationHandler = new Mock<IAuthenticationHandler>();
        authenticationHandler.Setup(h => h.ForbidAsync(It.IsAny<AuthenticationProperties>())).Throws(new InvalidOperationException("An error occurred during forbid"));
 
        var meterFactory = new TestMeterFactory();
        var httpContext = new DefaultHttpContext();
        var authenticationService = CreateAuthenticationService(authenticationHandler.Object, meterFactory);
        var meter = meterFactory.Meters.Single();
 
        using var forbidsCollector = new MetricCollector<long>(meterFactory, AuthenticationMetrics.MeterName, "aspnetcore.authentication.forbids");
 
        // Act
        var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => authenticationService.ForbidAsync(httpContext, scheme: "custom", properties: null));
 
        // Assert
        Assert.Equal("An error occurred during forbid", ex.Message);
        Assert.Equal(AuthenticationMetrics.MeterName, meter.Name);
        Assert.Null(meter.Version);
 
        var measurement = Assert.Single(forbidsCollector.GetMeasurementSnapshot());
        Assert.Equal(1, measurement.Value);
        Assert.Equal("custom", (string)measurement.Tags["aspnetcore.authentication.scheme"]);
        Assert.Equal("System.InvalidOperationException", (string)measurement.Tags["error.type"]);
    }
 
    [Fact]
    public async Task SignIn()
    {
        // Arrange
        var meterFactory = new TestMeterFactory();
        var httpContext = new DefaultHttpContext();
        var authenticationService = CreateAuthenticationService(Mock.Of<IAuthenticationSignInHandler>(), meterFactory);
        var meter = meterFactory.Meters.Single();
 
        using var signInsCollector = new MetricCollector<long>(meterFactory, AuthenticationMetrics.MeterName, "aspnetcore.authentication.sign_ins");
 
        // Act
        await authenticationService.SignInAsync(httpContext, scheme: "custom", new ClaimsPrincipal(), properties: null);
 
        // Assert
        Assert.Equal(AuthenticationMetrics.MeterName, meter.Name);
        Assert.Null(meter.Version);
 
        var measurement = Assert.Single(signInsCollector.GetMeasurementSnapshot());
        Assert.Equal(1, measurement.Value);
        Assert.Equal("custom", (string)measurement.Tags["aspnetcore.authentication.scheme"]);
    }
 
    [Fact]
    public async Task SignIn_ExceptionThrownInHandler()
    {
        // Arrange
        var authenticationHandler = new Mock<IAuthenticationSignInHandler>();
        authenticationHandler.Setup(h => h.SignInAsync(It.IsAny<ClaimsPrincipal>(), It.IsAny<AuthenticationProperties>())).Throws(new InvalidOperationException("An error occurred during sign in"));
 
        var meterFactory = new TestMeterFactory();
        var httpContext = new DefaultHttpContext();
        var authenticationService = CreateAuthenticationService(authenticationHandler.Object, meterFactory);
        var meter = meterFactory.Meters.Single();
 
        using var signInsCollector = new MetricCollector<long>(meterFactory, AuthenticationMetrics.MeterName, "aspnetcore.authentication.sign_ins");
 
        // Act
        var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => authenticationService.SignInAsync(httpContext, scheme: "custom", new ClaimsPrincipal(), properties: null));
 
        // Assert
        Assert.Equal("An error occurred during sign in", ex.Message);
        Assert.Equal(AuthenticationMetrics.MeterName, meter.Name);
        Assert.Null(meter.Version);
 
        var measurement = Assert.Single(signInsCollector.GetMeasurementSnapshot());
        Assert.Equal(1, measurement.Value);
        Assert.Equal("custom", (string)measurement.Tags["aspnetcore.authentication.scheme"]);
        Assert.Equal("System.InvalidOperationException", (string)measurement.Tags["error.type"]);
    }
 
    [Fact]
    public async Task SignOut()
    {
        // Arrange
        var httpContext = new DefaultHttpContext();
        var meterFactory = new TestMeterFactory();
        var authenticationService = CreateAuthenticationService(Mock.Of<IAuthenticationSignOutHandler>(), meterFactory);
        var meter = meterFactory.Meters.Single();
 
        using var signOutsCollector = new MetricCollector<long>(meterFactory, AuthenticationMetrics.MeterName, "aspnetcore.authentication.sign_outs");
 
        // Act
        await authenticationService.SignOutAsync(httpContext, scheme: "custom", properties: null);
 
        // Assert
        Assert.Equal(AuthenticationMetrics.MeterName, meter.Name);
        Assert.Null(meter.Version);
 
        var measurement = Assert.Single(signOutsCollector.GetMeasurementSnapshot());
        Assert.Equal(1, measurement.Value);
        Assert.Equal("custom", (string)measurement.Tags["aspnetcore.authentication.scheme"]);
    }
 
    [Fact]
    public async Task SignOut_ExceptionThrownInHandler()
    {
        // Arrange
        var authenticationHandler = new Mock<IAuthenticationSignOutHandler>();
        authenticationHandler.Setup(h => h.SignOutAsync(It.IsAny<AuthenticationProperties>())).Throws(new InvalidOperationException("An error occurred during sign out"));
 
        var httpContext = new DefaultHttpContext();
        var meterFactory = new TestMeterFactory();
        var authenticationService = CreateAuthenticationService(authenticationHandler.Object, meterFactory);
        var meter = meterFactory.Meters.Single();
 
        using var signOutsCollector = new MetricCollector<long>(meterFactory, AuthenticationMetrics.MeterName, "aspnetcore.authentication.sign_outs");
 
        // Act
        var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => authenticationService.SignOutAsync(httpContext, scheme: "custom", properties: null));
 
        // Assert
        Assert.Equal("An error occurred during sign out", ex.Message);
        Assert.Equal(AuthenticationMetrics.MeterName, meter.Name);
        Assert.Null(meter.Version);
 
        var measurement = Assert.Single(signOutsCollector.GetMeasurementSnapshot());
        Assert.Equal(1, measurement.Value);
        Assert.Equal("custom", (string)measurement.Tags["aspnetcore.authentication.scheme"]);
        Assert.Equal("System.InvalidOperationException", (string)measurement.Tags["error.type"]);
    }
 
    private static AuthenticationServiceImpl CreateAuthenticationService(IAuthenticationHandler authenticationHandler, TestMeterFactory meterFactory)
    {
        var authenticationHandlerProvider = new Mock<IAuthenticationHandlerProvider>();
        authenticationHandlerProvider.Setup(p => p.GetHandlerAsync(It.IsAny<HttpContext>(), "custom")).Returns(Task.FromResult(authenticationHandler));
 
        var claimsTransform = new Mock<IClaimsTransformation>();
        claimsTransform.Setup(t => t.TransformAsync(It.IsAny<ClaimsPrincipal>())).Returns((ClaimsPrincipal p) => Task.FromResult(p));
 
        var options = Options.Create(new AuthenticationOptions
        {
            DefaultSignInScheme = "custom",
            RequireAuthenticatedSignIn = false,
        });
 
        var metrics = new AuthenticationMetrics(meterFactory);
        var authenticationService = new AuthenticationServiceImpl(
            Mock.Of<IAuthenticationSchemeProvider>(),
            authenticationHandlerProvider.Object,
            claimsTransform.Object,
            options,
            metrics);
 
        return authenticationService;
    }
}