diff --git a/Directory.Build.props b/Directory.Build.props index 319aa4c..6f44e36 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,30 +1,29 @@ - - latest - net8.0 + + latest - enable - $(WarningsAsErrors);nullable; + enable + $(WarningsAsErrors);nullable; - enable + enable - latest-all - true + latest-all + true - true - true + true + true - false - + false + - - - + + + - - true - true - true - opencover - + + true + true + true + opencover + diff --git a/Directory.Packages.props b/Directory.Packages.props index 92efaaf..7b19cf5 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,36 +1,37 @@ - true + true - + - - - - - - + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - + + + + diff --git a/Immediate.Apis.sln b/Immediate.Apis.sln index dd28897..98dde4d 100644 --- a/Immediate.Apis.sln +++ b/Immediate.Apis.sln @@ -23,18 +23,53 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Source", "Source", "{681429 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{4A2C6A96-653A-40CC-9354-535F92A0BBD8}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Immediate.Apis", "src\Immediate.Apis\Immediate.Apis.csproj", "{EC7C9E81-8A2D-4BEA-9D6B-44FCC3796129}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Immediate.Apis.Generators", "src\Immediate.Apis.Generators\Immediate.Apis.Generators.csproj", "{01D48E54-9A25-4795-B154-19F97C306FC2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Immediate.Apis.Shared", "src\Immediate.Apis.Shared\Immediate.Apis.Shared.csproj", "{0EF463C2-6237-4491-A446-80860ACD280D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Immediate.Apis.FunctionalTests", "tests\Immediate.Apis.FunctionalTests\Immediate.Apis.FunctionalTests.csproj", "{4D9D5A85-102D-4F71-9E8A-FECBFE47EBA3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Immediate.Apis.Tests", "tests\Immediate.Apis.Tests\Immediate.Apis.Tests.csproj", "{058EFC02-6DDA-486F-8C4F-08BA816061CE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EC7C9E81-8A2D-4BEA-9D6B-44FCC3796129}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EC7C9E81-8A2D-4BEA-9D6B-44FCC3796129}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EC7C9E81-8A2D-4BEA-9D6B-44FCC3796129}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EC7C9E81-8A2D-4BEA-9D6B-44FCC3796129}.Release|Any CPU.Build.0 = Release|Any CPU + {01D48E54-9A25-4795-B154-19F97C306FC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {01D48E54-9A25-4795-B154-19F97C306FC2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {01D48E54-9A25-4795-B154-19F97C306FC2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {01D48E54-9A25-4795-B154-19F97C306FC2}.Release|Any CPU.Build.0 = Release|Any CPU + {0EF463C2-6237-4491-A446-80860ACD280D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0EF463C2-6237-4491-A446-80860ACD280D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0EF463C2-6237-4491-A446-80860ACD280D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0EF463C2-6237-4491-A446-80860ACD280D}.Release|Any CPU.Build.0 = Release|Any CPU + {4D9D5A85-102D-4F71-9E8A-FECBFE47EBA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4D9D5A85-102D-4F71-9E8A-FECBFE47EBA3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4D9D5A85-102D-4F71-9E8A-FECBFE47EBA3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4D9D5A85-102D-4F71-9E8A-FECBFE47EBA3}.Release|Any CPU.Build.0 = Release|Any CPU + {058EFC02-6DDA-486F-8C4F-08BA816061CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {058EFC02-6DDA-486F-8C4F-08BA816061CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {058EFC02-6DDA-486F-8C4F-08BA816061CE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {058EFC02-6DDA-486F-8C4F-08BA816061CE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {CA4DD675-66D2-45F3-B57B-F751AF1E66D3} = {AF02160B-E028-444A-9B6E-B548463E4A4D} + {EC7C9E81-8A2D-4BEA-9D6B-44FCC3796129} = {68142973-97B4-45D0-B6C7-BE884990AB7E} + {01D48E54-9A25-4795-B154-19F97C306FC2} = {68142973-97B4-45D0-B6C7-BE884990AB7E} + {0EF463C2-6237-4491-A446-80860ACD280D} = {68142973-97B4-45D0-B6C7-BE884990AB7E} + {4D9D5A85-102D-4F71-9E8A-FECBFE47EBA3} = {4A2C6A96-653A-40CC-9354-535F92A0BBD8} + {058EFC02-6DDA-486F-8C4F-08BA816061CE} = {4A2C6A96-653A-40CC-9354-535F92A0BBD8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E947E0E4-B196-46E5-8CFE-A6AE390B8897} diff --git a/src/Immediate.Apis.Generators/EquatableReadOnlyList.cs b/src/Immediate.Apis.Generators/EquatableReadOnlyList.cs new file mode 100644 index 0000000..cca38ea --- /dev/null +++ b/src/Immediate.Apis.Generators/EquatableReadOnlyList.cs @@ -0,0 +1,53 @@ +using System.Collections; +using System.Diagnostics.CodeAnalysis; + +namespace Immediate.Apis.Generators; + +[ExcludeFromCodeCoverage] +public static class EquatableReadOnlyList +{ + public static EquatableReadOnlyList ToEquatableReadOnlyList(this IEnumerable enumerable) + => new(enumerable.ToArray()); +} + +/// +/// A wrapper for IReadOnlyList that provides value equality support for the wrapped list. +/// +[ExcludeFromCodeCoverage] +public readonly struct EquatableReadOnlyList( + IReadOnlyList? collection +) : IEquatable>, IReadOnlyList +{ + private IReadOnlyList Collection => collection ?? []; + + public bool Equals(EquatableReadOnlyList other) + => this.SequenceEqual(other); + + public override bool Equals(object? obj) + => obj is EquatableReadOnlyList other && Equals(other); + + public override int GetHashCode() + { + var hashCode = new HashCode(); + + foreach (var item in Collection) + hashCode.Add(item); + + return hashCode.ToHashCode(); + } + + IEnumerator IEnumerable.GetEnumerator() + => Collection.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() + => Collection.GetEnumerator(); + + public int Count => Collection.Count; + public T this[int index] => Collection[index]; + + public static bool operator ==(EquatableReadOnlyList left, EquatableReadOnlyList right) + => left.Equals(right); + + public static bool operator !=(EquatableReadOnlyList left, EquatableReadOnlyList right) + => !left.Equals(right); +} diff --git a/src/Immediate.Apis.Generators/Immediate.Apis.Generators.csproj b/src/Immediate.Apis.Generators/Immediate.Apis.Generators.csproj new file mode 100644 index 0000000..e5e0987 --- /dev/null +++ b/src/Immediate.Apis.Generators/Immediate.Apis.Generators.csproj @@ -0,0 +1,43 @@ + + + + netstandard2.0 + true + true + + + + + + + + + + + + + + + + + + + + + $(GetTargetPathDependsOn);GetDependencyTargetPaths + + + + + + + + + + + minor + preview.0 + v + + + diff --git a/src/Immediate.Apis.Generators/ImmediateApisGenerator.cs b/src/Immediate.Apis.Generators/ImmediateApisGenerator.cs new file mode 100644 index 0000000..d07ae52 --- /dev/null +++ b/src/Immediate.Apis.Generators/ImmediateApisGenerator.cs @@ -0,0 +1,174 @@ +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; + +namespace Immediate.Apis.Generators; + +[Generator] +public sealed class ImmediateApisGenerator : IIncrementalGenerator +{ + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var methods = GetMapMethods(context, "Get") + .Concat( + GetMapMethods(context, "Post"), + GetMapMethods(context, "Put"), + GetMapMethods(context, "Patch"), + GetMapMethods(context, "Delete") + ); + + var assemblyName = context.CompilationProvider + .Select((cp, _) => cp.AssemblyName! + .Replace(".", string.Empty) + .Replace(" ", string.Empty) + .Trim() + ); + + context.RegisterSourceOutput( + methods.Combine(assemblyName), + (spc, m) => RenderMethods(spc, m.Left, m.Right) + ); + } + + private static IncrementalValueProvider> GetMapMethods( + IncrementalGeneratorInitializationContext context, + string method + ) => context.SyntaxProvider + .ForAttributeWithMetadataName( + $"Immediate.Apis.Shared.Map{method}Attribute", + (_, _) => true, + TransformMethod + ) + .Where(m => m != null) + .Collect()!; + + private sealed record Method + { + public required string Route { get; init; } + public required string ClassName { get; init; } + public required string MethodName { get; init; } + public required string ParameterType { get; init; } + public required bool AllowAnonymous { get; init; } + public required bool Authorize { get; init; } + public required string? AuthorizePolicy { get; init; } + } + + private static Method? TransformMethod( + GeneratorAttributeSyntaxContext context, + CancellationToken token + ) + { + token.ThrowIfCancellationRequested(); + + var symbol = (INamedTypeSymbol)context.TargetSymbol; + var displayName = symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + + token.ThrowIfCancellationRequested(); + + var attribute = context.Attributes[0]; + var method = attribute.AttributeClass!.Name[..^9]; + var route = (string?)attribute.ConstructorArguments.FirstOrDefault().Value; + + if (route == null) + return null; + + token.ThrowIfCancellationRequested(); + + if (symbol.ContainingType is not null) + return null; + + var attributes = symbol.GetAttributes() + .Select(a => a.AttributeClass?.ToString() ?? "") + .ToList(); + + if (!attributes.Contains("Immediate.Handlers.Shared.HandlerAttribute")) + return null; + + token.ThrowIfCancellationRequested(); + + if (symbol + .GetMembers() + .OfType() + .Where(m => m.IsStatic) + .Where(m => + m.Name.Equals("Handle", StringComparison.Ordinal) + || m.Name.Equals("HandleAsync", StringComparison.Ordinal) + ) + .ToList() is not [var handleMethod]) + { + return null; + } + + token.ThrowIfCancellationRequested(); + + // must have request type and cancellation token + if (handleMethod.Parameters.Length < 2) + return null; + + var requestType = handleMethod.Parameters[0].Type + .ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + + token.ThrowIfCancellationRequested(); + + var allowAnonymous = attributes.Contains("Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute"); + + var authorizeIndex = attributes.IndexOf("Microsoft.AspNetCore.Authorization.AuthorizeAttribute"); + var authorize = authorizeIndex >= 0; + var authorizePolicy = string.Empty; + + if (authorize) + { + var authorizeAttribute = symbol.GetAttributes()[authorizeIndex]; + if (authorizeAttribute.ConstructorArguments.Length > 0) + { + authorizePolicy = (string)authorizeAttribute.ConstructorArguments[0].Value!; + } + else if (authorizeAttribute.NamedArguments.Length > 0) + { + foreach (var argument in authorizeAttribute.NamedArguments) + { + if (argument.Key != "Policy") + return null; + + authorizePolicy = (string)argument.Value.Value!; + } + } + } + + token.ThrowIfCancellationRequested(); + + return new() + { + Route = route, + ClassName = displayName, + MethodName = method, + ParameterType = requestType, + AllowAnonymous = allowAnonymous, + Authorize = authorize, + AuthorizePolicy = authorizePolicy + }; + } + + private static void RenderMethods( + SourceProductionContext context, + EquatableReadOnlyList methods, + string assemblyName + ) + { + if (methods.Count == 0) + return; + + var token = context.CancellationToken; + + var template = Utility.GetTemplate("Routes"); + token.ThrowIfCancellationRequested(); + + var source = template.Render(new + { + Assembly = assemblyName, + Methods = methods, + }); + + token.ThrowIfCancellationRequested(); + context.AddSource("RoutesBuilder.g.cs", source); + } +} diff --git a/src/Immediate.Apis.Generators/Properties/launchSettings.json b/src/Immediate.Apis.Generators/Properties/launchSettings.json new file mode 100644 index 0000000..df45684 --- /dev/null +++ b/src/Immediate.Apis.Generators/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "Immediate.Apis.FunctionalTests": { + "commandName": "DebugRoslynComponent", + "targetProject": "../../tests/Immediate.Apis.FunctionalTests/Immediate.Apis.FunctionalTests.csproj" + } + } +} diff --git a/src/Immediate.Apis.Generators/Templates/Routes.sbntxt b/src/Immediate.Apis.Generators/Templates/Routes.sbntxt new file mode 100644 index 0000000..e829f27 --- /dev/null +++ b/src/Immediate.Apis.Generators/Templates/Routes.sbntxt @@ -0,0 +1,33 @@ +using Microsoft.AspNetCore.Mvc; + +#pragma warning disable CS1591 + +namespace Microsoft.AspNetCore.Builder; + +public static class {{ assembly }}RoutesBuilder +{ + public static IEndpointRouteBuilder Map{{ assembly }}Endpoints( + this IEndpointRouteBuilder app + ) + { + {{~ for m in methods ~}} + _ = app + .{{ m.method_name }}( + "{{ m.route }}", + async ( + [AsParameters] {{ m.parameter_type }} parameters, + [FromServices] {{ m.class_name }}.Handler handler, + CancellationToken token + ) => await handler.HandleAsync(parameters, token) + ) + {{~ if m.allow_anonymous ~}} + .AllowAnonymous() + {{~ else if m.authorize ~}} + .RequireAuthorization({{ if !string.empty m.authorize_policy }}"{{ m.authorize_policy }}"{{ end }}) + {{~ end ~}} + ; + + {{~ end ~}} + return app; + } +} diff --git a/src/Immediate.Apis.Generators/Utility.cs b/src/Immediate.Apis.Generators/Utility.cs new file mode 100644 index 0000000..e46d155 --- /dev/null +++ b/src/Immediate.Apis.Generators/Utility.cs @@ -0,0 +1,39 @@ +using System.Collections.Immutable; +using System.Reflection; +using Microsoft.CodeAnalysis; +using Scriban; + +namespace Immediate.Apis.Generators; + +internal static class Utility +{ + public static Template GetTemplate(string name) + { + using var stream = Assembly + .GetExecutingAssembly() + .GetManifestResourceStream( + $"Immediate.Apis.Generators.Templates.{name}.sbntxt" + )!; + + using var reader = new StreamReader(stream); + return Template.Parse(reader.ReadToEnd()); + } + + public static IncrementalValueProvider> Concat( + this IncrementalValueProvider> method1, + IncrementalValueProvider> method2, + IncrementalValueProvider> method3, + IncrementalValueProvider> method4, + IncrementalValueProvider> method5 + ) => method1.Combine(method2) + .Combine(method3.Combine(method4)) + .Combine(method5) + .Select((x, _) => + new EquatableReadOnlyList([ + .. x.Left.Left.Left, + .. x.Left.Left.Right, + .. x.Left.Right.Left, + .. x.Left.Right.Right, + .. x.Right, + ])); +} diff --git a/src/Immediate.Apis.Shared/.editorconfig b/src/Immediate.Apis.Shared/.editorconfig new file mode 100644 index 0000000..31f894b --- /dev/null +++ b/src/Immediate.Apis.Shared/.editorconfig @@ -0,0 +1,7 @@ +[*.cs] + +# XML Documentation +dotnet_diagnostic.CS0105.severity = error # CS0105: Using directive is unnecessary. +dotnet_diagnostic.CS1573.severity = error # CS1573: Missing XML comment for parameter +dotnet_diagnostic.CS1591.severity = error # CS1591: Missing XML comment for publicly visible type or member +dotnet_diagnostic.CS1712.severity = error # CS1712: Type parameter has no matching typeparam tag in the XML comment (but other type parameters do) diff --git a/src/Immediate.Apis.Shared/Immediate.Apis.Shared.csproj b/src/Immediate.Apis.Shared/Immediate.Apis.Shared.csproj new file mode 100644 index 0000000..89ea4d0 --- /dev/null +++ b/src/Immediate.Apis.Shared/Immediate.Apis.Shared.csproj @@ -0,0 +1,13 @@ + + + + netstandard2.0 + false + $(NoWarn);CA1716 + + + + + + + diff --git a/src/Immediate.Apis.Shared/MapDeleteAttribute.cs b/src/Immediate.Apis.Shared/MapDeleteAttribute.cs new file mode 100644 index 0000000..66bbbd0 --- /dev/null +++ b/src/Immediate.Apis.Shared/MapDeleteAttribute.cs @@ -0,0 +1,8 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Immediate.Apis.Shared; + +/// +public sealed class MapDeleteAttribute( + [StringSyntax("Route")] string route +) : MapMethodAttribute(route); diff --git a/src/Immediate.Apis.Shared/MapGetAttribute.cs b/src/Immediate.Apis.Shared/MapGetAttribute.cs new file mode 100644 index 0000000..cddf5a4 --- /dev/null +++ b/src/Immediate.Apis.Shared/MapGetAttribute.cs @@ -0,0 +1,8 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Immediate.Apis.Shared; + +/// +public sealed class MapGetAttribute( + [StringSyntax("Route")] string route +) : MapMethodAttribute(route); diff --git a/src/Immediate.Apis.Shared/MapMethodAttribute.cs b/src/Immediate.Apis.Shared/MapMethodAttribute.cs new file mode 100644 index 0000000..9693fb6 --- /dev/null +++ b/src/Immediate.Apis.Shared/MapMethodAttribute.cs @@ -0,0 +1,16 @@ +namespace Immediate.Apis.Shared; + +/// +/// Applied to a class to indicate that minimal APIs registration should be generated +/// +/// +/// The route that the handler should be registered with +/// +[AttributeUsage(AttributeTargets.Class)] +public abstract class MapMethodAttribute(string route) : Attribute +{ + /// + /// The route that the handler should be registered with + /// + public string Route { get; } = route; +} diff --git a/src/Immediate.Apis.Shared/MapPatchAttribute.cs b/src/Immediate.Apis.Shared/MapPatchAttribute.cs new file mode 100644 index 0000000..ca521f4 --- /dev/null +++ b/src/Immediate.Apis.Shared/MapPatchAttribute.cs @@ -0,0 +1,8 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Immediate.Apis.Shared; + +/// +public sealed class MapPatchAttribute( + [StringSyntax("Route")] string route +) : MapMethodAttribute(route); diff --git a/src/Immediate.Apis.Shared/MapPostAttribute.cs b/src/Immediate.Apis.Shared/MapPostAttribute.cs new file mode 100644 index 0000000..6ada2bd --- /dev/null +++ b/src/Immediate.Apis.Shared/MapPostAttribute.cs @@ -0,0 +1,8 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Immediate.Apis.Shared; + +/// +public sealed class MapPostAttribute( + [StringSyntax("Route")] string route +) : MapMethodAttribute(route); diff --git a/src/Immediate.Apis.Shared/MapPutAttribute.cs b/src/Immediate.Apis.Shared/MapPutAttribute.cs new file mode 100644 index 0000000..79c953a --- /dev/null +++ b/src/Immediate.Apis.Shared/MapPutAttribute.cs @@ -0,0 +1,8 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Immediate.Apis.Shared; + +/// +public sealed class MapPutAttribute( + [StringSyntax("Route")] string route +) : MapMethodAttribute(route); diff --git a/src/Immediate.Apis/Immediate.Apis.csproj b/src/Immediate.Apis/Immediate.Apis.csproj new file mode 100644 index 0000000..d6ed2fb --- /dev/null +++ b/src/Immediate.Apis/Immediate.Apis.csproj @@ -0,0 +1,54 @@ + + + + netstandard2.0 + true + false + + + + Immediate.Apis + An implementation of the mediator pattern in .NET using source-generation. + + Immediate.Apis Developers + + MIT + readme.md + + true + https://github.com/viceroypenguin/Immediate.Apis + + + + + + + + + + + + + + + + + + + + + + + + + + + + minor + preview.0 + v + + + diff --git a/tests/Immediate.Apis.FunctionalTests/.editorconfig b/tests/Immediate.Apis.FunctionalTests/.editorconfig new file mode 100644 index 0000000..b9ff461 --- /dev/null +++ b/tests/Immediate.Apis.FunctionalTests/.editorconfig @@ -0,0 +1,3 @@ +[*.cs] + +dotnet_diagnostic.CA1716.severity = none # CA1716: Identifiers should not match keywords diff --git a/tests/Immediate.Apis.FunctionalTests/Features/WeatherForecast/Get.cs b/tests/Immediate.Apis.FunctionalTests/Features/WeatherForecast/Get.cs new file mode 100644 index 0000000..25dbe7f --- /dev/null +++ b/tests/Immediate.Apis.FunctionalTests/Features/WeatherForecast/Get.cs @@ -0,0 +1,32 @@ +using Immediate.Apis.Shared; +using Immediate.Handlers.Shared; +using Microsoft.AspNetCore.Authorization; + +namespace Immediate.Apis.FunctionalTests.Features.WeatherForecast; + +[Handler] +[MapGet("/forecast")] +[AllowAnonymous] +public static partial class Get +{ + public sealed record Query; + + public sealed record Result + { + public required DateOnly Date { get; init; } + public required int TemperatureC { get; init; } + public required string? Summary { get; init; } + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + } + + private static async ValueTask> Handle( + Query _, + CancellationToken token + ) + { + await Task.Delay(1, token); + return [ + new() { Date = new(2024, 1, 1), TemperatureC = 0, Summary = "Sunny and Freezing" }, + ]; + } +} diff --git a/tests/Immediate.Apis.FunctionalTests/Features/WeatherForecast/Put.cs b/tests/Immediate.Apis.FunctionalTests/Features/WeatherForecast/Put.cs new file mode 100644 index 0000000..6f19ac4 --- /dev/null +++ b/tests/Immediate.Apis.FunctionalTests/Features/WeatherForecast/Put.cs @@ -0,0 +1,25 @@ +using Immediate.Apis.Shared; +using Immediate.Handlers.Shared; + +namespace Immediate.Apis.FunctionalTests.Features.WeatherForecast; + +[Handler] +[MapPut("/forecast")] +public static partial class Put +{ + public sealed record Command + { + public required DateOnly Date { get; init; } + public required int TemperatureC { get; init; } + public required string? Summary { get; init; } + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + } + + private static async ValueTask Handle( + Command _, + CancellationToken token + ) + { + await Task.Delay(1, token); + } +} diff --git a/tests/Immediate.Apis.FunctionalTests/Immediate.Apis.FunctionalTests.csproj b/tests/Immediate.Apis.FunctionalTests/Immediate.Apis.FunctionalTests.csproj new file mode 100644 index 0000000..26116e3 --- /dev/null +++ b/tests/Immediate.Apis.FunctionalTests/Immediate.Apis.FunctionalTests.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + + + + + + + + + + + + + + + diff --git a/tests/Immediate.Apis.FunctionalTests/Immediate.Apis.FunctionalTests.http b/tests/Immediate.Apis.FunctionalTests/Immediate.Apis.FunctionalTests.http new file mode 100644 index 0000000..240422e --- /dev/null +++ b/tests/Immediate.Apis.FunctionalTests/Immediate.Apis.FunctionalTests.http @@ -0,0 +1,6 @@ +@Immediate.Apis.FunctionalTests_HostAddress = http://localhost:5233 + +GET {{Immediate.Apis.FunctionalTests_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/tests/Immediate.Apis.FunctionalTests/Program.cs b/tests/Immediate.Apis.FunctionalTests/Program.cs new file mode 100644 index 0000000..c74ee77 --- /dev/null +++ b/tests/Immediate.Apis.FunctionalTests/Program.cs @@ -0,0 +1,20 @@ +using Immediate.Apis.FunctionalTests; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +builder.Services.AddHandlers(); + +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +app.UseSwagger(); +app.UseSwaggerUI(); + +app.MapImmediateApisFunctionalTestsEndpoints(); + +app.Run(); diff --git a/tests/Immediate.Apis.FunctionalTests/Properties/launchSettings.json b/tests/Immediate.Apis.FunctionalTests/Properties/launchSettings.json new file mode 100644 index 0000000..153261d --- /dev/null +++ b/tests/Immediate.Apis.FunctionalTests/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:62891", + "sslPort": 0 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5233", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/tests/Immediate.Apis.FunctionalTests/appsettings.Development.json b/tests/Immediate.Apis.FunctionalTests/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/tests/Immediate.Apis.FunctionalTests/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/tests/Immediate.Apis.FunctionalTests/appsettings.json b/tests/Immediate.Apis.FunctionalTests/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/tests/Immediate.Apis.FunctionalTests/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/tests/Immediate.Apis.Tests/.editorconfig b/tests/Immediate.Apis.Tests/.editorconfig new file mode 100644 index 0000000..7944128 --- /dev/null +++ b/tests/Immediate.Apis.Tests/.editorconfig @@ -0,0 +1,14 @@ +[*.g.verified.cs] + +# Mark code as excluded and generated +# NB: This disables regular analyzers by default +exclude = true +generated_code = true + +# Disable any active Roslyn analyzers +dotnet_analyzer_diagnostic.severity = none + +# These are security analyzers and must be manually disabled +dotnet_diagnostic.CA5350.severity = none +dotnet_diagnostic.CA5351.severity = none +dotnet_diagnostic.CA5394.severity = none diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAllowAnonymousTests.MapMethodWithAllowAnonymousTest_method=Delete#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAllowAnonymousTests.MapMethodWithAllowAnonymousTest_method=Delete#RoutesBuilder.g.verified.cs new file mode 100644 index 0000000..85b4d8f --- /dev/null +++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAllowAnonymousTests.MapMethodWithAllowAnonymousTest_method=Delete#RoutesBuilder.g.verified.cs @@ -0,0 +1,28 @@ +//HintName: RoutesBuilder.g.cs +using Microsoft.AspNetCore.Mvc; + +#pragma warning disable CS1591 + +namespace Microsoft.AspNetCore.Builder; + +public static class TestsRoutesBuilder +{ + public static IEndpointRouteBuilder MapTestsEndpoints( + this IEndpointRouteBuilder app + ) + { + _ = app + .MapDelete( + "/test", + async ( + [AsParameters] global::Dummy.GetUsersQuery.Query parameters, + [FromServices] global::Dummy.GetUsersQuery.Handler handler, + CancellationToken token + ) => await handler.HandleAsync(parameters, token) + ) + .AllowAnonymous() + ; + + return app; + } +} diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAllowAnonymousTests.MapMethodWithAllowAnonymousTest_method=Get#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAllowAnonymousTests.MapMethodWithAllowAnonymousTest_method=Get#RoutesBuilder.g.verified.cs new file mode 100644 index 0000000..949c5a6 --- /dev/null +++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAllowAnonymousTests.MapMethodWithAllowAnonymousTest_method=Get#RoutesBuilder.g.verified.cs @@ -0,0 +1,28 @@ +//HintName: RoutesBuilder.g.cs +using Microsoft.AspNetCore.Mvc; + +#pragma warning disable CS1591 + +namespace Microsoft.AspNetCore.Builder; + +public static class TestsRoutesBuilder +{ + public static IEndpointRouteBuilder MapTestsEndpoints( + this IEndpointRouteBuilder app + ) + { + _ = app + .MapGet( + "/test", + async ( + [AsParameters] global::Dummy.GetUsersQuery.Query parameters, + [FromServices] global::Dummy.GetUsersQuery.Handler handler, + CancellationToken token + ) => await handler.HandleAsync(parameters, token) + ) + .AllowAnonymous() + ; + + return app; + } +} diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAllowAnonymousTests.MapMethodWithAllowAnonymousTest_method=Patch#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAllowAnonymousTests.MapMethodWithAllowAnonymousTest_method=Patch#RoutesBuilder.g.verified.cs new file mode 100644 index 0000000..b425cd9 --- /dev/null +++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAllowAnonymousTests.MapMethodWithAllowAnonymousTest_method=Patch#RoutesBuilder.g.verified.cs @@ -0,0 +1,28 @@ +//HintName: RoutesBuilder.g.cs +using Microsoft.AspNetCore.Mvc; + +#pragma warning disable CS1591 + +namespace Microsoft.AspNetCore.Builder; + +public static class TestsRoutesBuilder +{ + public static IEndpointRouteBuilder MapTestsEndpoints( + this IEndpointRouteBuilder app + ) + { + _ = app + .MapPatch( + "/test", + async ( + [AsParameters] global::Dummy.GetUsersQuery.Query parameters, + [FromServices] global::Dummy.GetUsersQuery.Handler handler, + CancellationToken token + ) => await handler.HandleAsync(parameters, token) + ) + .AllowAnonymous() + ; + + return app; + } +} diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAllowAnonymousTests.MapMethodWithAllowAnonymousTest_method=Post#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAllowAnonymousTests.MapMethodWithAllowAnonymousTest_method=Post#RoutesBuilder.g.verified.cs new file mode 100644 index 0000000..6450780 --- /dev/null +++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAllowAnonymousTests.MapMethodWithAllowAnonymousTest_method=Post#RoutesBuilder.g.verified.cs @@ -0,0 +1,28 @@ +//HintName: RoutesBuilder.g.cs +using Microsoft.AspNetCore.Mvc; + +#pragma warning disable CS1591 + +namespace Microsoft.AspNetCore.Builder; + +public static class TestsRoutesBuilder +{ + public static IEndpointRouteBuilder MapTestsEndpoints( + this IEndpointRouteBuilder app + ) + { + _ = app + .MapPost( + "/test", + async ( + [AsParameters] global::Dummy.GetUsersQuery.Query parameters, + [FromServices] global::Dummy.GetUsersQuery.Handler handler, + CancellationToken token + ) => await handler.HandleAsync(parameters, token) + ) + .AllowAnonymous() + ; + + return app; + } +} diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAllowAnonymousTests.MapMethodWithAllowAnonymousTest_method=Put#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAllowAnonymousTests.MapMethodWithAllowAnonymousTest_method=Put#RoutesBuilder.g.verified.cs new file mode 100644 index 0000000..1822407 --- /dev/null +++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAllowAnonymousTests.MapMethodWithAllowAnonymousTest_method=Put#RoutesBuilder.g.verified.cs @@ -0,0 +1,28 @@ +//HintName: RoutesBuilder.g.cs +using Microsoft.AspNetCore.Mvc; + +#pragma warning disable CS1591 + +namespace Microsoft.AspNetCore.Builder; + +public static class TestsRoutesBuilder +{ + public static IEndpointRouteBuilder MapTestsEndpoints( + this IEndpointRouteBuilder app + ) + { + _ = app + .MapPut( + "/test", + async ( + [AsParameters] global::Dummy.GetUsersQuery.Query parameters, + [FromServices] global::Dummy.GetUsersQuery.Handler handler, + CancellationToken token + ) => await handler.HandleAsync(parameters, token) + ) + .AllowAnonymous() + ; + + return app; + } +} diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAllowAnonymousTests.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAllowAnonymousTests.cs new file mode 100644 index 0000000..905f4eb --- /dev/null +++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAllowAnonymousTests.cs @@ -0,0 +1,46 @@ +namespace Immediate.Apis.Tests.GeneratorTests; + +public sealed class ApiAllowAnonymousTests +{ + [Theory] + [InlineData("Get")] + [InlineData("Post")] + [InlineData("Patch")] + [InlineData("Put")] + [InlineData("Delete")] + public async Task MapMethodWithAllowAnonymousTest(string method) + { + var driver = GeneratorTestHelper.GetDriver( + $$""" + using System.Threading.Tasks; + using Immediate.Apis.Shared; + using Immediate.Handlers.Shared; + using Microsoft.AspNetCore.Authorization; + + namespace Dummy; + + [Handler] + [Map{{method}}("/test")] + [AllowAnonymous] + public static class GetUsersQuery + { + public record Query; + + private static ValueTask HandleAsync( + Query _, + CancellationToken token) + { + return 0; + } + } + """); + + var result = driver.GetRunResult(); + + Assert.Empty(result.Diagnostics); + _ = Assert.Single(result.GeneratedTrees); + + _ = await Verify(result) + .UseParameters(method); + } +} diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeConstructorTest_method=Delete#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeConstructorTest_method=Delete#RoutesBuilder.g.verified.cs new file mode 100644 index 0000000..0387c50 --- /dev/null +++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeConstructorTest_method=Delete#RoutesBuilder.g.verified.cs @@ -0,0 +1,28 @@ +//HintName: RoutesBuilder.g.cs +using Microsoft.AspNetCore.Mvc; + +#pragma warning disable CS1591 + +namespace Microsoft.AspNetCore.Builder; + +public static class TestsRoutesBuilder +{ + public static IEndpointRouteBuilder MapTestsEndpoints( + this IEndpointRouteBuilder app + ) + { + _ = app + .MapDelete( + "/test", + async ( + [AsParameters] global::Dummy.GetUsersQuery.Query parameters, + [FromServices] global::Dummy.GetUsersQuery.Handler handler, + CancellationToken token + ) => await handler.HandleAsync(parameters, token) + ) + .RequireAuthorization("TestPolicy") + ; + + return app; + } +} diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeConstructorTest_method=Get#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeConstructorTest_method=Get#RoutesBuilder.g.verified.cs new file mode 100644 index 0000000..56df973 --- /dev/null +++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeConstructorTest_method=Get#RoutesBuilder.g.verified.cs @@ -0,0 +1,28 @@ +//HintName: RoutesBuilder.g.cs +using Microsoft.AspNetCore.Mvc; + +#pragma warning disable CS1591 + +namespace Microsoft.AspNetCore.Builder; + +public static class TestsRoutesBuilder +{ + public static IEndpointRouteBuilder MapTestsEndpoints( + this IEndpointRouteBuilder app + ) + { + _ = app + .MapGet( + "/test", + async ( + [AsParameters] global::Dummy.GetUsersQuery.Query parameters, + [FromServices] global::Dummy.GetUsersQuery.Handler handler, + CancellationToken token + ) => await handler.HandleAsync(parameters, token) + ) + .RequireAuthorization("TestPolicy") + ; + + return app; + } +} diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeConstructorTest_method=Patch#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeConstructorTest_method=Patch#RoutesBuilder.g.verified.cs new file mode 100644 index 0000000..b53320c --- /dev/null +++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeConstructorTest_method=Patch#RoutesBuilder.g.verified.cs @@ -0,0 +1,28 @@ +//HintName: RoutesBuilder.g.cs +using Microsoft.AspNetCore.Mvc; + +#pragma warning disable CS1591 + +namespace Microsoft.AspNetCore.Builder; + +public static class TestsRoutesBuilder +{ + public static IEndpointRouteBuilder MapTestsEndpoints( + this IEndpointRouteBuilder app + ) + { + _ = app + .MapPatch( + "/test", + async ( + [AsParameters] global::Dummy.GetUsersQuery.Query parameters, + [FromServices] global::Dummy.GetUsersQuery.Handler handler, + CancellationToken token + ) => await handler.HandleAsync(parameters, token) + ) + .RequireAuthorization("TestPolicy") + ; + + return app; + } +} diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeConstructorTest_method=Post#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeConstructorTest_method=Post#RoutesBuilder.g.verified.cs new file mode 100644 index 0000000..1a7f346 --- /dev/null +++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeConstructorTest_method=Post#RoutesBuilder.g.verified.cs @@ -0,0 +1,28 @@ +//HintName: RoutesBuilder.g.cs +using Microsoft.AspNetCore.Mvc; + +#pragma warning disable CS1591 + +namespace Microsoft.AspNetCore.Builder; + +public static class TestsRoutesBuilder +{ + public static IEndpointRouteBuilder MapTestsEndpoints( + this IEndpointRouteBuilder app + ) + { + _ = app + .MapPost( + "/test", + async ( + [AsParameters] global::Dummy.GetUsersQuery.Query parameters, + [FromServices] global::Dummy.GetUsersQuery.Handler handler, + CancellationToken token + ) => await handler.HandleAsync(parameters, token) + ) + .RequireAuthorization("TestPolicy") + ; + + return app; + } +} diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeConstructorTest_method=Put#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeConstructorTest_method=Put#RoutesBuilder.g.verified.cs new file mode 100644 index 0000000..a0a81a3 --- /dev/null +++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeConstructorTest_method=Put#RoutesBuilder.g.verified.cs @@ -0,0 +1,28 @@ +//HintName: RoutesBuilder.g.cs +using Microsoft.AspNetCore.Mvc; + +#pragma warning disable CS1591 + +namespace Microsoft.AspNetCore.Builder; + +public static class TestsRoutesBuilder +{ + public static IEndpointRouteBuilder MapTestsEndpoints( + this IEndpointRouteBuilder app + ) + { + _ = app + .MapPut( + "/test", + async ( + [AsParameters] global::Dummy.GetUsersQuery.Query parameters, + [FromServices] global::Dummy.GetUsersQuery.Handler handler, + CancellationToken token + ) => await handler.HandleAsync(parameters, token) + ) + .RequireAuthorization("TestPolicy") + ; + + return app; + } +} diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeNamedPolicyArgumentTest_method=Delete#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeNamedPolicyArgumentTest_method=Delete#RoutesBuilder.g.verified.cs new file mode 100644 index 0000000..0387c50 --- /dev/null +++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeNamedPolicyArgumentTest_method=Delete#RoutesBuilder.g.verified.cs @@ -0,0 +1,28 @@ +//HintName: RoutesBuilder.g.cs +using Microsoft.AspNetCore.Mvc; + +#pragma warning disable CS1591 + +namespace Microsoft.AspNetCore.Builder; + +public static class TestsRoutesBuilder +{ + public static IEndpointRouteBuilder MapTestsEndpoints( + this IEndpointRouteBuilder app + ) + { + _ = app + .MapDelete( + "/test", + async ( + [AsParameters] global::Dummy.GetUsersQuery.Query parameters, + [FromServices] global::Dummy.GetUsersQuery.Handler handler, + CancellationToken token + ) => await handler.HandleAsync(parameters, token) + ) + .RequireAuthorization("TestPolicy") + ; + + return app; + } +} diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeNamedPolicyArgumentTest_method=Get#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeNamedPolicyArgumentTest_method=Get#RoutesBuilder.g.verified.cs new file mode 100644 index 0000000..56df973 --- /dev/null +++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeNamedPolicyArgumentTest_method=Get#RoutesBuilder.g.verified.cs @@ -0,0 +1,28 @@ +//HintName: RoutesBuilder.g.cs +using Microsoft.AspNetCore.Mvc; + +#pragma warning disable CS1591 + +namespace Microsoft.AspNetCore.Builder; + +public static class TestsRoutesBuilder +{ + public static IEndpointRouteBuilder MapTestsEndpoints( + this IEndpointRouteBuilder app + ) + { + _ = app + .MapGet( + "/test", + async ( + [AsParameters] global::Dummy.GetUsersQuery.Query parameters, + [FromServices] global::Dummy.GetUsersQuery.Handler handler, + CancellationToken token + ) => await handler.HandleAsync(parameters, token) + ) + .RequireAuthorization("TestPolicy") + ; + + return app; + } +} diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeNamedPolicyArgumentTest_method=Patch#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeNamedPolicyArgumentTest_method=Patch#RoutesBuilder.g.verified.cs new file mode 100644 index 0000000..b53320c --- /dev/null +++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeNamedPolicyArgumentTest_method=Patch#RoutesBuilder.g.verified.cs @@ -0,0 +1,28 @@ +//HintName: RoutesBuilder.g.cs +using Microsoft.AspNetCore.Mvc; + +#pragma warning disable CS1591 + +namespace Microsoft.AspNetCore.Builder; + +public static class TestsRoutesBuilder +{ + public static IEndpointRouteBuilder MapTestsEndpoints( + this IEndpointRouteBuilder app + ) + { + _ = app + .MapPatch( + "/test", + async ( + [AsParameters] global::Dummy.GetUsersQuery.Query parameters, + [FromServices] global::Dummy.GetUsersQuery.Handler handler, + CancellationToken token + ) => await handler.HandleAsync(parameters, token) + ) + .RequireAuthorization("TestPolicy") + ; + + return app; + } +} diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeNamedPolicyArgumentTest_method=Post#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeNamedPolicyArgumentTest_method=Post#RoutesBuilder.g.verified.cs new file mode 100644 index 0000000..1a7f346 --- /dev/null +++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeNamedPolicyArgumentTest_method=Post#RoutesBuilder.g.verified.cs @@ -0,0 +1,28 @@ +//HintName: RoutesBuilder.g.cs +using Microsoft.AspNetCore.Mvc; + +#pragma warning disable CS1591 + +namespace Microsoft.AspNetCore.Builder; + +public static class TestsRoutesBuilder +{ + public static IEndpointRouteBuilder MapTestsEndpoints( + this IEndpointRouteBuilder app + ) + { + _ = app + .MapPost( + "/test", + async ( + [AsParameters] global::Dummy.GetUsersQuery.Query parameters, + [FromServices] global::Dummy.GetUsersQuery.Handler handler, + CancellationToken token + ) => await handler.HandleAsync(parameters, token) + ) + .RequireAuthorization("TestPolicy") + ; + + return app; + } +} diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeNamedPolicyArgumentTest_method=Put#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeNamedPolicyArgumentTest_method=Put#RoutesBuilder.g.verified.cs new file mode 100644 index 0000000..a0a81a3 --- /dev/null +++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithAuthorizeNamedPolicyArgumentTest_method=Put#RoutesBuilder.g.verified.cs @@ -0,0 +1,28 @@ +//HintName: RoutesBuilder.g.cs +using Microsoft.AspNetCore.Mvc; + +#pragma warning disable CS1591 + +namespace Microsoft.AspNetCore.Builder; + +public static class TestsRoutesBuilder +{ + public static IEndpointRouteBuilder MapTestsEndpoints( + this IEndpointRouteBuilder app + ) + { + _ = app + .MapPut( + "/test", + async ( + [AsParameters] global::Dummy.GetUsersQuery.Query parameters, + [FromServices] global::Dummy.GetUsersQuery.Handler handler, + CancellationToken token + ) => await handler.HandleAsync(parameters, token) + ) + .RequireAuthorization("TestPolicy") + ; + + return app; + } +} diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithSimpleAuthorizeTest_method=Delete#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithSimpleAuthorizeTest_method=Delete#RoutesBuilder.g.verified.cs new file mode 100644 index 0000000..4c21f3f --- /dev/null +++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithSimpleAuthorizeTest_method=Delete#RoutesBuilder.g.verified.cs @@ -0,0 +1,28 @@ +//HintName: RoutesBuilder.g.cs +using Microsoft.AspNetCore.Mvc; + +#pragma warning disable CS1591 + +namespace Microsoft.AspNetCore.Builder; + +public static class TestsRoutesBuilder +{ + public static IEndpointRouteBuilder MapTestsEndpoints( + this IEndpointRouteBuilder app + ) + { + _ = app + .MapDelete( + "/test", + async ( + [AsParameters] global::Dummy.GetUsersQuery.Query parameters, + [FromServices] global::Dummy.GetUsersQuery.Handler handler, + CancellationToken token + ) => await handler.HandleAsync(parameters, token) + ) + .RequireAuthorization() + ; + + return app; + } +} diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithSimpleAuthorizeTest_method=Get#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithSimpleAuthorizeTest_method=Get#RoutesBuilder.g.verified.cs new file mode 100644 index 0000000..854a8bf --- /dev/null +++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithSimpleAuthorizeTest_method=Get#RoutesBuilder.g.verified.cs @@ -0,0 +1,28 @@ +//HintName: RoutesBuilder.g.cs +using Microsoft.AspNetCore.Mvc; + +#pragma warning disable CS1591 + +namespace Microsoft.AspNetCore.Builder; + +public static class TestsRoutesBuilder +{ + public static IEndpointRouteBuilder MapTestsEndpoints( + this IEndpointRouteBuilder app + ) + { + _ = app + .MapGet( + "/test", + async ( + [AsParameters] global::Dummy.GetUsersQuery.Query parameters, + [FromServices] global::Dummy.GetUsersQuery.Handler handler, + CancellationToken token + ) => await handler.HandleAsync(parameters, token) + ) + .RequireAuthorization() + ; + + return app; + } +} diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithSimpleAuthorizeTest_method=Patch#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithSimpleAuthorizeTest_method=Patch#RoutesBuilder.g.verified.cs new file mode 100644 index 0000000..ecd13f0 --- /dev/null +++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithSimpleAuthorizeTest_method=Patch#RoutesBuilder.g.verified.cs @@ -0,0 +1,28 @@ +//HintName: RoutesBuilder.g.cs +using Microsoft.AspNetCore.Mvc; + +#pragma warning disable CS1591 + +namespace Microsoft.AspNetCore.Builder; + +public static class TestsRoutesBuilder +{ + public static IEndpointRouteBuilder MapTestsEndpoints( + this IEndpointRouteBuilder app + ) + { + _ = app + .MapPatch( + "/test", + async ( + [AsParameters] global::Dummy.GetUsersQuery.Query parameters, + [FromServices] global::Dummy.GetUsersQuery.Handler handler, + CancellationToken token + ) => await handler.HandleAsync(parameters, token) + ) + .RequireAuthorization() + ; + + return app; + } +} diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithSimpleAuthorizeTest_method=Post#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithSimpleAuthorizeTest_method=Post#RoutesBuilder.g.verified.cs new file mode 100644 index 0000000..a2f4adb --- /dev/null +++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithSimpleAuthorizeTest_method=Post#RoutesBuilder.g.verified.cs @@ -0,0 +1,28 @@ +//HintName: RoutesBuilder.g.cs +using Microsoft.AspNetCore.Mvc; + +#pragma warning disable CS1591 + +namespace Microsoft.AspNetCore.Builder; + +public static class TestsRoutesBuilder +{ + public static IEndpointRouteBuilder MapTestsEndpoints( + this IEndpointRouteBuilder app + ) + { + _ = app + .MapPost( + "/test", + async ( + [AsParameters] global::Dummy.GetUsersQuery.Query parameters, + [FromServices] global::Dummy.GetUsersQuery.Handler handler, + CancellationToken token + ) => await handler.HandleAsync(parameters, token) + ) + .RequireAuthorization() + ; + + return app; + } +} diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithSimpleAuthorizeTest_method=Put#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithSimpleAuthorizeTest_method=Put#RoutesBuilder.g.verified.cs new file mode 100644 index 0000000..d62a271 --- /dev/null +++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.MapMethodWithSimpleAuthorizeTest_method=Put#RoutesBuilder.g.verified.cs @@ -0,0 +1,28 @@ +//HintName: RoutesBuilder.g.cs +using Microsoft.AspNetCore.Mvc; + +#pragma warning disable CS1591 + +namespace Microsoft.AspNetCore.Builder; + +public static class TestsRoutesBuilder +{ + public static IEndpointRouteBuilder MapTestsEndpoints( + this IEndpointRouteBuilder app + ) + { + _ = app + .MapPut( + "/test", + async ( + [AsParameters] global::Dummy.GetUsersQuery.Query parameters, + [FromServices] global::Dummy.GetUsersQuery.Handler handler, + CancellationToken token + ) => await handler.HandleAsync(parameters, token) + ) + .RequireAuthorization() + ; + + return app; + } +} diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.cs b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.cs new file mode 100644 index 0000000..02343c5 --- /dev/null +++ b/tests/Immediate.Apis.Tests/GeneratorTests/ApiAuthorizeTests.cs @@ -0,0 +1,130 @@ +namespace Immediate.Apis.Tests.GeneratorTests; + +public sealed class ApiAuthorizeTests +{ + [Theory] + [InlineData("Get")] + [InlineData("Post")] + [InlineData("Patch")] + [InlineData("Put")] + [InlineData("Delete")] + public async Task MapMethodWithSimpleAuthorizeTest(string method) + { + var driver = GeneratorTestHelper.GetDriver( + $$""" + using System.Threading.Tasks; + using Immediate.Apis.Shared; + using Immediate.Handlers.Shared; + using Microsoft.AspNetCore.Authorization; + + namespace Dummy; + + [Handler] + [Map{{method}}("/test")] + [Authorize] + public static class GetUsersQuery + { + public record Query; + + private static ValueTask HandleAsync( + Query _, + CancellationToken token) + { + return 0; + } + } + """); + + var result = driver.GetRunResult(); + + Assert.Empty(result.Diagnostics); + _ = Assert.Single(result.GeneratedTrees); + + _ = await Verify(result) + .UseParameters(method); + } + + [Theory] + [InlineData("Get")] + [InlineData("Post")] + [InlineData("Patch")] + [InlineData("Put")] + [InlineData("Delete")] + public async Task MapMethodWithAuthorizeConstructorTest(string method) + { + var driver = GeneratorTestHelper.GetDriver( + $$""" + using System.Threading.Tasks; + using Immediate.Apis.Shared; + using Immediate.Handlers.Shared; + using Microsoft.AspNetCore.Authorization; + + namespace Dummy; + + [Handler] + [Map{{method}}("/test")] + [Authorize("TestPolicy")] + public static class GetUsersQuery + { + public record Query; + + private static ValueTask HandleAsync( + Query _, + CancellationToken token) + { + return 0; + } + } + """); + + var result = driver.GetRunResult(); + + Assert.Empty(result.Diagnostics); + _ = Assert.Single(result.GeneratedTrees); + + _ = await Verify(result) + .UseParameters(method); + } + + [Theory] + [InlineData("Get")] + [InlineData("Post")] + [InlineData("Patch")] + [InlineData("Put")] + [InlineData("Delete")] + public async Task MapMethodWithAuthorizeNamedPolicyArgumentTest(string method) + { + var driver = GeneratorTestHelper.GetDriver( + $$""" + using System.Threading.Tasks; + using Immediate.Apis.Shared; + using Immediate.Handlers.Shared; + using Microsoft.AspNetCore.Authorization; + + namespace Dummy; + + [Handler] + [Map{{method}}("/test")] + [Authorize(Policy = "TestPolicy")] + public static class GetUsersQuery + { + public record Query; + + private static ValueTask HandleAsync( + Query _, + CancellationToken token) + { + return 0; + } + } + """); + + var result = driver.GetRunResult(); + + Assert.Empty(result.Diagnostics); + _ = Assert.Single(result.GeneratedTrees); + + _ = await Verify(result) + .UseParameters(method); + } +} diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/GeneratorTestHelper.cs b/tests/Immediate.Apis.Tests/GeneratorTests/GeneratorTestHelper.cs new file mode 100644 index 0000000..6f9f26d --- /dev/null +++ b/tests/Immediate.Apis.Tests/GeneratorTests/GeneratorTestHelper.cs @@ -0,0 +1,42 @@ +using Immediate.Apis.Generators; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace Immediate.Apis.Tests.GeneratorTests; + +public static class GeneratorTestHelper +{ + public static GeneratorDriver GetDriver(string source) + { + // Parse the provided string into a C# syntax tree + var syntaxTree = CSharpSyntaxTree.ParseText(source); + + // Create a Roslyn compilation for the syntax tree. + var compilation = CSharpCompilation.Create( + assemblyName: "Tests", + syntaxTrees: [syntaxTree], + references: + [ + .. Basic.Reference.Assemblies.Net80.References.All, + MetadataReference.CreateFromFile("./Immediate.Handlers.Shared.dll"), + MetadataReference.CreateFromFile("./Immediate.Apis.Shared.dll"), + MetadataReference.CreateFromFile("./Microsoft.Extensions.DependencyInjection.dll"), + MetadataReference.CreateFromFile("./Microsoft.Extensions.DependencyInjection.Abstractions.dll"), + MetadataReference.CreateFromFile("./Microsoft.AspNetCore.Authorization.dll"), + MetadataReference.CreateFromFile("./Microsoft.AspNetCore.Metadata.dll"), + MetadataReference.CreateFromFile("./Microsoft.Extensions.Logging.Abstractions.dll"), + MetadataReference.CreateFromFile("./Microsoft.Extensions.Options.dll"), + MetadataReference.CreateFromFile("./Microsoft.Extensions.Primitives.dll"), + ] + ); + + // Create an instance of our incremental source generator + var generator = new ImmediateApisGenerator(); + + // The GeneratorDriver is used to run our generator against a compilation + GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); + + // Run the source generator! + return driver.RunGenerators(compilation); + } +} diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/InvalidCodeTests.cs b/tests/Immediate.Apis.Tests/GeneratorTests/InvalidCodeTests.cs new file mode 100644 index 0000000..d60d531 --- /dev/null +++ b/tests/Immediate.Apis.Tests/GeneratorTests/InvalidCodeTests.cs @@ -0,0 +1,260 @@ +namespace Immediate.Apis.Tests.GeneratorTests; + +public sealed class InvalidCodeTests +{ + [Theory] + [InlineData("Get")] + [InlineData("Post")] + [InlineData("Patch")] + [InlineData("Put")] + [InlineData("Delete")] + public void MissingRoute(string method) + { + var driver = GeneratorTestHelper.GetDriver( + $$""" + using System.Threading.Tasks; + using Immediate.Apis.Shared; + using Immediate.Handlers.Shared; + + namespace Dummy; + + [Handler] + [Map{{method}}()] + public static class GetUsersQuery + { + public record Query; + + private static ValueTask Handle( + Query _, + CancellationToken token) + { + return 0; + } + } + """); + + var result = driver.GetRunResult(); + + Assert.Empty(result.Diagnostics); + Assert.Empty(result.GeneratedTrees); + } + + [Theory] + [InlineData("Get")] + [InlineData("Post")] + [InlineData("Patch")] + [InlineData("Put")] + [InlineData("Delete")] + public void MissingHandlerAttribute(string method) + { + var driver = GeneratorTestHelper.GetDriver( + $$""" + using System.Threading.Tasks; + using Immediate.Apis.Shared; + + namespace Dummy; + + [Map{{method}}("/test")] + public static class GetUsersQuery + { + public record Query; + + private static ValueTask Handle( + Query _, + CancellationToken token) + { + return 0; + } + } + """); + + var result = driver.GetRunResult(); + + Assert.Empty(result.Diagnostics); + Assert.Empty(result.GeneratedTrees); + } + + [Theory] + [InlineData("Get")] + [InlineData("Post")] + [InlineData("Patch")] + [InlineData("Put")] + [InlineData("Delete")] + public void NestedClass(string method) + { + var driver = GeneratorTestHelper.GetDriver( + $$""" + using System.Threading.Tasks; + using Immediate.Apis.Shared; + using Immediate.Handlers.Shared; + + namespace Dummy; + + public static class Outer + { + [Handler] + [Map{{method}}("/test")] + public static class GetUsersQuery + { + public record Query; + + private static ValueTask Handle( + Query _, + CancellationToken token) + { + return 0; + } + } + } + """); + + var result = driver.GetRunResult(); + + Assert.Empty(result.Diagnostics); + Assert.Empty(result.GeneratedTrees); + } + + [Theory] + [InlineData("Get")] + [InlineData("Post")] + [InlineData("Patch")] + [InlineData("Put")] + [InlineData("Delete")] + public void MissingHandler(string method) + { + var driver = GeneratorTestHelper.GetDriver( + $$""" + using System.Threading.Tasks; + using Immediate.Apis.Shared; + using Immediate.Handlers.Shared; + + namespace Dummy; + + [Handler] + [Map{{method}}("/test")] + public static class GetUsersQuery + { + public record Query; + } + """); + + var result = driver.GetRunResult(); + + Assert.Empty(result.Diagnostics); + Assert.Empty(result.GeneratedTrees); + } + + [Theory] + [InlineData("Get")] + [InlineData("Post")] + [InlineData("Patch")] + [InlineData("Put")] + [InlineData("Delete")] + public void InvalidHandler(string method) + { + var driver = GeneratorTestHelper.GetDriver( + $$""" + using System.Threading.Tasks; + using Immediate.Apis.Shared; + using Immediate.Handlers.Shared; + + namespace Dummy; + + [Handler] + [Map{{method}}("/test")] + public static class GetUsersQuery + { + public record Query; + + private static ValueTask Handle( + Query _) + { + return 0; + } + } + """); + + var result = driver.GetRunResult(); + + Assert.Empty(result.Diagnostics); + Assert.Empty(result.GeneratedTrees); + } + + [Theory] + [InlineData("Get")] + [InlineData("Post")] + [InlineData("Patch")] + [InlineData("Put")] + [InlineData("Delete")] + public void AuthorizeUsesAuthenticationSchemes(string method) + { + var driver = GeneratorTestHelper.GetDriver( + $$""" + using System.Threading.Tasks; + using Immediate.Apis.Shared; + using Immediate.Handlers.Shared; + using Microsoft.AspNetCore.Authorization; + + namespace Dummy; + + [Handler] + [Map{{method}}("/test")] + [Authorize(AuthenticationSchemes = "test")] + public static class GetUsersQuery + { + public record Query; + + private static ValueTask HandleAsync( + Query _, + CancellationToken token) + { + return 0; + } + } + """); + + var result = driver.GetRunResult(); + + Assert.Empty(result.Diagnostics); + Assert.Empty(result.GeneratedTrees); + } + + [Theory] + [InlineData("Get")] + [InlineData("Post")] + [InlineData("Patch")] + [InlineData("Put")] + [InlineData("Delete")] + public void AuthorizeUsesRoles(string method) + { + var driver = GeneratorTestHelper.GetDriver( + $$""" + using System.Threading.Tasks; + using Immediate.Apis.Shared; + using Immediate.Handlers.Shared; + using Microsoft.AspNetCore.Authorization; + + namespace Dummy; + + [Handler] + [Map{{method}}("/test")] + [Authorize(Roles = "test")] + public static class GetUsersQuery + { + public record Query; + + private static ValueTask HandleAsync( + Query _, + CancellationToken token) + { + return 0; + } + } + """); + + var result = driver.GetRunResult(); + + Assert.Empty(result.Diagnostics); + Assert.Empty(result.GeneratedTrees); + } +} diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleAsyncTest_method=Delete#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleAsyncTest_method=Delete#RoutesBuilder.g.verified.cs new file mode 100644 index 0000000..02a9210 --- /dev/null +++ b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleAsyncTest_method=Delete#RoutesBuilder.g.verified.cs @@ -0,0 +1,27 @@ +//HintName: RoutesBuilder.g.cs +using Microsoft.AspNetCore.Mvc; + +#pragma warning disable CS1591 + +namespace Microsoft.AspNetCore.Builder; + +public static class TestsRoutesBuilder +{ + public static IEndpointRouteBuilder MapTestsEndpoints( + this IEndpointRouteBuilder app + ) + { + _ = app + .MapDelete( + "/test", + async ( + [AsParameters] global::Dummy.GetUsersQuery.Query parameters, + [FromServices] global::Dummy.GetUsersQuery.Handler handler, + CancellationToken token + ) => await handler.HandleAsync(parameters, token) + ) + ; + + return app; + } +} diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleAsyncTest_method=Get#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleAsyncTest_method=Get#RoutesBuilder.g.verified.cs new file mode 100644 index 0000000..5da5de2 --- /dev/null +++ b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleAsyncTest_method=Get#RoutesBuilder.g.verified.cs @@ -0,0 +1,27 @@ +//HintName: RoutesBuilder.g.cs +using Microsoft.AspNetCore.Mvc; + +#pragma warning disable CS1591 + +namespace Microsoft.AspNetCore.Builder; + +public static class TestsRoutesBuilder +{ + public static IEndpointRouteBuilder MapTestsEndpoints( + this IEndpointRouteBuilder app + ) + { + _ = app + .MapGet( + "/test", + async ( + [AsParameters] global::Dummy.GetUsersQuery.Query parameters, + [FromServices] global::Dummy.GetUsersQuery.Handler handler, + CancellationToken token + ) => await handler.HandleAsync(parameters, token) + ) + ; + + return app; + } +} diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleAsyncTest_method=Patch#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleAsyncTest_method=Patch#RoutesBuilder.g.verified.cs new file mode 100644 index 0000000..5c5f0e8 --- /dev/null +++ b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleAsyncTest_method=Patch#RoutesBuilder.g.verified.cs @@ -0,0 +1,27 @@ +//HintName: RoutesBuilder.g.cs +using Microsoft.AspNetCore.Mvc; + +#pragma warning disable CS1591 + +namespace Microsoft.AspNetCore.Builder; + +public static class TestsRoutesBuilder +{ + public static IEndpointRouteBuilder MapTestsEndpoints( + this IEndpointRouteBuilder app + ) + { + _ = app + .MapPatch( + "/test", + async ( + [AsParameters] global::Dummy.GetUsersQuery.Query parameters, + [FromServices] global::Dummy.GetUsersQuery.Handler handler, + CancellationToken token + ) => await handler.HandleAsync(parameters, token) + ) + ; + + return app; + } +} diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleAsyncTest_method=Post#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleAsyncTest_method=Post#RoutesBuilder.g.verified.cs new file mode 100644 index 0000000..05c9595 --- /dev/null +++ b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleAsyncTest_method=Post#RoutesBuilder.g.verified.cs @@ -0,0 +1,27 @@ +//HintName: RoutesBuilder.g.cs +using Microsoft.AspNetCore.Mvc; + +#pragma warning disable CS1591 + +namespace Microsoft.AspNetCore.Builder; + +public static class TestsRoutesBuilder +{ + public static IEndpointRouteBuilder MapTestsEndpoints( + this IEndpointRouteBuilder app + ) + { + _ = app + .MapPost( + "/test", + async ( + [AsParameters] global::Dummy.GetUsersQuery.Query parameters, + [FromServices] global::Dummy.GetUsersQuery.Handler handler, + CancellationToken token + ) => await handler.HandleAsync(parameters, token) + ) + ; + + return app; + } +} diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleAsyncTest_method=Put#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleAsyncTest_method=Put#RoutesBuilder.g.verified.cs new file mode 100644 index 0000000..b5ca151 --- /dev/null +++ b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleAsyncTest_method=Put#RoutesBuilder.g.verified.cs @@ -0,0 +1,27 @@ +//HintName: RoutesBuilder.g.cs +using Microsoft.AspNetCore.Mvc; + +#pragma warning disable CS1591 + +namespace Microsoft.AspNetCore.Builder; + +public static class TestsRoutesBuilder +{ + public static IEndpointRouteBuilder MapTestsEndpoints( + this IEndpointRouteBuilder app + ) + { + _ = app + .MapPut( + "/test", + async ( + [AsParameters] global::Dummy.GetUsersQuery.Query parameters, + [FromServices] global::Dummy.GetUsersQuery.Handler handler, + CancellationToken token + ) => await handler.HandleAsync(parameters, token) + ) + ; + + return app; + } +} diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleTest_method=Delete#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleTest_method=Delete#RoutesBuilder.g.verified.cs new file mode 100644 index 0000000..02a9210 --- /dev/null +++ b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleTest_method=Delete#RoutesBuilder.g.verified.cs @@ -0,0 +1,27 @@ +//HintName: RoutesBuilder.g.cs +using Microsoft.AspNetCore.Mvc; + +#pragma warning disable CS1591 + +namespace Microsoft.AspNetCore.Builder; + +public static class TestsRoutesBuilder +{ + public static IEndpointRouteBuilder MapTestsEndpoints( + this IEndpointRouteBuilder app + ) + { + _ = app + .MapDelete( + "/test", + async ( + [AsParameters] global::Dummy.GetUsersQuery.Query parameters, + [FromServices] global::Dummy.GetUsersQuery.Handler handler, + CancellationToken token + ) => await handler.HandleAsync(parameters, token) + ) + ; + + return app; + } +} diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleTest_method=Get#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleTest_method=Get#RoutesBuilder.g.verified.cs new file mode 100644 index 0000000..5da5de2 --- /dev/null +++ b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleTest_method=Get#RoutesBuilder.g.verified.cs @@ -0,0 +1,27 @@ +//HintName: RoutesBuilder.g.cs +using Microsoft.AspNetCore.Mvc; + +#pragma warning disable CS1591 + +namespace Microsoft.AspNetCore.Builder; + +public static class TestsRoutesBuilder +{ + public static IEndpointRouteBuilder MapTestsEndpoints( + this IEndpointRouteBuilder app + ) + { + _ = app + .MapGet( + "/test", + async ( + [AsParameters] global::Dummy.GetUsersQuery.Query parameters, + [FromServices] global::Dummy.GetUsersQuery.Handler handler, + CancellationToken token + ) => await handler.HandleAsync(parameters, token) + ) + ; + + return app; + } +} diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleTest_method=Patch#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleTest_method=Patch#RoutesBuilder.g.verified.cs new file mode 100644 index 0000000..5c5f0e8 --- /dev/null +++ b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleTest_method=Patch#RoutesBuilder.g.verified.cs @@ -0,0 +1,27 @@ +//HintName: RoutesBuilder.g.cs +using Microsoft.AspNetCore.Mvc; + +#pragma warning disable CS1591 + +namespace Microsoft.AspNetCore.Builder; + +public static class TestsRoutesBuilder +{ + public static IEndpointRouteBuilder MapTestsEndpoints( + this IEndpointRouteBuilder app + ) + { + _ = app + .MapPatch( + "/test", + async ( + [AsParameters] global::Dummy.GetUsersQuery.Query parameters, + [FromServices] global::Dummy.GetUsersQuery.Handler handler, + CancellationToken token + ) => await handler.HandleAsync(parameters, token) + ) + ; + + return app; + } +} diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleTest_method=Post#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleTest_method=Post#RoutesBuilder.g.verified.cs new file mode 100644 index 0000000..05c9595 --- /dev/null +++ b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleTest_method=Post#RoutesBuilder.g.verified.cs @@ -0,0 +1,27 @@ +//HintName: RoutesBuilder.g.cs +using Microsoft.AspNetCore.Mvc; + +#pragma warning disable CS1591 + +namespace Microsoft.AspNetCore.Builder; + +public static class TestsRoutesBuilder +{ + public static IEndpointRouteBuilder MapTestsEndpoints( + this IEndpointRouteBuilder app + ) + { + _ = app + .MapPost( + "/test", + async ( + [AsParameters] global::Dummy.GetUsersQuery.Query parameters, + [FromServices] global::Dummy.GetUsersQuery.Handler handler, + CancellationToken token + ) => await handler.HandleAsync(parameters, token) + ) + ; + + return app; + } +} diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleTest_method=Put#RoutesBuilder.g.verified.cs b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleTest_method=Put#RoutesBuilder.g.verified.cs new file mode 100644 index 0000000..b5ca151 --- /dev/null +++ b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.MapMethodHandleTest_method=Put#RoutesBuilder.g.verified.cs @@ -0,0 +1,27 @@ +//HintName: RoutesBuilder.g.cs +using Microsoft.AspNetCore.Mvc; + +#pragma warning disable CS1591 + +namespace Microsoft.AspNetCore.Builder; + +public static class TestsRoutesBuilder +{ + public static IEndpointRouteBuilder MapTestsEndpoints( + this IEndpointRouteBuilder app + ) + { + _ = app + .MapPut( + "/test", + async ( + [AsParameters] global::Dummy.GetUsersQuery.Query parameters, + [FromServices] global::Dummy.GetUsersQuery.Handler handler, + CancellationToken token + ) => await handler.HandleAsync(parameters, token) + ) + ; + + return app; + } +} diff --git a/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.cs b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.cs new file mode 100644 index 0000000..a2e3713 --- /dev/null +++ b/tests/Immediate.Apis.Tests/GeneratorTests/SimpleApiTests.cs @@ -0,0 +1,84 @@ +namespace Immediate.Apis.Tests.GeneratorTests; + +public sealed class SimpleApiTests +{ + [Theory] + [InlineData("Get")] + [InlineData("Post")] + [InlineData("Patch")] + [InlineData("Put")] + [InlineData("Delete")] + public async Task MapMethodHandleTest(string method) + { + var driver = GeneratorTestHelper.GetDriver( + $$""" + using System.Threading.Tasks; + using Immediate.Apis.Shared; + using Immediate.Handlers.Shared; + + namespace Dummy; + + [Handler] + [Map{{method}}("/test")] + public static class GetUsersQuery + { + public record Query; + + private static ValueTask Handle( + Query _, + CancellationToken token) + { + return 0; + } + } + """); + + var result = driver.GetRunResult(); + + Assert.Empty(result.Diagnostics); + _ = Assert.Single(result.GeneratedTrees); + + _ = await Verify(result) + .UseParameters(method); + } + + [Theory] + [InlineData("Get")] + [InlineData("Post")] + [InlineData("Patch")] + [InlineData("Put")] + [InlineData("Delete")] + public async Task MapMethodHandleAsyncTest(string method) + { + var driver = GeneratorTestHelper.GetDriver( + $$""" + using System.Threading.Tasks; + using Immediate.Apis.Shared; + using Immediate.Handlers.Shared; + + namespace Dummy; + + [Handler] + [Map{{method}}("/test")] + public static class GetUsersQuery + { + public record Query; + + private static ValueTask HandleAsync( + Query _, + CancellationToken token) + { + return 0; + } + } + """); + + var result = driver.GetRunResult(); + + Assert.Empty(result.Diagnostics); + _ = Assert.Single(result.GeneratedTrees); + + _ = await Verify(result) + .UseParameters(method); + } +} diff --git a/tests/Immediate.Apis.Tests/Immediate.Apis.Tests.csproj b/tests/Immediate.Apis.Tests/Immediate.Apis.Tests.csproj new file mode 100644 index 0000000..908ef06 --- /dev/null +++ b/tests/Immediate.Apis.Tests/Immediate.Apis.Tests.csproj @@ -0,0 +1,31 @@ + + + + net8.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/Immediate.Apis.Tests/ModuleInitializer.cs b/tests/Immediate.Apis.Tests/ModuleInitializer.cs new file mode 100644 index 0000000..d9900e5 --- /dev/null +++ b/tests/Immediate.Apis.Tests/ModuleInitializer.cs @@ -0,0 +1,13 @@ +using System.Runtime.CompilerServices; + +namespace Immediate.Apis.Tests; + +public static class ModuleInitializer +{ + [ModuleInitializer] + public static void Init() + { + VerifierSettings.AutoVerify(includeBuildServer: false); + VerifySourceGenerators.Initialize(); + } +}