diff --git a/src/Http/Wolverine.Http.Tests/Bugs/Bug_505_required_attribute_not_working.cs b/src/Http/Wolverine.Http.Tests/Bugs/Bug_505_required_attribute_not_working.cs new file mode 100644 index 000000000..a85c00dbd --- /dev/null +++ b/src/Http/Wolverine.Http.Tests/Bugs/Bug_505_required_attribute_not_working.cs @@ -0,0 +1,108 @@ +using System.ComponentModel.DataAnnotations; +using System.Security.Claims; +using Alba; +using JasperFx.Core; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using NSubstitute; + +namespace Wolverine.Http.Tests.Bugs; + +public class Bug_505_required_attribute_not_working +{ + [Fact] + public async Task try_endpoint_hit() + { + var breeder = new Breeder(); + var id = Guid.NewGuid(); + AggregateRepository.Breeders[id] = breeder; + + var builder = WebApplication.CreateBuilder(); + builder.Host.UseWolverine(); + builder.Services.AddSingleton(); + + + var authorizationService = Substitute.For(); + builder.Services.AddSingleton(authorizationService); + var principal = new ClaimsPrincipal(); + + authorizationService.AuthorizeAsync(principal, id, "EditBreeder").Returns(AuthorizationResult.Success()); + + using var host = await AlbaHost.For(builder, app => + { + app.MapWolverineEndpoints(); + }); + + // Hit should be 204, no body + await host.Scenario(x => + { + x.ConfigureHttpContext(c => c.User = principal); + x.Post.Json(new ChangeVisionCommand("good", id)).ToUrl("/api/breeder/change-vision"); + x.StatusCodeShouldBe(204); + }); + + // Miss should 404 + await host.Scenario(x => + { + x.ConfigureHttpContext(c => c.User = principal); + var breederId = Guid.NewGuid(); + authorizationService.AuthorizeAsync(principal, breederId, "EditBreeder") + .Returns(AuthorizationResult.Success()); + + x.Post.Json(new ChangeVisionCommand("good", breederId)).ToUrl("/api/breeder/change-vision"); + x.StatusCodeShouldBe(404); + }); + } +} + +public class Breeder +{ + +} + +public class AggregateRepository +{ + public static Dictionary Breeders { get; } = new(); + + public Task LoadAsync(Guid breederId, string what, CancellationToken cancellationToken) where T : class + { + if (Breeders.TryGetValue(breederId, out var breeder)) + { + return Task.FromResult(breeder as T); + } + + return Task.FromResult(null); + } +} + +public record ChangeVisionCommand(string Vision, Guid BreederId); + +public class ChangeBreederVisionEndpoint +{ + public static async Task Before(ChangeVisionCommand c, ClaimsPrincipal u, IAuthorizationService auth, CancellationToken ct) + { + var authenticated = await auth.AuthorizeAsync(u, c.BreederId, "EditBreeder"); + return !authenticated.Succeeded ? Results.Forbid() : WolverineContinue.Result(); + } + + public static async Task LoadAsync(ChangeVisionCommand c, AggregateRepository r, CancellationToken ct) + => await r.LoadAsync(c.BreederId, null, ct); + + [Tags("Breeder")] + [ProducesResponseType(204)] + [WolverinePost("/api/breeder/change-vision")] + public static async Task Post(ChangeVisionCommand c,[Required] Breeder? breeder, AggregateRepository repo, CancellationToken ct) + { + if (breeder is null) return Results.NotFound(); + + // Don't care for the sake of the test + // breeder.ChangeNextPlannedLitter(c.Vision); + // + // await repo.StoreAsync(breeder, ct); + return Results.NoContent(); + } +} \ No newline at end of file diff --git a/src/Http/Wolverine.Http/HttpChain.Codegen.cs b/src/Http/Wolverine.Http/HttpChain.Codegen.cs index 784eac08e..b2d7c87a1 100644 --- a/src/Http/Wolverine.Http/HttpChain.Codegen.cs +++ b/src/Http/Wolverine.Http/HttpChain.Codegen.cs @@ -52,6 +52,8 @@ bool ICodeFile.AttachTypesSynchronously(GenerationRules rules, Assembly assembly { return false; } + + Debug.WriteLine(_generatedType?.SourceCode); return true; } diff --git a/src/Http/Wolverine.Http/Policies/RequiredEntityPolicy.cs b/src/Http/Wolverine.Http/Policies/RequiredEntityPolicy.cs index bbae51449..e78a46f8d 100644 --- a/src/Http/Wolverine.Http/Policies/RequiredEntityPolicy.cs +++ b/src/Http/Wolverine.Http/Policies/RequiredEntityPolicy.cs @@ -14,20 +14,17 @@ public void Apply(IReadOnlyList chains, GenerationRules rules, IConta { foreach (var chain in chains) { - if (chain.RoutePattern.Parameters.Any()) + var requiredParameters = chain.Method.Method.GetParameters() + .Where(x => x.HasAttribute() && x.ParameterType.IsClass).ToArray(); + + if (requiredParameters.Any()) { - var requiredParameters = chain.Method.Method.GetParameters() - .Where(x => x.HasAttribute() && x.ParameterType.IsClass).ToArray(); + chain.Metadata.Produces(404); - if (requiredParameters.Any()) + foreach (var parameter in requiredParameters) { - chain.Metadata.Produces(404); - - foreach (var parameter in requiredParameters) - { - var frame = new SetStatusCodeAndReturnFrame(parameter.ParameterType); - chain.Middleware.Add(frame); - } + var frame = new SetStatusCodeAndReturnFrame(parameter.ParameterType); + chain.Middleware.Add(frame); } } }