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);
}
}
}