feat: добавил интеграционные тесты

This commit is contained in:
2026-05-11 03:42:47 +03:00
parent fc380c7c51
commit f168050637
19 changed files with 1616 additions and 51 deletions
@@ -0,0 +1,81 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.OpenApi;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace UniVerse.Api.Filters;
/// <summary>
/// Swagger operation filter that:
/// 1. Adds Bearer security requirement only to endpoints that actually require authentication.
/// 2. Appends a "Required roles: ..." remark to the operation description when role restrictions exist.
///
/// This replaces the global AddSecurityRequirement approach so anonymous endpoints
/// (auth/login, auth/refresh, auth/callback) don't show the lock icon in Swagger UI.
/// </summary>
public class AuthorizeOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
// Collect [Authorize] and [AllowAnonymous] from both the controller and the action.
var actionAttributes = context.MethodInfo.GetCustomAttributes(inherit: true);
var controllerAttributes = context.MethodInfo.DeclaringType?
.GetCustomAttributes(inherit: true) ?? [];
var allAttributes = actionAttributes.Concat(controllerAttributes).ToList();
var hasAllowAnonymous = allAttributes.OfType<AllowAnonymousAttribute>().Any();
if (hasAllowAnonymous)
return; // completely public — no lock icon
var authorizeAttributes = allAttributes.OfType<AuthorizeAttribute>().ToList();
if (authorizeAttributes.Count == 0)
return; // no [Authorize] at all — also public
// Collect all distinct roles across all [Authorize(Roles = "...")] attributes.
var roles = authorizeAttributes
.Where(a => !string.IsNullOrWhiteSpace(a.Roles))
.SelectMany(a => a.Roles!.Split(',', StringSplitOptions.TrimEntries))
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(r => r)
.ToList();
// Append role information to the operation description.
var roleInfo = roles.Count > 0
? $"**Required roles:** {string.Join(", ", roles)}"
: "**Required:** any authenticated user";
operation.Description = string.IsNullOrWhiteSpace(operation.Description)
? roleInfo
: $"{operation.Description}\n\n{roleInfo}";
operation.Responses ??= new OpenApiResponses();
// Add 401 / 403 responses if not already declared.
if (!operation.Responses.ContainsKey("401"))
{
operation.Responses.Add("401", new OpenApiResponse
{
Description = "Unauthorized — JWT token missing or invalid"
});
}
if (roles.Count > 0 && !operation.Responses.ContainsKey("403"))
{
operation.Responses.Add("403", new OpenApiResponse
{
Description = $"Forbidden — requires role: {string.Join(" or ", roles)}"
});
}
// Add Bearer security requirement to this specific operation.
// OpenAPI v2 (Microsoft.OpenApi 2.x) uses OpenApiSecuritySchemeReference
// instead of OpenApiSecurityScheme with a Reference property.
var bearerSchemeRef = new OpenApiSecuritySchemeReference("Bearer", context.Document);
operation.Security ??= [];
operation.Security.Add(new OpenApiSecurityRequirement
{
[bearerSchemeRef] = []
});
}
}