From 628e4c3301fe4b97d8b042dba5d6c37a8a612ab9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Fija=C5=82kowski?= Date: Tue, 6 Feb 2024 13:47:34 +0100 Subject: [PATCH] Ensure that `ObjectExecutorFactory` resolves the handler that was used to generate metadata --- .../ObjectExecutorFactory.cs | 23 ++-- ...ServiceCollectionRegistrationExtensions.cs | 5 +- .../ObjectExecutorFactoryTests.cs | 127 +++++++++++++----- 3 files changed, 106 insertions(+), 49 deletions(-) diff --git a/src/CQRS/LeanCode.CQRS.AspNetCore/ObjectExecutorFactory.cs b/src/CQRS/LeanCode.CQRS.AspNetCore/ObjectExecutorFactory.cs index 8878e899c..a26c29b5d 100644 --- a/src/CQRS/LeanCode.CQRS.AspNetCore/ObjectExecutorFactory.cs +++ b/src/CQRS/LeanCode.CQRS.AspNetCore/ObjectExecutorFactory.cs @@ -33,14 +33,16 @@ public ObjectExecutor CreateExecutorFor(CQRSObjectMetadata @object) return @object.ObjectKind switch { CQRSObjectKind.Command - => ExecuteCommandMethod.MakeGenericMethod(@object.ObjectType).CreateDelegate(), + => ExecuteCommandMethod + .MakeGenericMethod(@object.ObjectType, @object.HandlerType) + .CreateDelegate(), CQRSObjectKind.Query => ExecuteQueryMethod - .MakeGenericMethod(@object.ObjectType, GetResultType(typeof(IQuery<>))) + .MakeGenericMethod(@object.ObjectType, GetResultType(typeof(IQuery<>)), @object.HandlerType) .CreateDelegate(), CQRSObjectKind.Operation => ExecuteOperationMethod - .MakeGenericMethod(@object.ObjectType, GetResultType(typeof(IOperation<>))) + .MakeGenericMethod(@object.ObjectType, GetResultType(typeof(IOperation<>)), @object.HandlerType) .CreateDelegate(), _ => throw new InvalidOperationException($"Unexpected object kind: {@object.ObjectKind}") }; @@ -54,44 +56,47 @@ Type GetResultType(Type interfaceType) => .First(); } - private static async Task ExecuteOperationAsync( + private static async Task ExecuteOperationAsync( HttpContext httpContext, CQRSRequestPayload payload ) where TOperation : IOperation + where THandler : IOperationHandler { var operation = (TOperation)payload.Payload; var handler = - httpContext.RequestServices.GetService>() + httpContext.RequestServices.GetService() ?? throw new OperationHandlerNotFoundException(typeof(TOperation)); return await handler.ExecuteAsync(httpContext, operation); } - private static async Task ExecuteQueryAsync( + private static async Task ExecuteQueryAsync( HttpContext httpContext, CQRSRequestPayload payload ) where TQuery : IQuery + where THandler : IQueryHandler { var query = (TQuery)payload.Payload; var handler = - httpContext.RequestServices.GetService>() + httpContext.RequestServices.GetService() ?? throw new QueryHandlerNotFoundException(typeof(TQuery)); return await handler.ExecuteAsync(httpContext, query); } - private static async Task ExecuteCommandAsync( + private static async Task ExecuteCommandAsync( HttpContext httpContext, CQRSRequestPayload payload ) where TCommand : ICommand + where THandler : ICommandHandler { var command = (TCommand)payload.Payload; var handler = - httpContext.RequestServices.GetService>() + httpContext.RequestServices.GetService() ?? throw new CommandHandlerNotFoundException(typeof(TCommand)); await handler.ExecuteAsync(httpContext, command); diff --git a/src/CQRS/LeanCode.CQRS.AspNetCore/Registration/ServiceCollectionRegistrationExtensions.cs b/src/CQRS/LeanCode.CQRS.AspNetCore/Registration/ServiceCollectionRegistrationExtensions.cs index 7bae2192e..c9d96fa7e 100644 --- a/src/CQRS/LeanCode.CQRS.AspNetCore/Registration/ServiceCollectionRegistrationExtensions.cs +++ b/src/CQRS/LeanCode.CQRS.AspNetCore/Registration/ServiceCollectionRegistrationExtensions.cs @@ -20,9 +20,8 @@ IEnumerable cqrsObjects public static void AddCQRSHandler(this IServiceCollection serviceCollection, CQRSObjectMetadata obj) { - serviceCollection.Add( - new ServiceDescriptor(MakeHandlerInterfaceType(obj), obj.HandlerType, ServiceLifetime.Scoped) - ); + serviceCollection.Add(new(MakeHandlerInterfaceType(obj), obj.HandlerType, ServiceLifetime.Scoped)); + serviceCollection.Add(new(obj.HandlerType, obj.HandlerType, ServiceLifetime.Scoped)); Type MakeHandlerInterfaceType(CQRSObjectMetadata obj) { diff --git a/test/CQRS/LeanCode.CQRS.AspNetCore.Tests/ObjectExecutorFactoryTests.cs b/test/CQRS/LeanCode.CQRS.AspNetCore.Tests/ObjectExecutorFactoryTests.cs index cb34a49f4..cb7b68761 100644 --- a/test/CQRS/LeanCode.CQRS.AspNetCore.Tests/ObjectExecutorFactoryTests.cs +++ b/test/CQRS/LeanCode.CQRS.AspNetCore.Tests/ObjectExecutorFactoryTests.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using FluentAssertions; using LeanCode.Contracts; using LeanCode.CQRS.Execution; using Microsoft.AspNetCore.Http; @@ -10,32 +11,27 @@ namespace LeanCode.CQRS.AspNetCore.Tests; [SuppressMessage(category: "?", "CA1034", Justification = "Nesting public types for better tests separation")] public class ObjectExecutorFactoryTests { - private readonly ObjectExecutorFactory executorFactory; - private readonly IServiceProvider serviceProvider; - private readonly ICommandHandler commandHandler; - private readonly IQueryHandler queryHandler; - private readonly IOperationHandler operationHandler; + private readonly ObjectExecutorFactory executorFactory = new ObjectExecutorFactory(); + private readonly IServiceProvider serviceProvider = Substitute.For(); + + private readonly CommandHandler commandHandler = Substitute.For(); + private readonly QueryHandler queryHandler = Substitute.For(); + private readonly OperationHandler operationHandler = Substitute.For(); private readonly CQRSObjectMetadata queryMetadata = - new(CQRSObjectKind.Query, typeof(Query), typeof(QueryResult), typeof(IgnoreType)); + new(CQRSObjectKind.Query, typeof(Query), typeof(QueryResult), typeof(QueryHandler)); private readonly CQRSObjectMetadata commandMetadata = - new(CQRSObjectKind.Command, typeof(Command), typeof(CommandResult), typeof(IgnoreType)); + new(CQRSObjectKind.Command, typeof(Command), typeof(CommandResult), typeof(CommandHandler)); private readonly CQRSObjectMetadata operationMetadata = - new(CQRSObjectKind.Operation, typeof(Operation), typeof(OperationResult), typeof(IgnoreType)); + new(CQRSObjectKind.Operation, typeof(Operation), typeof(OperationResult), typeof(OperationHandler)); public ObjectExecutorFactoryTests() { - executorFactory = new ObjectExecutorFactory(); - serviceProvider = Substitute.For(); - commandHandler = Substitute.For>(); - queryHandler = Substitute.For>(); - operationHandler = Substitute.For>(); - - serviceProvider.GetService(typeof(ICommandHandler)).Returns(commandHandler); - serviceProvider.GetService(typeof(IQueryHandler)).Returns(queryHandler); - serviceProvider.GetService(typeof(IOperationHandler)).Returns(operationHandler); + serviceProvider.GetService(typeof(CommandHandler)).Returns(commandHandler); + serviceProvider.GetService(typeof(QueryHandler)).Returns(queryHandler); + serviceProvider.GetService(typeof(OperationHandler)).Returns(operationHandler); } [Fact] @@ -48,8 +44,23 @@ public async Task Creates_executor_for_command_which_passes_payload_to_command_h var result = await executorMethod(ctx, new CQRSRequestPayload(cmd)); await commandHandler.Received().ExecuteAsync(ctx, cmd); - var commandResult = Assert.IsType(result); - Assert.True(commandResult.WasSuccessful); + result.Should().BeOfType().Which.WasSuccessful.Should().BeTrue(); + } + + [Fact] + public async Task Resolves_the_exact_command_handler_from_DI() + { + var cmd = new Command(); + var ctx = MockHttpContext(); + + serviceProvider + .GetService(typeof(ICommandHandler)) + .Returns(Substitute.For>()); + + var executorMethod = executorFactory.CreateExecutorFor(commandMetadata); + await executorMethod(ctx, new CQRSRequestPayload(cmd)); + + await commandHandler.Received().ExecuteAsync(ctx, cmd); } [Fact] @@ -65,8 +76,23 @@ public async Task Creates_query_handler_which_passes_payload_to_query_handler() var result = await executorMethod(ctx, new CQRSRequestPayload(query)); await queryHandler.Received().ExecuteAsync(ctx, query); - var queryResult = Assert.IsType(result); - Assert.Same(returnedResult, queryResult); + result.Should().BeOfType().Which.Should().BeSameAs(returnedResult); + } + + [Fact] + public async Task Resolves_the_exact_query_handler_from_DI() + { + var query = new Query(); + var ctx = MockHttpContext(); + + serviceProvider + .GetService(typeof(IQueryHandler)) + .Returns(Substitute.For>()); + + var executorMethod = executorFactory.CreateExecutorFor(queryMetadata); + await executorMethod(ctx, new CQRSRequestPayload(query)); + + await queryHandler.Received().ExecuteAsync(ctx, Arg.Any()); } [Fact] @@ -81,8 +107,23 @@ public async Task Creates_operation_handler_which_passes_payload_to_operation_ha var result = await executorMethod(ctx, new CQRSRequestPayload(operation)); await operationHandler.Received().ExecuteAsync(ctx, operation); - var operationResult = Assert.IsType(result); - Assert.Same(returnedResult, operationResult); + result.Should().BeOfType().Which.Should().BeSameAs(returnedResult); + } + + [Fact] + public async Task Resolves_the_exact_operation_handler_from_DI() + { + var operation = new Operation(); + var ctx = MockHttpContext(); + + serviceProvider + .GetService(typeof(IOperationHandler)) + .Returns(Substitute.For>()); + + var executorMethod = executorFactory.CreateExecutorFor(operationMetadata); + await executorMethod(ctx, new CQRSRequestPayload(operation)); + + await operationHandler.Received().ExecuteAsync(ctx, Arg.Any()); } [Fact] @@ -91,12 +132,11 @@ public async Task Throws_command_handler_not_found_if_cannot_get_it_from_DI() var cmd = new Command(); var ctx = MockHttpContext(); - serviceProvider.GetService(typeof(ICommandHandler)).Returns(null); + serviceProvider.GetService(typeof(CommandHandler)).Returns(null); var executorMethod = executorFactory.CreateExecutorFor(commandMetadata); - await Assert.ThrowsAsync( - () => executorMethod(ctx, new CQRSRequestPayload(cmd)) - ); + var act = () => executorMethod(ctx, new CQRSRequestPayload(cmd)); + await act.Should().ThrowAsync(); } [Fact] @@ -105,12 +145,11 @@ public async Task Throws_query_handler_not_found_if_cannot_get_it_from_DI() var query = new Query(); var ctx = MockHttpContext(); - serviceProvider.GetService(typeof(IQueryHandler)).Returns(null); + serviceProvider.GetService(typeof(QueryHandler)).Returns(null); var executorMethod = executorFactory.CreateExecutorFor(queryMetadata); - await Assert.ThrowsAsync( - () => executorMethod(ctx, new CQRSRequestPayload(query)) - ); + var act = () => executorMethod(ctx, new CQRSRequestPayload(query)); + await act.Should().ThrowAsync(); } [Fact] @@ -119,12 +158,11 @@ public async Task Throws_operation_handler_not_found_if_cannot_get_it_from_DI() var operation = new Operation(); var ctx = MockHttpContext(); - serviceProvider.GetService(typeof(IOperationHandler)).Returns(null); + serviceProvider.GetService(typeof(OperationHandler)).Returns(null); var executorMethod = executorFactory.CreateExecutorFor(operationMetadata); - await Assert.ThrowsAsync( - () => executorMethod(ctx, new CQRSRequestPayload(operation)) - ); + var act = () => executorMethod(ctx, new CQRSRequestPayload(operation)); + await act.Should().ThrowAsync(); } private HttpContext MockHttpContext() @@ -136,13 +174,28 @@ private HttpContext MockHttpContext() public class Command : ICommand { } - public class Query : IQuery { } + public class CommandHandler : ICommandHandler + { + public Task ExecuteAsync(HttpContext context, Command command) => Task.CompletedTask; + } public class QueryResult { } + public class Query : IQuery { } + + public class QueryHandler : IQueryHandler + { + public virtual Task ExecuteAsync(HttpContext context, Query query) => + Task.FromResult(new QueryResult()); + } + public class Operation : IOperation { } public class OperationResult { } - public class IgnoreType { } + public class OperationHandler : IOperationHandler + { + public virtual Task ExecuteAsync(HttpContext context, Operation operation) => + Task.FromResult(new OperationResult()); + } }