using System.Security.Claims; using ManagerService.Security; using ManagerService.Tests.Infrastructure; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Moq; using Xunit; namespace ManagerService.Tests.Security { /// /// Les tests des contrôleurs appellent les méthodes directement : aucun filtre /// d'autorisation ne s'y exécute, et ils ne prouvent donc rien sur la fermeture des /// routes. C'est ce trou-là que ces tests comblent — ils visent le filtre lui-même. /// public class RequireAppKeyAttributeTests { private static AuthorizationFilterContext BuildContext( bool apiKeyValid, ClaimsPrincipal user = null) { var authentication = new Mock(); authentication .Setup(a => a.AuthenticateAsync(It.IsAny(), RequireAppKeyAttribute.ApiKeyScheme)) .ReturnsAsync(apiKeyValid ? AuthenticateResult.Success(new AuthenticationTicket( new ClaimsPrincipal(new ClaimsIdentity("ApiKey")), RequireAppKeyAttribute.ApiKeyScheme)) : AuthenticateResult.NoResult()); var services = new ServiceCollection(); services.AddSingleton(authentication.Object); var http = new DefaultHttpContext { RequestServices = services.BuildServiceProvider(), User = user ?? new ClaimsPrincipal(new ClaimsIdentity()) }; return new AuthorizationFilterContext( new ActionContext(http, new RouteData(), new ActionDescriptor()), new List()); } [Fact] public async Task NoKeyNoUser_Returns401() { var context = BuildContext(apiKeyValid: false); await new RequireAppKeyAttribute().OnAuthorizationAsync(context); var result = Assert.IsType(context.Result); Assert.Equal(401, result.StatusCode); } [Fact] public async Task ValidApiKey_LetsThrough() { var context = BuildContext(apiKeyValid: true); await new RequireAppKeyAttribute().OnAuthorizationAsync(context); Assert.Null(context.Result); } [Fact] public async Task ManagerUser_LetsThrough() { // Le back-office appelle les mêmes routes avec son jeton, sans clé d'API. var manager = new ClaimsPrincipal(new ClaimsIdentity( new[] { new Claim(ClaimTypeKeys.Permission, Permissions.Viewer) }, authenticationType: "Bearer")); var context = BuildContext(apiKeyValid: false, user: manager); await new RequireAppKeyAttribute().OnAuthorizationAsync(context); Assert.Null(context.Result); } [Fact] public async Task AuthenticatedWithoutPermission_Returns401() { // Authentifié ne suffit pas : il faut la permission de lecture. var stranger = new ClaimsPrincipal(new ClaimsIdentity( new[] { new Claim("sub", "someone") }, authenticationType: "Bearer")); var context = BuildContext(apiKeyValid: false, user: stranger); await new RequireAppKeyAttribute().OnAuthorizationAsync(context); var result = Assert.IsType(context.Result); Assert.Equal(401, result.StatusCode); } } }