From 0c392d6bcb2e33b5c37c28c88c8c2418874bbf93 Mon Sep 17 00:00:00 2001 From: hasanxdev Date: Tue, 29 Jul 2025 02:10:42 +0330 Subject: [PATCH 01/23] add first test --- DispatchR.sln | 15 +++++ .../DispatchR.UnitTest.csproj | 29 ++++++++++ tests/DispatchR.UnitTest/Fixtures/Fixture.cs | 6 ++ .../Notification/MultiHandlersNotification.cs | 5 ++ .../Notification/NotificationOneHandler.cs | 11 ++++ .../Notification/NotificationThreeHandler.cs | 11 ++++ .../Notification/NotificationTwoHandler.cs | 11 ++++ .../SendRequest/FirstPipelineBehavior.cs | 12 ++++ .../SendRequest/GenericPipelineBehavior.cs | 15 +++++ .../Fixtures/SendRequest/PingValueTask.cs | 8 +++ .../SendRequest/PingValueTaskHandler.cs | 11 ++++ .../SendRequest/SecondPipelineBehavior.cs | 14 +++++ .../CounterPipelineStreamHandler.cs | 17 ++++++ .../StreamRequest/CounterStreamHandler.cs | 13 +++++ .../StreamRequest/CounterStreamRequest.cs | 5 ++ .../StreamRequest/GenericPipelineBehavior.cs | 18 ++++++ .../DispatchR.UnitTest/RequestHandlerTests.cs | 57 +++++++++++++++++++ tests/DispatchR.UnitTest/SendRequests.cs | 9 +++ 18 files changed, 267 insertions(+) create mode 100644 tests/DispatchR.UnitTest/DispatchR.UnitTest.csproj create mode 100644 tests/DispatchR.UnitTest/Fixtures/Fixture.cs create mode 100644 tests/DispatchR.UnitTest/Fixtures/Notification/MultiHandlersNotification.cs create mode 100644 tests/DispatchR.UnitTest/Fixtures/Notification/NotificationOneHandler.cs create mode 100644 tests/DispatchR.UnitTest/Fixtures/Notification/NotificationThreeHandler.cs create mode 100644 tests/DispatchR.UnitTest/Fixtures/Notification/NotificationTwoHandler.cs create mode 100644 tests/DispatchR.UnitTest/Fixtures/SendRequest/FirstPipelineBehavior.cs create mode 100644 tests/DispatchR.UnitTest/Fixtures/SendRequest/GenericPipelineBehavior.cs create mode 100644 tests/DispatchR.UnitTest/Fixtures/SendRequest/PingValueTask.cs create mode 100644 tests/DispatchR.UnitTest/Fixtures/SendRequest/PingValueTaskHandler.cs create mode 100644 tests/DispatchR.UnitTest/Fixtures/SendRequest/SecondPipelineBehavior.cs create mode 100644 tests/DispatchR.UnitTest/Fixtures/StreamRequest/CounterPipelineStreamHandler.cs create mode 100644 tests/DispatchR.UnitTest/Fixtures/StreamRequest/CounterStreamHandler.cs create mode 100644 tests/DispatchR.UnitTest/Fixtures/StreamRequest/CounterStreamRequest.cs create mode 100644 tests/DispatchR.UnitTest/Fixtures/StreamRequest/GenericPipelineBehavior.cs create mode 100644 tests/DispatchR.UnitTest/RequestHandlerTests.cs create mode 100644 tests/DispatchR.UnitTest/SendRequests.cs diff --git a/DispatchR.sln b/DispatchR.sln index 075f584..73b8ed9 100644 --- a/DispatchR.sln +++ b/DispatchR.sln @@ -37,6 +37,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspireModularSample.Service EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspireModularSample.ServiceB", "src\AspireModularExample\AspireModularSample.ServiceB\AspireModularSample.ServiceB.csproj", "{707E07BA-998C-49DE-BA56-7E9C0B6B7DBA}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DispatchR.UnitTest", "tests\DispatchR.UnitTest\DispatchR.UnitTest.csproj", "{806030F5-86B1-4EFC-923C-94FF7D32DFC9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -131,6 +133,18 @@ Global {707E07BA-998C-49DE-BA56-7E9C0B6B7DBA}.Release|x64.Build.0 = Release|Any CPU {707E07BA-998C-49DE-BA56-7E9C0B6B7DBA}.Release|x86.ActiveCfg = Release|Any CPU {707E07BA-998C-49DE-BA56-7E9C0B6B7DBA}.Release|x86.Build.0 = Release|Any CPU + {806030F5-86B1-4EFC-923C-94FF7D32DFC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {806030F5-86B1-4EFC-923C-94FF7D32DFC9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {806030F5-86B1-4EFC-923C-94FF7D32DFC9}.Debug|x64.ActiveCfg = Debug|Any CPU + {806030F5-86B1-4EFC-923C-94FF7D32DFC9}.Debug|x64.Build.0 = Debug|Any CPU + {806030F5-86B1-4EFC-923C-94FF7D32DFC9}.Debug|x86.ActiveCfg = Debug|Any CPU + {806030F5-86B1-4EFC-923C-94FF7D32DFC9}.Debug|x86.Build.0 = Debug|Any CPU + {806030F5-86B1-4EFC-923C-94FF7D32DFC9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {806030F5-86B1-4EFC-923C-94FF7D32DFC9}.Release|Any CPU.Build.0 = Release|Any CPU + {806030F5-86B1-4EFC-923C-94FF7D32DFC9}.Release|x64.ActiveCfg = Release|Any CPU + {806030F5-86B1-4EFC-923C-94FF7D32DFC9}.Release|x64.Build.0 = Release|Any CPU + {806030F5-86B1-4EFC-923C-94FF7D32DFC9}.Release|x86.ActiveCfg = Release|Any CPU + {806030F5-86B1-4EFC-923C-94FF7D32DFC9}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -146,5 +160,6 @@ Global {3416F900-58F9-4AB6-AC8A-95B03C7BD9A3} = {BA3021C0-B64E-B700-D62A-004419E20C36} {7D2890FF-66F7-4870-BB89-952167AB0681} = {BA3021C0-B64E-B700-D62A-004419E20C36} {707E07BA-998C-49DE-BA56-7E9C0B6B7DBA} = {BA3021C0-B64E-B700-D62A-004419E20C36} + {806030F5-86B1-4EFC-923C-94FF7D32DFC9} = {7F7601D5-C62E-4EA3-8B71-E946A62B4529} EndGlobalSection EndGlobal diff --git a/tests/DispatchR.UnitTest/DispatchR.UnitTest.csproj b/tests/DispatchR.UnitTest/DispatchR.UnitTest.csproj new file mode 100644 index 0000000..066919a --- /dev/null +++ b/tests/DispatchR.UnitTest/DispatchR.UnitTest.csproj @@ -0,0 +1,29 @@ + + + + net9.0 + enable + enable + false + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + diff --git a/tests/DispatchR.UnitTest/Fixtures/Fixture.cs b/tests/DispatchR.UnitTest/Fixtures/Fixture.cs new file mode 100644 index 0000000..7d8915b --- /dev/null +++ b/tests/DispatchR.UnitTest/Fixtures/Fixture.cs @@ -0,0 +1,6 @@ +namespace DispatchR.UnitTest.Fixtures; + +public class Fixture +{ + +} \ No newline at end of file diff --git a/tests/DispatchR.UnitTest/Fixtures/Notification/MultiHandlersNotification.cs b/tests/DispatchR.UnitTest/Fixtures/Notification/MultiHandlersNotification.cs new file mode 100644 index 0000000..e60cd38 --- /dev/null +++ b/tests/DispatchR.UnitTest/Fixtures/Notification/MultiHandlersNotification.cs @@ -0,0 +1,5 @@ +using DispatchR.Requests.Notification; + +namespace DispatchR.UnitTest.Fixtures.Notification; + +public sealed record MultiHandlersNotification(Guid Id) : INotification; \ No newline at end of file diff --git a/tests/DispatchR.UnitTest/Fixtures/Notification/NotificationOneHandler.cs b/tests/DispatchR.UnitTest/Fixtures/Notification/NotificationOneHandler.cs new file mode 100644 index 0000000..0becef7 --- /dev/null +++ b/tests/DispatchR.UnitTest/Fixtures/Notification/NotificationOneHandler.cs @@ -0,0 +1,11 @@ +using DispatchR.Requests.Notification; + +namespace DispatchR.UnitTest.Fixtures.Notification; + +public sealed class NotificationOneHandler() : INotificationHandler +{ + public ValueTask Handle(MultiHandlersNotification request, CancellationToken cancellationToken) + { + return ValueTask.CompletedTask; + } +} \ No newline at end of file diff --git a/tests/DispatchR.UnitTest/Fixtures/Notification/NotificationThreeHandler.cs b/tests/DispatchR.UnitTest/Fixtures/Notification/NotificationThreeHandler.cs new file mode 100644 index 0000000..c3dbbf8 --- /dev/null +++ b/tests/DispatchR.UnitTest/Fixtures/Notification/NotificationThreeHandler.cs @@ -0,0 +1,11 @@ +using DispatchR.Requests.Notification; + +namespace DispatchR.UnitTest.Fixtures.Notification; + +public sealed class NotificationThreeHandler() : INotificationHandler +{ + public ValueTask Handle(MultiHandlersNotification request, CancellationToken cancellationToken) + { + return ValueTask.CompletedTask; + } +} \ No newline at end of file diff --git a/tests/DispatchR.UnitTest/Fixtures/Notification/NotificationTwoHandler.cs b/tests/DispatchR.UnitTest/Fixtures/Notification/NotificationTwoHandler.cs new file mode 100644 index 0000000..a80c5f3 --- /dev/null +++ b/tests/DispatchR.UnitTest/Fixtures/Notification/NotificationTwoHandler.cs @@ -0,0 +1,11 @@ +using DispatchR.Requests.Notification; + +namespace DispatchR.UnitTest.Fixtures.Notification; + +public sealed class NotificationTwoHandler() : INotificationHandler +{ + public ValueTask Handle(MultiHandlersNotification request, CancellationToken cancellationToken) + { + return ValueTask.CompletedTask; + } +} \ No newline at end of file diff --git a/tests/DispatchR.UnitTest/Fixtures/SendRequest/FirstPipelineBehavior.cs b/tests/DispatchR.UnitTest/Fixtures/SendRequest/FirstPipelineBehavior.cs new file mode 100644 index 0000000..f7146d5 --- /dev/null +++ b/tests/DispatchR.UnitTest/Fixtures/SendRequest/FirstPipelineBehavior.cs @@ -0,0 +1,12 @@ +using DispatchR.Requests.Send; + +namespace DispatchR.UnitTest.Fixtures.SendRequest; + +public class FirstPipelineBehavior() : IPipelineBehavior> +{ + public required IRequestHandler> NextPipeline { get; set; } + public ValueTask Handle(PingValueTask request, CancellationToken cancellationToken) + { + return NextPipeline.Handle(request, cancellationToken); + } +} \ No newline at end of file diff --git a/tests/DispatchR.UnitTest/Fixtures/SendRequest/GenericPipelineBehavior.cs b/tests/DispatchR.UnitTest/Fixtures/SendRequest/GenericPipelineBehavior.cs new file mode 100644 index 0000000..1aee117 --- /dev/null +++ b/tests/DispatchR.UnitTest/Fixtures/SendRequest/GenericPipelineBehavior.cs @@ -0,0 +1,15 @@ +using DispatchR.Requests.Send; + +namespace DispatchR.UnitTest.Fixtures.SendRequest; + +public class GenericPipelineBehavior() + : IPipelineBehavior> + where TRequest : class, IRequest>, new() +{ + public ValueTask Handle(TRequest request, CancellationToken cancellationToken) + { + return NextPipeline.Handle(request, cancellationToken); + } + + public required IRequestHandler> NextPipeline { get; set; } +} \ No newline at end of file diff --git a/tests/DispatchR.UnitTest/Fixtures/SendRequest/PingValueTask.cs b/tests/DispatchR.UnitTest/Fixtures/SendRequest/PingValueTask.cs new file mode 100644 index 0000000..1f28fe9 --- /dev/null +++ b/tests/DispatchR.UnitTest/Fixtures/SendRequest/PingValueTask.cs @@ -0,0 +1,8 @@ +using DispatchR.Requests.Send; + +namespace DispatchR.UnitTest.Fixtures.SendRequest; + +public class PingValueTask : IRequest> +{ + +} \ No newline at end of file diff --git a/tests/DispatchR.UnitTest/Fixtures/SendRequest/PingValueTaskHandler.cs b/tests/DispatchR.UnitTest/Fixtures/SendRequest/PingValueTaskHandler.cs new file mode 100644 index 0000000..9abcbac --- /dev/null +++ b/tests/DispatchR.UnitTest/Fixtures/SendRequest/PingValueTaskHandler.cs @@ -0,0 +1,11 @@ +using DispatchR.Requests.Send; + +namespace DispatchR.UnitTest.Fixtures.SendRequest; + +public class PingValueTaskHandler() : IRequestHandler> +{ + public ValueTask Handle(PingValueTask request, CancellationToken cancellationToken) + { + return ValueTask.FromResult(1); + } +} \ No newline at end of file diff --git a/tests/DispatchR.UnitTest/Fixtures/SendRequest/SecondPipelineBehavior.cs b/tests/DispatchR.UnitTest/Fixtures/SendRequest/SecondPipelineBehavior.cs new file mode 100644 index 0000000..4ae2a6a --- /dev/null +++ b/tests/DispatchR.UnitTest/Fixtures/SendRequest/SecondPipelineBehavior.cs @@ -0,0 +1,14 @@ +using DispatchR.Requests.Send; + +namespace DispatchR.UnitTest.Fixtures.SendRequest +{ + public class SecondPipelineBehavior() : IPipelineBehavior> + { + public required IRequestHandler> NextPipeline { get; set; } + + public ValueTask Handle(PingValueTask request, CancellationToken cancellationToken) + { + return NextPipeline.Handle(request, cancellationToken); + } + } +} \ No newline at end of file diff --git a/tests/DispatchR.UnitTest/Fixtures/StreamRequest/CounterPipelineStreamHandler.cs b/tests/DispatchR.UnitTest/Fixtures/StreamRequest/CounterPipelineStreamHandler.cs new file mode 100644 index 0000000..4c78c45 --- /dev/null +++ b/tests/DispatchR.UnitTest/Fixtures/StreamRequest/CounterPipelineStreamHandler.cs @@ -0,0 +1,17 @@ +using System.Runtime.CompilerServices; +using DispatchR.Requests.Stream; + +namespace DispatchR.UnitTest.Fixtures.StreamRequest; + +public class CounterPipelineStreamHandler : IStreamPipelineBehavior +{ + public required IStreamRequestHandler NextPipeline { get; set; } + + public async IAsyncEnumerable Handle(CounterStreamRequest request, [EnumeratorCancellation] CancellationToken cancellationToken) + { + await foreach (var response in NextPipeline.Handle(request, cancellationToken).ConfigureAwait(false)) + { + yield return response; + } + } +} \ No newline at end of file diff --git a/tests/DispatchR.UnitTest/Fixtures/StreamRequest/CounterStreamHandler.cs b/tests/DispatchR.UnitTest/Fixtures/StreamRequest/CounterStreamHandler.cs new file mode 100644 index 0000000..cd26676 --- /dev/null +++ b/tests/DispatchR.UnitTest/Fixtures/StreamRequest/CounterStreamHandler.cs @@ -0,0 +1,13 @@ +using System.Runtime.CompilerServices; +using DispatchR.Requests.Stream; + +namespace DispatchR.UnitTest.Fixtures.StreamRequest; + +public class CounterStreamHandler() : IStreamRequestHandler +{ + public async IAsyncEnumerable Handle(CounterStreamRequest request, [EnumeratorCancellation] CancellationToken cancellationToken) + { + await Task.CompletedTask; + yield return string.Empty; + } +} \ No newline at end of file diff --git a/tests/DispatchR.UnitTest/Fixtures/StreamRequest/CounterStreamRequest.cs b/tests/DispatchR.UnitTest/Fixtures/StreamRequest/CounterStreamRequest.cs new file mode 100644 index 0000000..abab345 --- /dev/null +++ b/tests/DispatchR.UnitTest/Fixtures/StreamRequest/CounterStreamRequest.cs @@ -0,0 +1,5 @@ +using DispatchR.Requests.Stream; + +namespace DispatchR.UnitTest.Fixtures.StreamRequest; + +public class CounterStreamRequest : IStreamRequest { } \ No newline at end of file diff --git a/tests/DispatchR.UnitTest/Fixtures/StreamRequest/GenericPipelineBehavior.cs b/tests/DispatchR.UnitTest/Fixtures/StreamRequest/GenericPipelineBehavior.cs new file mode 100644 index 0000000..5223bb9 --- /dev/null +++ b/tests/DispatchR.UnitTest/Fixtures/StreamRequest/GenericPipelineBehavior.cs @@ -0,0 +1,18 @@ +using DispatchR.Requests.Stream; + +namespace DispatchR.UnitTest.Fixtures.StreamRequest; + +public class GenericPipelineBehavior() + : IStreamPipelineBehavior + where TRequest : class, IStreamRequest, new() +{ + public async IAsyncEnumerable Handle(TRequest request, CancellationToken cancellationToken) + { + await foreach (var response in NextPipeline.Handle(request, cancellationToken).ConfigureAwait(false)) + { + yield return response; + } + } + + public IStreamRequestHandler NextPipeline { get; set; } +} \ No newline at end of file diff --git a/tests/DispatchR.UnitTest/RequestHandlerTests.cs b/tests/DispatchR.UnitTest/RequestHandlerTests.cs new file mode 100644 index 0000000..db169e5 --- /dev/null +++ b/tests/DispatchR.UnitTest/RequestHandlerTests.cs @@ -0,0 +1,57 @@ +using DispatchR.Configuration; +using DispatchR.Extensions; +using DispatchR.Requests; +using DispatchR.UnitTest.Fixtures.SendRequest; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; + +namespace DispatchR.UnitTest; + +public class RequestHandlerTests +{ + [Fact] + public void Send_ReturnsExpectedResponse_SyncRequestHandler() + { + } + + [Fact] + public void Send_ReturnsExpectedResponse_AsyncRequestHandlerWithTask() + { + } + + [Fact] + public async Task Send_ReturnsExpectedResponse_AsyncRequestHandlerWithValueTask() + { + // Arrange + var services = new Microsoft.Extensions.DependencyInjection.ServiceCollection(); + services.AddDispatchR(cfg => + { + cfg.Assemblies.Add(this.GetType().Assembly); + cfg.RegisterPipelines = false; + cfg.RegisterNotifications = false; + cfg.IncludeHandlers = [typeof(PingValueTask)]; + }); + var serviceProvider = services.BuildServiceProvider(); + var mediator = serviceProvider.GetRequiredService(); + + // Act + var result = await mediator.Send(new PingValueTask(), CancellationToken.None); + + Assert.Equal(1, result); + } + + [Fact] + public void Send_UsesPipelineBehaviorsInCorrectOrder_RequestWithMultiplePipelines() + { + } + + [Fact] + public void Send_ThrowsException_WhenNoHandlerIsRegistered() + { + } + + [Fact] + public void Send_UsesCachedHandler_InstanceReusedInScopedLifetime() + { + } +} \ No newline at end of file diff --git a/tests/DispatchR.UnitTest/SendRequests.cs b/tests/DispatchR.UnitTest/SendRequests.cs new file mode 100644 index 0000000..e9b1731 --- /dev/null +++ b/tests/DispatchR.UnitTest/SendRequests.cs @@ -0,0 +1,9 @@ +namespace DispatchR.UnitTest; + +public class SendRequests +{ + [Fact] + public void Send() + { + } +} \ No newline at end of file From 592a79543045ec3f59a80d400e8b60ff9106dfc9 Mon Sep 17 00:00:00 2001 From: hasanxdev Date: Tue, 29 Jul 2025 04:07:35 +0330 Subject: [PATCH 02/23] =?UTF-8?q?=E2=9C=A8=20Add=20unit=20tests=20for=20Di?= =?UTF-8?q?spatchR=20configuration=20and=20handler=20registration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DispatchR.sln | 30 +++++ src/Benchmark/Program.cs | 52 ++++---- .../Configuration/ServiceRegistrator.cs | 14 ++- .../Exceptions/HandlerNotFoundException.cs | 7 ++ src/DispatchR/Requests/IMediator.cs | 12 +- src/Sample/Program.cs | 8 +- .../DispatchR.IntegrationTest.csproj | 28 +++++ .../RequestHandlerTests.cs | 70 +++++++++++ .../DispatchR.TestCommon.csproj | 12 ++ .../DispatchR.TestCommon/Fixtures/Fixture.cs | 13 ++ .../Notification/MultiHandlersNotification.cs | 2 +- .../Notification/NotificationOneHandler.cs | 2 +- .../Notification/NotificationThreeHandler.cs | 2 +- .../Notification/NotificationTwoHandler.cs | 2 +- .../SendRequest/GenericPipelineBehavior.cs | 2 +- .../SendRequest/RequestWithoutHandler.cs | 7 ++ .../RequestReusedInScopedLifetime.cs | 8 ++ .../RequestReusedInScopedLifetimeHandler.cs | 12 ++ .../Fixtures/SendRequest/Sync/Ping.cs | 8 ++ .../Fixtures/SendRequest/Sync/PingHandler.cs | 11 ++ .../Fixtures/SendRequest/Task/PingTask.cs | 8 ++ .../SendRequest/Task/PingTaskHandler.cs | 11 ++ .../SendRequest/ValueTask}/PingValueTask.cs | 2 +- .../PingValueTaskFirstPipelineBehavior.cs} | 6 +- .../ValueTask}/PingValueTaskHandler.cs | 4 +- .../PingValueTaskSecondPipelineBehavior.cs} | 6 +- .../CounterPipelineStreamHandler.cs | 2 +- .../StreamRequest/CounterStreamHandler.cs | 2 +- .../StreamRequest/CounterStreamRequest.cs | 2 +- .../StreamRequest/GenericPipelineBehavior.cs | 2 +- .../AddDispachRConfigurationTests.cs | 113 ++++++++++++++++++ .../DispatchR.UnitTest.csproj | 7 +- tests/DispatchR.UnitTest/Fixtures/Fixture.cs | 6 - .../DispatchR.UnitTest/RequestHandlerTests.cs | 112 ++++++++++++++++- 34 files changed, 523 insertions(+), 62 deletions(-) create mode 100644 src/DispatchR/Exceptions/HandlerNotFoundException.cs create mode 100644 tests/DispatchR.IntegrationTest/DispatchR.IntegrationTest.csproj create mode 100644 tests/DispatchR.IntegrationTest/RequestHandlerTests.cs create mode 100644 tests/DispatchR.TestCommon/DispatchR.TestCommon.csproj create mode 100644 tests/DispatchR.TestCommon/Fixtures/Fixture.cs rename tests/{DispatchR.UnitTest => DispatchR.TestCommon}/Fixtures/Notification/MultiHandlersNotification.cs (67%) rename tests/{DispatchR.UnitTest => DispatchR.TestCommon}/Fixtures/Notification/NotificationOneHandler.cs (84%) rename tests/{DispatchR.UnitTest => DispatchR.TestCommon}/Fixtures/Notification/NotificationThreeHandler.cs (84%) rename tests/{DispatchR.UnitTest => DispatchR.TestCommon}/Fixtures/Notification/NotificationTwoHandler.cs (84%) rename tests/{DispatchR.UnitTest => DispatchR.TestCommon}/Fixtures/SendRequest/GenericPipelineBehavior.cs (90%) create mode 100644 tests/DispatchR.TestCommon/Fixtures/SendRequest/RequestWithoutHandler.cs create mode 100644 tests/DispatchR.TestCommon/Fixtures/SendRequest/ReusedInScopedLifetime/RequestReusedInScopedLifetime.cs create mode 100644 tests/DispatchR.TestCommon/Fixtures/SendRequest/ReusedInScopedLifetime/RequestReusedInScopedLifetimeHandler.cs create mode 100644 tests/DispatchR.TestCommon/Fixtures/SendRequest/Sync/Ping.cs create mode 100644 tests/DispatchR.TestCommon/Fixtures/SendRequest/Sync/PingHandler.cs create mode 100644 tests/DispatchR.TestCommon/Fixtures/SendRequest/Task/PingTask.cs create mode 100644 tests/DispatchR.TestCommon/Fixtures/SendRequest/Task/PingTaskHandler.cs rename tests/{DispatchR.UnitTest/Fixtures/SendRequest => DispatchR.TestCommon/Fixtures/SendRequest/ValueTask}/PingValueTask.cs (63%) rename tests/{DispatchR.UnitTest/Fixtures/SendRequest/FirstPipelineBehavior.cs => DispatchR.TestCommon/Fixtures/SendRequest/ValueTask/PingValueTaskFirstPipelineBehavior.cs} (53%) rename tests/{DispatchR.UnitTest/Fixtures/SendRequest => DispatchR.TestCommon/Fixtures/SendRequest/ValueTask}/PingValueTaskHandler.cs (64%) rename tests/{DispatchR.UnitTest/Fixtures/SendRequest/SecondPipelineBehavior.cs => DispatchR.TestCommon/Fixtures/SendRequest/ValueTask/PingValueTaskSecondPipelineBehavior.cs} (54%) rename tests/{DispatchR.UnitTest => DispatchR.TestCommon}/Fixtures/StreamRequest/CounterPipelineStreamHandler.cs (91%) rename tests/{DispatchR.UnitTest => DispatchR.TestCommon}/Fixtures/StreamRequest/CounterStreamHandler.cs (87%) rename tests/{DispatchR.UnitTest => DispatchR.TestCommon}/Fixtures/StreamRequest/CounterStreamRequest.cs (68%) rename tests/{DispatchR.UnitTest => DispatchR.TestCommon}/Fixtures/StreamRequest/GenericPipelineBehavior.cs (91%) create mode 100644 tests/DispatchR.UnitTest/AddDispachRConfigurationTests.cs delete mode 100644 tests/DispatchR.UnitTest/Fixtures/Fixture.cs diff --git a/DispatchR.sln b/DispatchR.sln index 73b8ed9..ff93c34 100644 --- a/DispatchR.sln +++ b/DispatchR.sln @@ -39,6 +39,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspireModularSample.Service EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DispatchR.UnitTest", "tests\DispatchR.UnitTest\DispatchR.UnitTest.csproj", "{806030F5-86B1-4EFC-923C-94FF7D32DFC9}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DispatchR.IntegrationTest", "tests\DispatchR.IntegrationTest\DispatchR.IntegrationTest.csproj", "{D8646A62-9FE7-4E79-861C-49391007F98A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DispatchR.TestCommon", "tests\DispatchR.TestCommon\DispatchR.TestCommon.csproj", "{F01B6563-64D0-4316-947C-AB75426D9924}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -145,6 +149,30 @@ Global {806030F5-86B1-4EFC-923C-94FF7D32DFC9}.Release|x64.Build.0 = Release|Any CPU {806030F5-86B1-4EFC-923C-94FF7D32DFC9}.Release|x86.ActiveCfg = Release|Any CPU {806030F5-86B1-4EFC-923C-94FF7D32DFC9}.Release|x86.Build.0 = Release|Any CPU + {D8646A62-9FE7-4E79-861C-49391007F98A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D8646A62-9FE7-4E79-861C-49391007F98A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D8646A62-9FE7-4E79-861C-49391007F98A}.Debug|x64.ActiveCfg = Debug|Any CPU + {D8646A62-9FE7-4E79-861C-49391007F98A}.Debug|x64.Build.0 = Debug|Any CPU + {D8646A62-9FE7-4E79-861C-49391007F98A}.Debug|x86.ActiveCfg = Debug|Any CPU + {D8646A62-9FE7-4E79-861C-49391007F98A}.Debug|x86.Build.0 = Debug|Any CPU + {D8646A62-9FE7-4E79-861C-49391007F98A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D8646A62-9FE7-4E79-861C-49391007F98A}.Release|Any CPU.Build.0 = Release|Any CPU + {D8646A62-9FE7-4E79-861C-49391007F98A}.Release|x64.ActiveCfg = Release|Any CPU + {D8646A62-9FE7-4E79-861C-49391007F98A}.Release|x64.Build.0 = Release|Any CPU + {D8646A62-9FE7-4E79-861C-49391007F98A}.Release|x86.ActiveCfg = Release|Any CPU + {D8646A62-9FE7-4E79-861C-49391007F98A}.Release|x86.Build.0 = Release|Any CPU + {F01B6563-64D0-4316-947C-AB75426D9924}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F01B6563-64D0-4316-947C-AB75426D9924}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F01B6563-64D0-4316-947C-AB75426D9924}.Debug|x64.ActiveCfg = Debug|Any CPU + {F01B6563-64D0-4316-947C-AB75426D9924}.Debug|x64.Build.0 = Debug|Any CPU + {F01B6563-64D0-4316-947C-AB75426D9924}.Debug|x86.ActiveCfg = Debug|Any CPU + {F01B6563-64D0-4316-947C-AB75426D9924}.Debug|x86.Build.0 = Debug|Any CPU + {F01B6563-64D0-4316-947C-AB75426D9924}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F01B6563-64D0-4316-947C-AB75426D9924}.Release|Any CPU.Build.0 = Release|Any CPU + {F01B6563-64D0-4316-947C-AB75426D9924}.Release|x64.ActiveCfg = Release|Any CPU + {F01B6563-64D0-4316-947C-AB75426D9924}.Release|x64.Build.0 = Release|Any CPU + {F01B6563-64D0-4316-947C-AB75426D9924}.Release|x86.ActiveCfg = Release|Any CPU + {F01B6563-64D0-4316-947C-AB75426D9924}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -161,5 +189,7 @@ Global {7D2890FF-66F7-4870-BB89-952167AB0681} = {BA3021C0-B64E-B700-D62A-004419E20C36} {707E07BA-998C-49DE-BA56-7E9C0B6B7DBA} = {BA3021C0-B64E-B700-D62A-004419E20C36} {806030F5-86B1-4EFC-923C-94FF7D32DFC9} = {7F7601D5-C62E-4EA3-8B71-E946A62B4529} + {D8646A62-9FE7-4E79-861C-49391007F98A} = {7F7601D5-C62E-4EA3-8B71-E946A62B4529} + {F01B6563-64D0-4316-947C-AB75426D9924} = {7F7601D5-C62E-4EA3-8B71-E946A62B4529} EndGlobalSection EndGlobal diff --git a/src/Benchmark/Program.cs b/src/Benchmark/Program.cs index 84c5c3b..110203b 100644 --- a/src/Benchmark/Program.cs +++ b/src/Benchmark/Program.cs @@ -1,4 +1,3 @@ -using System.ComponentModel.DataAnnotations; using Benchmark; using Benchmark.Notification; using Benchmark.SendRequest; @@ -19,33 +18,36 @@ .AddColumn(new OperationsColumn()) ); -public class OperationsColumn : IColumn +namespace Benchmark { - public string Id => nameof(OperationsColumn); - public string ColumnName => "OpsCount"; - public bool AlwaysShow => true; - public ColumnCategory Category => ColumnCategory.Custom; - public int PriorityInCategory => -10; - public bool IsNumeric => true; - public UnitType UnitType => UnitType.Dimensionless; - public string Legend => "Number of operations per invoke"; - - public string GetValue(Summary summary, BenchmarkCase benchmarkCase) + public class OperationsColumn : IColumn { - return benchmarkCase.Descriptor.WorkloadMethod - .GetCustomAttributes(typeof(BenchmarkAttribute), false) - .Cast() - .FirstOrDefault()?.OperationsPerInvoke.ToString() ?? "1"; - } + public string Id => nameof(OperationsColumn); + public string ColumnName => "OpsCount"; + public bool AlwaysShow => true; + public ColumnCategory Category => ColumnCategory.Custom; + public int PriorityInCategory => -10; + public bool IsNumeric => true; + public UnitType UnitType => UnitType.Dimensionless; + public string Legend => "Number of operations per invoke"; - public string GetValue(Summary summary, BenchmarkCase benchmarkCase, SummaryStyle style) - => GetValue(summary, benchmarkCase); + public string GetValue(Summary summary, BenchmarkCase benchmarkCase) + { + return benchmarkCase.Descriptor.WorkloadMethod + .GetCustomAttributes(typeof(BenchmarkAttribute), false) + .Cast() + .FirstOrDefault()?.OperationsPerInvoke.ToString() ?? "1"; + } - public bool IsDefault(Summary summary, BenchmarkCase benchmarkCase) - { - return true; - } + public string GetValue(Summary summary, BenchmarkCase benchmarkCase, SummaryStyle style) + => GetValue(summary, benchmarkCase); + + public bool IsDefault(Summary summary, BenchmarkCase benchmarkCase) + { + return true; + } - public bool IsAvailable(Summary summary) => true; - public bool IsDefault(Summary summary) => false; + public bool IsAvailable(Summary summary) => true; + public bool IsDefault(Summary summary) => false; + } } \ No newline at end of file diff --git a/src/DispatchR/Configuration/ServiceRegistrator.cs b/src/DispatchR/Configuration/ServiceRegistrator.cs index f6e9a17..3bfd207 100644 --- a/src/DispatchR/Configuration/ServiceRegistrator.cs +++ b/src/DispatchR/Configuration/ServiceRegistrator.cs @@ -53,12 +53,22 @@ public static void RegisterHandlers(IServiceCollection services, List allT .Where(p => { var interfaces = p.GetInterfaces(); + if (p.IsGenericType) + { + // handle generic pipelines + return interfaces + .FirstOrDefault(inter => + inter.IsGenericType && + inter.GetGenericTypeDefinition() == behaviorType) + ?.GetInterfaces().First().GetGenericTypeDefinition() == + handlerInterface.GetGenericTypeDefinition(); + } + return interfaces .FirstOrDefault(inter => inter.IsGenericType && inter.GetGenericTypeDefinition() == behaviorType) - ?.GetInterfaces().First().GetGenericTypeDefinition() == - handlerInterface.GetGenericTypeDefinition(); + ?.GetInterfaces().First() == handlerInterface; }).ToList(); // Sort pipelines by the specified order passed via ConfigurationOptions diff --git a/src/DispatchR/Exceptions/HandlerNotFoundException.cs b/src/DispatchR/Exceptions/HandlerNotFoundException.cs new file mode 100644 index 0000000..44f02d0 --- /dev/null +++ b/src/DispatchR/Exceptions/HandlerNotFoundException.cs @@ -0,0 +1,7 @@ +namespace DispatchR.Exceptions; + +public class HandlerNotFoundException() : Exception( + $""" + Handler for request of type '{typeof(TRequest).Name}' returning '{typeof(TResponse).Name}' was not found. + Make sure you have registered a handler that implements IRequestHandler<{typeof(TRequest).Name}, {typeof(TResponse).Name}> in the DI container. + """); \ No newline at end of file diff --git a/src/DispatchR/Requests/IMediator.cs b/src/DispatchR/Requests/IMediator.cs index 4519e93..e99043f 100644 --- a/src/DispatchR/Requests/IMediator.cs +++ b/src/DispatchR/Requests/IMediator.cs @@ -1,4 +1,5 @@ using System.Runtime.CompilerServices; +using DispatchR.Exceptions; using DispatchR.Requests.Notification; using DispatchR.Requests.Send; using DispatchR.Requests.Stream; @@ -23,8 +24,15 @@ public sealed class Mediator(IServiceProvider serviceProvider) : IMediator public TResponse Send(IRequest request, CancellationToken cancellationToken) where TRequest : class, IRequest { - return serviceProvider.GetRequiredService>() - .Handle(Unsafe.As(request), cancellationToken); + try + { + return serviceProvider.GetRequiredService>() + .Handle(Unsafe.As(request), cancellationToken); + } + catch (Exception e) when (e.Message.Contains("No service for type", StringComparison.OrdinalIgnoreCase)) + { + throw new HandlerNotFoundException(); + } } public IAsyncEnumerable CreateStream(IStreamRequest request, diff --git a/src/Sample/Program.cs b/src/Sample/Program.cs index 3bba68d..62d7caf 100644 --- a/src/Sample/Program.cs +++ b/src/Sample/Program.cs @@ -1,4 +1,5 @@ using DispatchR.Extensions; +using Sample; using Scalar.AspNetCore; using DispatchRNotificationSample = Sample.DispatchR.Notification; using DispatchRSample = Sample.DispatchR.SendRequest; @@ -125,7 +126,10 @@ app.Run(); -record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary) +namespace Sample { - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary) + { + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + } } \ No newline at end of file diff --git a/tests/DispatchR.IntegrationTest/DispatchR.IntegrationTest.csproj b/tests/DispatchR.IntegrationTest/DispatchR.IntegrationTest.csproj new file mode 100644 index 0000000..bb4c3da --- /dev/null +++ b/tests/DispatchR.IntegrationTest/DispatchR.IntegrationTest.csproj @@ -0,0 +1,28 @@ + + + + net9.0 + enable + enable + false + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/DispatchR.IntegrationTest/RequestHandlerTests.cs b/tests/DispatchR.IntegrationTest/RequestHandlerTests.cs new file mode 100644 index 0000000..4080b85 --- /dev/null +++ b/tests/DispatchR.IntegrationTest/RequestHandlerTests.cs @@ -0,0 +1,70 @@ +using DispatchR.Extensions; +using DispatchR.Requests; +using DispatchR.Requests.Send; +using DispatchR.TestCommon.Fixtures; +using DispatchR.TestCommon.Fixtures.SendRequest.Sync; +using DispatchR.TestCommon.Fixtures.SendRequest.Task; +using DispatchR.TestCommon.Fixtures.SendRequest.ValueTask; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Moq; + +namespace DispatchR.IntegrationTest; + +public class RequestHandlerTests +{ + [Fact] + public async Task Send_UsesPipelineBehaviors_RequestWithSinglePipelines() + { + // Arrange + var services = new ServiceCollection(); + services.AddDispatchR(cfg => + { + cfg.Assemblies.Add(typeof(Fixture).Assembly); + cfg.RegisterPipelines = true; + cfg.RegisterNotifications = false; + cfg.IncludeHandlers = [typeof(PingValueTaskHandler)]; + }); + + var firstPipeline = services + .Single(p => p.IsKeyedService && p.KeyedImplementationType == typeof(PingValueTaskFirstPipelineBehavior)); + var secondPipeline = services + .Single(p => p.IsKeyedService && p.KeyedImplementationType == typeof(PingValueTaskSecondPipelineBehavior)); + + var spyPipelineTwoMock = new Mock>>(); + spyPipelineTwoMock.Setup(p => p.Handle(It.IsAny(), It.IsAny())) + .Returns((PingValueTask req, CancellationToken ct) => new PingValueTaskHandler().Handle(req, ct)); + + var spyPipelineOneMock = new Mock>>(); + spyPipelineOneMock.Setup(p => p.Handle(It.IsAny(), It.IsAny())) + .Returns((PingValueTask req, CancellationToken ct) => spyPipelineTwoMock.Object.Handle(req, ct)); + + services.RemoveAllKeyed(typeof(IRequestHandler), firstPipeline.ServiceKey); + services.RemoveAllKeyed(typeof(IRequestHandler), secondPipeline.ServiceKey); + + services.AddKeyedScoped(typeof(IRequestHandler), firstPipeline.ServiceKey!, (_,__) => spyPipelineTwoMock.Object); + services.AddKeyedScoped(typeof(IRequestHandler), secondPipeline.ServiceKey!, (_,__) => spyPipelineOneMock.Object); + + var serviceProvider = services.BuildServiceProvider(); + var mediator = serviceProvider.GetRequiredService(); + + + // Act + var result = await mediator.Send(new PingValueTask(), CancellationToken.None); + + // Assert + Assert.Equal(1, result); + spyPipelineOneMock.Verify(p => p.Handle(It.IsAny(), It.IsAny()), Times.Exactly(1)); + spyPipelineTwoMock.Verify(p => p.Handle(It.IsAny(), It.IsAny()), Times.Exactly(1)); + } + + [Fact] + public void Send_ThrowsException_WhenNoHandlerIsRegistered() + { + } + + [Fact] + public void Send_UsesCachedHandler_InstanceReusedInScopedLifetime() + { + } +} \ No newline at end of file diff --git a/tests/DispatchR.TestCommon/DispatchR.TestCommon.csproj b/tests/DispatchR.TestCommon/DispatchR.TestCommon.csproj new file mode 100644 index 0000000..bb8e64a --- /dev/null +++ b/tests/DispatchR.TestCommon/DispatchR.TestCommon.csproj @@ -0,0 +1,12 @@ + + + + net9.0 + enable + enable + + + + + + diff --git a/tests/DispatchR.TestCommon/Fixtures/Fixture.cs b/tests/DispatchR.TestCommon/Fixtures/Fixture.cs new file mode 100644 index 0000000..a4af0dd --- /dev/null +++ b/tests/DispatchR.TestCommon/Fixtures/Fixture.cs @@ -0,0 +1,13 @@ +using DispatchR.TestCommon.Fixtures.SendRequest.Task; +using DispatchR.TestCommon.Fixtures.SendRequest.ValueTask; + +namespace DispatchR.TestCommon.Fixtures; + +public class Fixture +{ + public static PingTask AnyRequestWithoutPipeline => new(); + public static PingTaskHandler AnyHandlerRequestWithoutPipeline => new(); + + public static PingValueTask AnyRequestWithPipeline => new(); + public static PingValueTaskHandler AnyHandlerRequestWithPipeline => new(); +} \ No newline at end of file diff --git a/tests/DispatchR.UnitTest/Fixtures/Notification/MultiHandlersNotification.cs b/tests/DispatchR.TestCommon/Fixtures/Notification/MultiHandlersNotification.cs similarity index 67% rename from tests/DispatchR.UnitTest/Fixtures/Notification/MultiHandlersNotification.cs rename to tests/DispatchR.TestCommon/Fixtures/Notification/MultiHandlersNotification.cs index e60cd38..70195cc 100644 --- a/tests/DispatchR.UnitTest/Fixtures/Notification/MultiHandlersNotification.cs +++ b/tests/DispatchR.TestCommon/Fixtures/Notification/MultiHandlersNotification.cs @@ -1,5 +1,5 @@ using DispatchR.Requests.Notification; -namespace DispatchR.UnitTest.Fixtures.Notification; +namespace DispatchR.TestCommon.Fixtures.Notification; public sealed record MultiHandlersNotification(Guid Id) : INotification; \ No newline at end of file diff --git a/tests/DispatchR.UnitTest/Fixtures/Notification/NotificationOneHandler.cs b/tests/DispatchR.TestCommon/Fixtures/Notification/NotificationOneHandler.cs similarity index 84% rename from tests/DispatchR.UnitTest/Fixtures/Notification/NotificationOneHandler.cs rename to tests/DispatchR.TestCommon/Fixtures/Notification/NotificationOneHandler.cs index 0becef7..1e09860 100644 --- a/tests/DispatchR.UnitTest/Fixtures/Notification/NotificationOneHandler.cs +++ b/tests/DispatchR.TestCommon/Fixtures/Notification/NotificationOneHandler.cs @@ -1,6 +1,6 @@ using DispatchR.Requests.Notification; -namespace DispatchR.UnitTest.Fixtures.Notification; +namespace DispatchR.TestCommon.Fixtures.Notification; public sealed class NotificationOneHandler() : INotificationHandler { diff --git a/tests/DispatchR.UnitTest/Fixtures/Notification/NotificationThreeHandler.cs b/tests/DispatchR.TestCommon/Fixtures/Notification/NotificationThreeHandler.cs similarity index 84% rename from tests/DispatchR.UnitTest/Fixtures/Notification/NotificationThreeHandler.cs rename to tests/DispatchR.TestCommon/Fixtures/Notification/NotificationThreeHandler.cs index c3dbbf8..3da0fa0 100644 --- a/tests/DispatchR.UnitTest/Fixtures/Notification/NotificationThreeHandler.cs +++ b/tests/DispatchR.TestCommon/Fixtures/Notification/NotificationThreeHandler.cs @@ -1,6 +1,6 @@ using DispatchR.Requests.Notification; -namespace DispatchR.UnitTest.Fixtures.Notification; +namespace DispatchR.TestCommon.Fixtures.Notification; public sealed class NotificationThreeHandler() : INotificationHandler { diff --git a/tests/DispatchR.UnitTest/Fixtures/Notification/NotificationTwoHandler.cs b/tests/DispatchR.TestCommon/Fixtures/Notification/NotificationTwoHandler.cs similarity index 84% rename from tests/DispatchR.UnitTest/Fixtures/Notification/NotificationTwoHandler.cs rename to tests/DispatchR.TestCommon/Fixtures/Notification/NotificationTwoHandler.cs index a80c5f3..8750f9c 100644 --- a/tests/DispatchR.UnitTest/Fixtures/Notification/NotificationTwoHandler.cs +++ b/tests/DispatchR.TestCommon/Fixtures/Notification/NotificationTwoHandler.cs @@ -1,6 +1,6 @@ using DispatchR.Requests.Notification; -namespace DispatchR.UnitTest.Fixtures.Notification; +namespace DispatchR.TestCommon.Fixtures.Notification; public sealed class NotificationTwoHandler() : INotificationHandler { diff --git a/tests/DispatchR.UnitTest/Fixtures/SendRequest/GenericPipelineBehavior.cs b/tests/DispatchR.TestCommon/Fixtures/SendRequest/GenericPipelineBehavior.cs similarity index 90% rename from tests/DispatchR.UnitTest/Fixtures/SendRequest/GenericPipelineBehavior.cs rename to tests/DispatchR.TestCommon/Fixtures/SendRequest/GenericPipelineBehavior.cs index 1aee117..63f7b3e 100644 --- a/tests/DispatchR.UnitTest/Fixtures/SendRequest/GenericPipelineBehavior.cs +++ b/tests/DispatchR.TestCommon/Fixtures/SendRequest/GenericPipelineBehavior.cs @@ -1,6 +1,6 @@ using DispatchR.Requests.Send; -namespace DispatchR.UnitTest.Fixtures.SendRequest; +namespace DispatchR.TestCommon.Fixtures.SendRequest; public class GenericPipelineBehavior() : IPipelineBehavior> diff --git a/tests/DispatchR.TestCommon/Fixtures/SendRequest/RequestWithoutHandler.cs b/tests/DispatchR.TestCommon/Fixtures/SendRequest/RequestWithoutHandler.cs new file mode 100644 index 0000000..8fe8b44 --- /dev/null +++ b/tests/DispatchR.TestCommon/Fixtures/SendRequest/RequestWithoutHandler.cs @@ -0,0 +1,7 @@ +using DispatchR.Requests.Send; + +namespace DispatchR.TestCommon.Fixtures.SendRequest; + +public class RequestWithoutHandler : IRequest +{ +} \ No newline at end of file diff --git a/tests/DispatchR.TestCommon/Fixtures/SendRequest/ReusedInScopedLifetime/RequestReusedInScopedLifetime.cs b/tests/DispatchR.TestCommon/Fixtures/SendRequest/ReusedInScopedLifetime/RequestReusedInScopedLifetime.cs new file mode 100644 index 0000000..b1ed25c --- /dev/null +++ b/tests/DispatchR.TestCommon/Fixtures/SendRequest/ReusedInScopedLifetime/RequestReusedInScopedLifetime.cs @@ -0,0 +1,8 @@ +using DispatchR.Requests.Send; + +namespace DispatchR.TestCommon.Fixtures.SendRequest.ReusedInScopedLifetime; + +public class RequestReusedInScopedLifetime : IRequest +{ + +} \ No newline at end of file diff --git a/tests/DispatchR.TestCommon/Fixtures/SendRequest/ReusedInScopedLifetime/RequestReusedInScopedLifetimeHandler.cs b/tests/DispatchR.TestCommon/Fixtures/SendRequest/ReusedInScopedLifetime/RequestReusedInScopedLifetimeHandler.cs new file mode 100644 index 0000000..d5c00a7 --- /dev/null +++ b/tests/DispatchR.TestCommon/Fixtures/SendRequest/ReusedInScopedLifetime/RequestReusedInScopedLifetimeHandler.cs @@ -0,0 +1,12 @@ +using DispatchR.Requests.Send; + +namespace DispatchR.TestCommon.Fixtures.SendRequest.ReusedInScopedLifetime; + +public class RequestReusedInScopedLifetimeHandler : IRequestHandler +{ + private int _counter = 0; + public int Handle(RequestReusedInScopedLifetime request, CancellationToken cancellationToken) + { + return _counter++; + } +} \ No newline at end of file diff --git a/tests/DispatchR.TestCommon/Fixtures/SendRequest/Sync/Ping.cs b/tests/DispatchR.TestCommon/Fixtures/SendRequest/Sync/Ping.cs new file mode 100644 index 0000000..5bf74c0 --- /dev/null +++ b/tests/DispatchR.TestCommon/Fixtures/SendRequest/Sync/Ping.cs @@ -0,0 +1,8 @@ +using DispatchR.Requests.Send; + +namespace DispatchR.TestCommon.Fixtures.SendRequest.Sync; + +public class Ping : IRequest +{ + +} \ No newline at end of file diff --git a/tests/DispatchR.TestCommon/Fixtures/SendRequest/Sync/PingHandler.cs b/tests/DispatchR.TestCommon/Fixtures/SendRequest/Sync/PingHandler.cs new file mode 100644 index 0000000..0052566 --- /dev/null +++ b/tests/DispatchR.TestCommon/Fixtures/SendRequest/Sync/PingHandler.cs @@ -0,0 +1,11 @@ +using DispatchR.Requests.Send; + +namespace DispatchR.TestCommon.Fixtures.SendRequest.Sync; + +public class PingHandler() : IRequestHandler +{ + public int Handle(Ping request, CancellationToken cancellationToken) + { + return 1; + } +} \ No newline at end of file diff --git a/tests/DispatchR.TestCommon/Fixtures/SendRequest/Task/PingTask.cs b/tests/DispatchR.TestCommon/Fixtures/SendRequest/Task/PingTask.cs new file mode 100644 index 0000000..4f24a9c --- /dev/null +++ b/tests/DispatchR.TestCommon/Fixtures/SendRequest/Task/PingTask.cs @@ -0,0 +1,8 @@ +using DispatchR.Requests.Send; + +namespace DispatchR.TestCommon.Fixtures.SendRequest.Task; + +public class PingTask : IRequest> +{ + +} \ No newline at end of file diff --git a/tests/DispatchR.TestCommon/Fixtures/SendRequest/Task/PingTaskHandler.cs b/tests/DispatchR.TestCommon/Fixtures/SendRequest/Task/PingTaskHandler.cs new file mode 100644 index 0000000..0a30bb3 --- /dev/null +++ b/tests/DispatchR.TestCommon/Fixtures/SendRequest/Task/PingTaskHandler.cs @@ -0,0 +1,11 @@ +using DispatchR.Requests.Send; + +namespace DispatchR.TestCommon.Fixtures.SendRequest.Task; + +public class PingTaskHandler() : IRequestHandler> +{ + public Task Handle(PingTask request, CancellationToken cancellationToken) + { + return System.Threading.Tasks.Task.FromResult(1); + } +} \ No newline at end of file diff --git a/tests/DispatchR.UnitTest/Fixtures/SendRequest/PingValueTask.cs b/tests/DispatchR.TestCommon/Fixtures/SendRequest/ValueTask/PingValueTask.cs similarity index 63% rename from tests/DispatchR.UnitTest/Fixtures/SendRequest/PingValueTask.cs rename to tests/DispatchR.TestCommon/Fixtures/SendRequest/ValueTask/PingValueTask.cs index 1f28fe9..a5dab0b 100644 --- a/tests/DispatchR.UnitTest/Fixtures/SendRequest/PingValueTask.cs +++ b/tests/DispatchR.TestCommon/Fixtures/SendRequest/ValueTask/PingValueTask.cs @@ -1,6 +1,6 @@ using DispatchR.Requests.Send; -namespace DispatchR.UnitTest.Fixtures.SendRequest; +namespace DispatchR.TestCommon.Fixtures.SendRequest.ValueTask; public class PingValueTask : IRequest> { diff --git a/tests/DispatchR.UnitTest/Fixtures/SendRequest/FirstPipelineBehavior.cs b/tests/DispatchR.TestCommon/Fixtures/SendRequest/ValueTask/PingValueTaskFirstPipelineBehavior.cs similarity index 53% rename from tests/DispatchR.UnitTest/Fixtures/SendRequest/FirstPipelineBehavior.cs rename to tests/DispatchR.TestCommon/Fixtures/SendRequest/ValueTask/PingValueTaskFirstPipelineBehavior.cs index f7146d5..4869146 100644 --- a/tests/DispatchR.UnitTest/Fixtures/SendRequest/FirstPipelineBehavior.cs +++ b/tests/DispatchR.TestCommon/Fixtures/SendRequest/ValueTask/PingValueTaskFirstPipelineBehavior.cs @@ -1,12 +1,14 @@ using DispatchR.Requests.Send; -namespace DispatchR.UnitTest.Fixtures.SendRequest; +namespace DispatchR.TestCommon.Fixtures.SendRequest.ValueTask; -public class FirstPipelineBehavior() : IPipelineBehavior> +public class PingValueTaskFirstPipelineBehavior() : IPipelineBehavior> { + public static DateTime ExecutionTime { get; private set; } public required IRequestHandler> NextPipeline { get; set; } public ValueTask Handle(PingValueTask request, CancellationToken cancellationToken) { + ExecutionTime = DateTime.Now; return NextPipeline.Handle(request, cancellationToken); } } \ No newline at end of file diff --git a/tests/DispatchR.UnitTest/Fixtures/SendRequest/PingValueTaskHandler.cs b/tests/DispatchR.TestCommon/Fixtures/SendRequest/ValueTask/PingValueTaskHandler.cs similarity index 64% rename from tests/DispatchR.UnitTest/Fixtures/SendRequest/PingValueTaskHandler.cs rename to tests/DispatchR.TestCommon/Fixtures/SendRequest/ValueTask/PingValueTaskHandler.cs index 9abcbac..75bccf2 100644 --- a/tests/DispatchR.UnitTest/Fixtures/SendRequest/PingValueTaskHandler.cs +++ b/tests/DispatchR.TestCommon/Fixtures/SendRequest/ValueTask/PingValueTaskHandler.cs @@ -1,11 +1,11 @@ using DispatchR.Requests.Send; -namespace DispatchR.UnitTest.Fixtures.SendRequest; +namespace DispatchR.TestCommon.Fixtures.SendRequest.ValueTask; public class PingValueTaskHandler() : IRequestHandler> { public ValueTask Handle(PingValueTask request, CancellationToken cancellationToken) { - return ValueTask.FromResult(1); + return System.Threading.Tasks.ValueTask.FromResult(1); } } \ No newline at end of file diff --git a/tests/DispatchR.UnitTest/Fixtures/SendRequest/SecondPipelineBehavior.cs b/tests/DispatchR.TestCommon/Fixtures/SendRequest/ValueTask/PingValueTaskSecondPipelineBehavior.cs similarity index 54% rename from tests/DispatchR.UnitTest/Fixtures/SendRequest/SecondPipelineBehavior.cs rename to tests/DispatchR.TestCommon/Fixtures/SendRequest/ValueTask/PingValueTaskSecondPipelineBehavior.cs index 4ae2a6a..62c0ecc 100644 --- a/tests/DispatchR.UnitTest/Fixtures/SendRequest/SecondPipelineBehavior.cs +++ b/tests/DispatchR.TestCommon/Fixtures/SendRequest/ValueTask/PingValueTaskSecondPipelineBehavior.cs @@ -1,13 +1,15 @@ using DispatchR.Requests.Send; -namespace DispatchR.UnitTest.Fixtures.SendRequest +namespace DispatchR.TestCommon.Fixtures.SendRequest.ValueTask { - public class SecondPipelineBehavior() : IPipelineBehavior> + public class PingValueTaskSecondPipelineBehavior() : IPipelineBehavior> { + public static DateTime ExecutionTime { get; private set; } public required IRequestHandler> NextPipeline { get; set; } public ValueTask Handle(PingValueTask request, CancellationToken cancellationToken) { + ExecutionTime = DateTime.Now; return NextPipeline.Handle(request, cancellationToken); } } diff --git a/tests/DispatchR.UnitTest/Fixtures/StreamRequest/CounterPipelineStreamHandler.cs b/tests/DispatchR.TestCommon/Fixtures/StreamRequest/CounterPipelineStreamHandler.cs similarity index 91% rename from tests/DispatchR.UnitTest/Fixtures/StreamRequest/CounterPipelineStreamHandler.cs rename to tests/DispatchR.TestCommon/Fixtures/StreamRequest/CounterPipelineStreamHandler.cs index 4c78c45..4f3b980 100644 --- a/tests/DispatchR.UnitTest/Fixtures/StreamRequest/CounterPipelineStreamHandler.cs +++ b/tests/DispatchR.TestCommon/Fixtures/StreamRequest/CounterPipelineStreamHandler.cs @@ -1,7 +1,7 @@ using System.Runtime.CompilerServices; using DispatchR.Requests.Stream; -namespace DispatchR.UnitTest.Fixtures.StreamRequest; +namespace DispatchR.TestCommon.Fixtures.StreamRequest; public class CounterPipelineStreamHandler : IStreamPipelineBehavior { diff --git a/tests/DispatchR.UnitTest/Fixtures/StreamRequest/CounterStreamHandler.cs b/tests/DispatchR.TestCommon/Fixtures/StreamRequest/CounterStreamHandler.cs similarity index 87% rename from tests/DispatchR.UnitTest/Fixtures/StreamRequest/CounterStreamHandler.cs rename to tests/DispatchR.TestCommon/Fixtures/StreamRequest/CounterStreamHandler.cs index cd26676..7da1265 100644 --- a/tests/DispatchR.UnitTest/Fixtures/StreamRequest/CounterStreamHandler.cs +++ b/tests/DispatchR.TestCommon/Fixtures/StreamRequest/CounterStreamHandler.cs @@ -1,7 +1,7 @@ using System.Runtime.CompilerServices; using DispatchR.Requests.Stream; -namespace DispatchR.UnitTest.Fixtures.StreamRequest; +namespace DispatchR.TestCommon.Fixtures.StreamRequest; public class CounterStreamHandler() : IStreamRequestHandler { diff --git a/tests/DispatchR.UnitTest/Fixtures/StreamRequest/CounterStreamRequest.cs b/tests/DispatchR.TestCommon/Fixtures/StreamRequest/CounterStreamRequest.cs similarity index 68% rename from tests/DispatchR.UnitTest/Fixtures/StreamRequest/CounterStreamRequest.cs rename to tests/DispatchR.TestCommon/Fixtures/StreamRequest/CounterStreamRequest.cs index abab345..7111fac 100644 --- a/tests/DispatchR.UnitTest/Fixtures/StreamRequest/CounterStreamRequest.cs +++ b/tests/DispatchR.TestCommon/Fixtures/StreamRequest/CounterStreamRequest.cs @@ -1,5 +1,5 @@ using DispatchR.Requests.Stream; -namespace DispatchR.UnitTest.Fixtures.StreamRequest; +namespace DispatchR.TestCommon.Fixtures.StreamRequest; public class CounterStreamRequest : IStreamRequest { } \ No newline at end of file diff --git a/tests/DispatchR.UnitTest/Fixtures/StreamRequest/GenericPipelineBehavior.cs b/tests/DispatchR.TestCommon/Fixtures/StreamRequest/GenericPipelineBehavior.cs similarity index 91% rename from tests/DispatchR.UnitTest/Fixtures/StreamRequest/GenericPipelineBehavior.cs rename to tests/DispatchR.TestCommon/Fixtures/StreamRequest/GenericPipelineBehavior.cs index 5223bb9..1e07078 100644 --- a/tests/DispatchR.UnitTest/Fixtures/StreamRequest/GenericPipelineBehavior.cs +++ b/tests/DispatchR.TestCommon/Fixtures/StreamRequest/GenericPipelineBehavior.cs @@ -1,6 +1,6 @@ using DispatchR.Requests.Stream; -namespace DispatchR.UnitTest.Fixtures.StreamRequest; +namespace DispatchR.TestCommon.Fixtures.StreamRequest; public class GenericPipelineBehavior() : IStreamPipelineBehavior diff --git a/tests/DispatchR.UnitTest/AddDispachRConfigurationTests.cs b/tests/DispatchR.UnitTest/AddDispachRConfigurationTests.cs new file mode 100644 index 0000000..49c1879 --- /dev/null +++ b/tests/DispatchR.UnitTest/AddDispachRConfigurationTests.cs @@ -0,0 +1,113 @@ +using DispatchR.Extensions; +using DispatchR.Requests; +using DispatchR.Requests.Send; +using DispatchR.Requests.Stream; +using DispatchR.TestCommon.Fixtures; +using DispatchR.TestCommon.Fixtures.SendRequest.ReusedInScopedLifetime; +using DispatchR.TestCommon.Fixtures.SendRequest.ValueTask; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Moq; + +namespace DispatchR.UnitTest; + +public class AddDispachRConfigurationTests +{ + [Fact] + public void Send_ReturnsExpectedResponse_IncludeAllHandlers() + { + // Arrange + var services = new ServiceCollection(); + + // Act + services.AddDispatchR(cfg => + { + cfg.Assemblies.Add(typeof(Fixture).Assembly); + cfg.RegisterPipelines = true; + cfg.RegisterNotifications = false; + cfg.IncludeHandlers = null; // <== this line + cfg.ExcludeHandlers = null; // <== this line + }); + + // Assert + var countOfAllSimpleHandlers = services + .Count(p => + p.IsKeyedService && + p.KeyedImplementationType!.GetInterface(typeof(IStreamRequestHandler<,>).Name, true) is null); + Assert.True(countOfAllSimpleHandlers > 1); + } + + [Fact] + public void Send_ReturnsExpectedResponse_IncludeSingleHandler() + { + // Arrange + var services = new ServiceCollection(); + + // Act + services.AddDispatchR(cfg => + { + cfg.Assemblies.Add(typeof(Fixture).Assembly); + cfg.RegisterPipelines = true; + cfg.RegisterNotifications = false; + cfg.IncludeHandlers = [Fixture.AnyHandlerRequestWithoutPipeline.GetType()]; // <== this line + }); + + // Assert + var countOfAllSimpleHandlers = services + .Count(p => + p.IsKeyedService && + p.KeyedImplementationType!.GetInterface(typeof(IStreamRequestHandler<,>).Name, true) is null); + Assert.Equal(1, countOfAllSimpleHandlers); + } + + [Fact] + public void Send_ReturnsExpectedResponse_ExcludeSingleHandler() + { + // Arrange + var services = new ServiceCollection(); + + // Act + services.AddDispatchR(cfg => + { + cfg.Assemblies.Add(typeof(Fixture).Assembly); + cfg.RegisterPipelines = true; + cfg.RegisterNotifications = false; + cfg.ExcludeHandlers = [Fixture.AnyHandlerRequestWithoutPipeline.GetType()]; // <== this line + }); + + // Assert + var countOfAllSimpleHandlers = services + .Count(p => + p.IsKeyedService && + p.KeyedImplementationType == Fixture.AnyHandlerRequestWithoutPipeline.GetType()); + Assert.Equal(0, countOfAllSimpleHandlers); + } + + [Fact] + public async Task Send_UsesPipelineBehaviorsInCorrectOrder_RequestWithMultiplePipelines() + { + // Arrange + var services = new ServiceCollection(); + services.AddDispatchR(cfg => + { + cfg.Assemblies.Add(typeof(Fixture).Assembly); + cfg.RegisterPipelines = true; + cfg.RegisterNotifications = false; + cfg.PipelineOrder = + [ + typeof(PingValueTaskFirstPipelineBehavior), + typeof(PingValueTaskSecondPipelineBehavior), + ]; + cfg.IncludeHandlers = [typeof(PingValueTaskHandler)]; + }); + var serviceProvider = services.BuildServiceProvider(); + var mediator = serviceProvider.GetRequiredService(); + + // Act + var result = await mediator.Send(new PingValueTask(), CancellationToken.None); + + // Assert + Assert.Equal(1, result); + Assert.True(PingValueTaskFirstPipelineBehavior.ExecutionTime < PingValueTaskSecondPipelineBehavior.ExecutionTime); + } +} \ No newline at end of file diff --git a/tests/DispatchR.UnitTest/DispatchR.UnitTest.csproj b/tests/DispatchR.UnitTest/DispatchR.UnitTest.csproj index 066919a..006b9ff 100644 --- a/tests/DispatchR.UnitTest/DispatchR.UnitTest.csproj +++ b/tests/DispatchR.UnitTest/DispatchR.UnitTest.csproj @@ -11,10 +11,11 @@ + - all - runtime; build; native; contentfiles; analyzers; buildtransitive + all + runtime; build; native; contentfiles; analyzers; buildtransitive @@ -23,7 +24,7 @@ - + diff --git a/tests/DispatchR.UnitTest/Fixtures/Fixture.cs b/tests/DispatchR.UnitTest/Fixtures/Fixture.cs deleted file mode 100644 index 7d8915b..0000000 --- a/tests/DispatchR.UnitTest/Fixtures/Fixture.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace DispatchR.UnitTest.Fixtures; - -public class Fixture -{ - -} \ No newline at end of file diff --git a/tests/DispatchR.UnitTest/RequestHandlerTests.cs b/tests/DispatchR.UnitTest/RequestHandlerTests.cs index db169e5..a2ddf64 100644 --- a/tests/DispatchR.UnitTest/RequestHandlerTests.cs +++ b/tests/DispatchR.UnitTest/RequestHandlerTests.cs @@ -1,7 +1,13 @@ using DispatchR.Configuration; +using DispatchR.Exceptions; using DispatchR.Extensions; using DispatchR.Requests; -using DispatchR.UnitTest.Fixtures.SendRequest; +using DispatchR.TestCommon.Fixtures; +using DispatchR.TestCommon.Fixtures.SendRequest; +using DispatchR.TestCommon.Fixtures.SendRequest.ReusedInScopedLifetime; +using DispatchR.TestCommon.Fixtures.SendRequest.Sync; +using DispatchR.TestCommon.Fixtures.SendRequest.Task; +using DispatchR.TestCommon.Fixtures.SendRequest.ValueTask; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection; @@ -12,24 +18,58 @@ public class RequestHandlerTests [Fact] public void Send_ReturnsExpectedResponse_SyncRequestHandler() { + // Arrange + var services = new ServiceCollection(); + services.AddDispatchR(cfg => + { + cfg.Assemblies.Add(typeof(Fixture).Assembly); + cfg.RegisterPipelines = false; + cfg.RegisterNotifications = false; + cfg.IncludeHandlers = [typeof(PingHandler)]; + }); + var serviceProvider = services.BuildServiceProvider(); + var mediator = serviceProvider.GetRequiredService(); + + // Act + var result = mediator.Send(new Ping(), CancellationToken.None); + + // Assert + Assert.Equal(1, result); } [Fact] - public void Send_ReturnsExpectedResponse_AsyncRequestHandlerWithTask() + public async Task Send_ReturnsExpectedResponse_AsyncRequestHandlerWithTask() { + // Arrange + var services = new ServiceCollection(); + services.AddDispatchR(cfg => + { + cfg.Assemblies.Add(typeof(Fixture).Assembly); + cfg.RegisterPipelines = false; + cfg.RegisterNotifications = false; + cfg.IncludeHandlers = [typeof(PingTaskHandler)]; + }); + var serviceProvider = services.BuildServiceProvider(); + var mediator = serviceProvider.GetRequiredService(); + + // Act + var result = await mediator.Send(new PingTask(), CancellationToken.None); + + // Assert + Assert.Equal(1, result); } [Fact] public async Task Send_ReturnsExpectedResponse_AsyncRequestHandlerWithValueTask() { // Arrange - var services = new Microsoft.Extensions.DependencyInjection.ServiceCollection(); + var services = new ServiceCollection(); services.AddDispatchR(cfg => { - cfg.Assemblies.Add(this.GetType().Assembly); + cfg.Assemblies.Add(typeof(Fixture).Assembly); cfg.RegisterPipelines = false; cfg.RegisterNotifications = false; - cfg.IncludeHandlers = [typeof(PingValueTask)]; + cfg.IncludeHandlers = [typeof(PingValueTaskHandler)]; }); var serviceProvider = services.BuildServiceProvider(); var mediator = serviceProvider.GetRequiredService(); @@ -37,21 +77,81 @@ public async Task Send_ReturnsExpectedResponse_AsyncRequestHandlerWithValueTask( // Act var result = await mediator.Send(new PingValueTask(), CancellationToken.None); + // Assert Assert.Equal(1, result); } [Fact] - public void Send_UsesPipelineBehaviorsInCorrectOrder_RequestWithMultiplePipelines() + public async Task Send_UsesPipelineBehaviors_RequestWithSinglePipelines() { + // Arrange + var services = new ServiceCollection(); + services.AddDispatchR(cfg => + { + cfg.Assemblies.Add(typeof(Fixture).Assembly); + cfg.RegisterPipelines = true; + cfg.RegisterNotifications = false; + cfg.IncludeHandlers = [typeof(PingValueTaskHandler)]; + }); + var serviceProvider = services.BuildServiceProvider(); + var mediator = serviceProvider.GetRequiredService(); + + // Act + var result = await mediator.Send(new PingValueTask(), CancellationToken.None); + + // Assert + Assert.Equal(1, result); } [Fact] public void Send_ThrowsException_WhenNoHandlerIsRegistered() { + // Arrange + var services = new ServiceCollection(); + services.AddDispatchR(cfg => + { + cfg.Assemblies.Add(typeof(Fixture).Assembly); + cfg.RegisterPipelines = true; + cfg.RegisterNotifications = false; + cfg.IncludeHandlers = [typeof(RequestWithoutHandler)]; + }); + + var serviceProvider = services.BuildServiceProvider(); + var mediator = serviceProvider.GetRequiredService(); + + // Act + void Action() => mediator.Send(new RequestWithoutHandler(), CancellationToken.None); + + // Assert + var exception = Assert.Throws>(Action); + Assert.Equal(""" + Handler for request of type 'RequestWithoutHandler' returning 'Int32' was not found. + Make sure you have registered a handler that implements IRequestHandler in the DI container. + """, exception.Message); } [Fact] public void Send_UsesCachedHandler_InstanceReusedInScopedLifetime() { + // Arrange + var services = new ServiceCollection(); + services.AddDispatchR(cfg => + { + cfg.Assemblies.Add(typeof(Fixture).Assembly); + cfg.RegisterPipelines = true; + cfg.RegisterNotifications = false; + cfg.IncludeHandlers = [typeof(RequestReusedInScopedLifetimeHandler)]; + }); + var serviceProvider = services.BuildServiceProvider(); + var scope = serviceProvider.CreateScope(); + var mediator = scope.ServiceProvider.GetRequiredService(); + + // Act + var first = mediator.Send(new RequestReusedInScopedLifetime(), CancellationToken.None); + var second = mediator.Send(new RequestReusedInScopedLifetime(), CancellationToken.None); + var third = mediator.Send(new RequestReusedInScopedLifetime(), CancellationToken.None); + + // Assert + Assert.Equal(first, 1); } } \ No newline at end of file From 0110122eaf2ba09da2be1a4e0b455c9d5e8da567 Mon Sep 17 00:00:00 2001 From: hasanxdev Date: Sun, 3 Aug 2025 00:57:22 +0330 Subject: [PATCH 03/23] =?UTF-8?q?=E2=9C=A8=20Add=20tests=20for=20handler?= =?UTF-8?q?=20inclusion/exclusion=20and=20exceptions=20for=20empty=20handl?= =?UTF-8?q?er=20arrays?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ludeHandlersCannotBeArrayEmptyException.cs | 5 ++ ...ludeHandlersCannotBeArrayEmptyException.cs | 5 ++ .../Extensions/ServiceCollectionExtensions.cs | 11 +++ .../AddDispachRConfigurationTests.cs | 69 +++++++++++++++++-- 4 files changed, 85 insertions(+), 5 deletions(-) create mode 100644 src/DispatchR/Exceptions/ExcludeHandlersCannotBeArrayEmptyException.cs create mode 100644 src/DispatchR/Exceptions/IncludeHandlersCannotBeArrayEmptyException.cs diff --git a/src/DispatchR/Exceptions/ExcludeHandlersCannotBeArrayEmptyException.cs b/src/DispatchR/Exceptions/ExcludeHandlersCannotBeArrayEmptyException.cs new file mode 100644 index 0000000..17aa435 --- /dev/null +++ b/src/DispatchR/Exceptions/ExcludeHandlersCannotBeArrayEmptyException.cs @@ -0,0 +1,5 @@ +namespace DispatchR.Exceptions; + +public class ExcludeHandlersCannotBeArrayEmptyException() : Exception("Exclude handlers cannot be array empty.") +{ +} \ No newline at end of file diff --git a/src/DispatchR/Exceptions/IncludeHandlersCannotBeArrayEmptyException.cs b/src/DispatchR/Exceptions/IncludeHandlersCannotBeArrayEmptyException.cs new file mode 100644 index 0000000..7a21310 --- /dev/null +++ b/src/DispatchR/Exceptions/IncludeHandlersCannotBeArrayEmptyException.cs @@ -0,0 +1,5 @@ +namespace DispatchR.Exceptions; + +public class IncludeHandlersCannotBeArrayEmptyException() : Exception("Include handlers cannot be array empty.") +{ +} \ No newline at end of file diff --git a/src/DispatchR/Extensions/ServiceCollectionExtensions.cs b/src/DispatchR/Extensions/ServiceCollectionExtensions.cs index 2fa6425..6f38de2 100644 --- a/src/DispatchR/Extensions/ServiceCollectionExtensions.cs +++ b/src/DispatchR/Extensions/ServiceCollectionExtensions.cs @@ -5,6 +5,7 @@ using DispatchR.Requests.Stream; using Microsoft.Extensions.DependencyInjection; using System.Reflection; +using DispatchR.Exceptions; namespace DispatchR.Extensions; @@ -15,6 +16,16 @@ public static IServiceCollection AddDispatchR(this IServiceCollection services, var config = new ConfigurationOptions(); configuration(config); + if (config is {IncludeHandlers.Count:0}) + { + throw new IncludeHandlersCannotBeArrayEmptyException(); + } + + if (config is {ExcludeHandlers.Count:0}) + { + throw new ExcludeHandlersCannotBeArrayEmptyException(); + } + return services.AddDispatchR(config); } diff --git a/tests/DispatchR.UnitTest/AddDispachRConfigurationTests.cs b/tests/DispatchR.UnitTest/AddDispachRConfigurationTests.cs index 49c1879..e3eb317 100644 --- a/tests/DispatchR.UnitTest/AddDispachRConfigurationTests.cs +++ b/tests/DispatchR.UnitTest/AddDispachRConfigurationTests.cs @@ -1,20 +1,17 @@ +using DispatchR.Exceptions; using DispatchR.Extensions; using DispatchR.Requests; -using DispatchR.Requests.Send; using DispatchR.Requests.Stream; using DispatchR.TestCommon.Fixtures; -using DispatchR.TestCommon.Fixtures.SendRequest.ReusedInScopedLifetime; using DispatchR.TestCommon.Fixtures.SendRequest.ValueTask; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Moq; namespace DispatchR.UnitTest; public class AddDispachRConfigurationTests { [Fact] - public void Send_ReturnsExpectedResponse_IncludeAllHandlers() + public void Send_ReturnsExpectedResponse_DefaultHandlers() { // Arrange var services = new ServiceCollection(); @@ -83,6 +80,68 @@ public void Send_ReturnsExpectedResponse_ExcludeSingleHandler() Assert.Equal(0, countOfAllSimpleHandlers); } + [Fact] + public void Send_ReturnsExpectedResponse_IncludeAndExcludeOneHandlers() + { + // Arrange + var services = new ServiceCollection(); + + // Act + services.AddDispatchR(cfg => + { + cfg.Assemblies.Add(typeof(Fixture).Assembly); + cfg.RegisterPipelines = true; + cfg.RegisterNotifications = false; + cfg.IncludeHandlers = [Fixture.AnyHandlerRequestWithoutPipeline.GetType()]; + cfg.ExcludeHandlers = [Fixture.AnyHandlerRequestWithoutPipeline.GetType()]; + }); + + // Assert + var countOfAllSimpleHandlers = services + .Count(p => + p.IsKeyedService && + p.KeyedImplementationType == Fixture.AnyHandlerRequestWithoutPipeline.GetType()); + Assert.Equal(0, countOfAllSimpleHandlers); + } + + [Fact] + public void Send_ThrowsException_WhenIncludeHandlersBeEmpty() + { + // Arrange + var services = new ServiceCollection(); + + // Act + var act = () => services.AddDispatchR(cfg => + { + cfg.Assemblies.Add(typeof(Fixture).Assembly); + cfg.RegisterPipelines = true; + cfg.RegisterNotifications = false; + cfg.IncludeHandlers = []; + }); + + // Assert + Assert.Throws(act); + } + + [Fact] + public void Send_ThrowsException_WhenExcludeHandlersBeEmpty() + { + // Arrange + var services = new ServiceCollection(); + + // Act + var act = () => services.AddDispatchR(cfg => + { + cfg.Assemblies.Add(typeof(Fixture).Assembly); + cfg.RegisterPipelines = true; + cfg.RegisterNotifications = false; + cfg.ExcludeHandlers = []; + }); + + // Assert + Assert.Throws(act); + } + [Fact] public async Task Send_UsesPipelineBehaviorsInCorrectOrder_RequestWithMultiplePipelines() { From 929f47e5cd03c43d9dff6aa03b88bd55d08b6ac5 Mon Sep 17 00:00:00 2001 From: hasanxdev Date: Sun, 3 Aug 2025 01:36:02 +0330 Subject: [PATCH 04/23] =?UTF-8?q?=E2=9C=A8=20Add=20unit=20test=20for=20gen?= =?UTF-8?q?eric=20pipeline=20registration=20in=20DispatchR=20configuration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AddDispachRConfigurationTests.cs | 25 +++++++++++++++++++ .../DispatchR.UnitTest/RequestHandlerTests.cs | 4 +-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/tests/DispatchR.UnitTest/AddDispachRConfigurationTests.cs b/tests/DispatchR.UnitTest/AddDispachRConfigurationTests.cs index e3eb317..6009e19 100644 --- a/tests/DispatchR.UnitTest/AddDispachRConfigurationTests.cs +++ b/tests/DispatchR.UnitTest/AddDispachRConfigurationTests.cs @@ -3,6 +3,7 @@ using DispatchR.Requests; using DispatchR.Requests.Stream; using DispatchR.TestCommon.Fixtures; +using DispatchR.TestCommon.Fixtures.SendRequest; using DispatchR.TestCommon.Fixtures.SendRequest.ValueTask; using Microsoft.Extensions.DependencyInjection; @@ -169,4 +170,28 @@ public async Task Send_UsesPipelineBehaviorsInCorrectOrder_RequestWithMultiplePi Assert.Equal(1, result); Assert.True(PingValueTaskFirstPipelineBehavior.ExecutionTime < PingValueTaskSecondPipelineBehavior.ExecutionTime); } + + [Fact] + public void Send_RegisterGenericPipeline_IncludeGenericPipeline() + { + // Arrange + var services = new ServiceCollection(); + + // Act + services.AddDispatchR(cfg => + { + cfg.Assemblies.Add(typeof(Fixture).Assembly); + cfg.RegisterPipelines = true; + cfg.RegisterNotifications = false; + cfg.IncludeHandlers = [Fixture.AnyHandlerRequestWithPipeline.GetType()]; + }); + + // Assert + var countOfAllSimpleHandlers = services + .Count(p => + p.IsKeyedService && + p.KeyedImplementationType!.IsGenericType && + p.KeyedImplementationType?.GetGenericTypeDefinition() == typeof(GenericPipelineBehavior<,>).GetGenericTypeDefinition()); + Assert.Equal(1, countOfAllSimpleHandlers); + } } \ No newline at end of file diff --git a/tests/DispatchR.UnitTest/RequestHandlerTests.cs b/tests/DispatchR.UnitTest/RequestHandlerTests.cs index a2ddf64..0bca149 100644 --- a/tests/DispatchR.UnitTest/RequestHandlerTests.cs +++ b/tests/DispatchR.UnitTest/RequestHandlerTests.cs @@ -82,7 +82,7 @@ public async Task Send_ReturnsExpectedResponse_AsyncRequestHandlerWithValueTask( } [Fact] - public async Task Send_UsesPipelineBehaviors_RequestWithSinglePipelines() + public async Task Send_UsesPipelineBehaviors_RequestWithPipelines() { // Arrange var services = new ServiceCollection(); @@ -152,6 +152,6 @@ public void Send_UsesCachedHandler_InstanceReusedInScopedLifetime() var third = mediator.Send(new RequestReusedInScopedLifetime(), CancellationToken.None); // Assert - Assert.Equal(first, 1); + Assert.Equal(3, first + second + third); } } \ No newline at end of file From 1f10490ac003bd2b9468c026c260fc89219db53d Mon Sep 17 00:00:00 2001 From: hasanxdev Date: Sun, 3 Aug 2025 02:00:18 +0330 Subject: [PATCH 05/23] =?UTF-8?q?=E2=9C=A8=20Add=20StreamRequestHandler=20?= =?UTF-8?q?tests=20and=20update=20Fixture=20with=20stream=20request=20hand?= =?UTF-8?q?lers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RequestHandlerTests.cs | 10 --- .../StreamRequestHandlerTests.cs | 56 +++++++++++++++ .../DispatchR.TestCommon/Fixtures/Fixture.cs | 4 ++ .../StreamRequestHandlerTests.cs | 69 +++++++++++++++++++ 4 files changed, 129 insertions(+), 10 deletions(-) create mode 100644 tests/DispatchR.IntegrationTest/StreamRequestHandlerTests.cs create mode 100644 tests/DispatchR.UnitTest/StreamRequestHandlerTests.cs diff --git a/tests/DispatchR.IntegrationTest/RequestHandlerTests.cs b/tests/DispatchR.IntegrationTest/RequestHandlerTests.cs index 4080b85..746de8d 100644 --- a/tests/DispatchR.IntegrationTest/RequestHandlerTests.cs +++ b/tests/DispatchR.IntegrationTest/RequestHandlerTests.cs @@ -57,14 +57,4 @@ public async Task Send_UsesPipelineBehaviors_RequestWithSinglePipelines() spyPipelineOneMock.Verify(p => p.Handle(It.IsAny(), It.IsAny()), Times.Exactly(1)); spyPipelineTwoMock.Verify(p => p.Handle(It.IsAny(), It.IsAny()), Times.Exactly(1)); } - - [Fact] - public void Send_ThrowsException_WhenNoHandlerIsRegistered() - { - } - - [Fact] - public void Send_UsesCachedHandler_InstanceReusedInScopedLifetime() - { - } } \ No newline at end of file diff --git a/tests/DispatchR.IntegrationTest/StreamRequestHandlerTests.cs b/tests/DispatchR.IntegrationTest/StreamRequestHandlerTests.cs new file mode 100644 index 0000000..d14503e --- /dev/null +++ b/tests/DispatchR.IntegrationTest/StreamRequestHandlerTests.cs @@ -0,0 +1,56 @@ +using DispatchR.Extensions; +using DispatchR.Requests; +using DispatchR.Requests.Send; +using DispatchR.Requests.Stream; +using DispatchR.TestCommon.Fixtures; +using DispatchR.TestCommon.Fixtures.SendRequest.Sync; +using DispatchR.TestCommon.Fixtures.SendRequest.Task; +using DispatchR.TestCommon.Fixtures.SendRequest.ValueTask; +using DispatchR.TestCommon.Fixtures.StreamRequest; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Moq; + +namespace DispatchR.IntegrationTest; + +public class StreamRequestHandlerTests +{ + [Fact] + public async Task Send_UsesPipelineBehaviors_RequestWithSinglePipelines() + { + // Arrange + var services = new ServiceCollection(); + services.AddDispatchR(cfg => + { + cfg.Assemblies.Add(typeof(Fixture).Assembly); + cfg.RegisterPipelines = true; + cfg.RegisterNotifications = false; + cfg.IncludeHandlers = [Fixture.AnyStreamHandler.GetType()]; + }); + + var firstPipeline = services + .Single(p => p.IsKeyedService && p.KeyedImplementationType == typeof(CounterPipelineStreamHandler)); + + var spyPipelineOneMock = new Mock>(); + spyPipelineOneMock.Setup(p => p.Handle(It.IsAny(), It.IsAny())) + .Returns((CounterStreamRequest req, CancellationToken ct) => Fixture.AnyStreamHandler.Handle(req, ct)); + + services.RemoveAllKeyed(typeof(IRequestHandler), firstPipeline.ServiceKey); + + services.AddKeyedScoped(typeof(IRequestHandler), firstPipeline.ServiceKey!, (_,__) => spyPipelineOneMock.Object); + + var serviceProvider = services.BuildServiceProvider(); + var mediator = serviceProvider.GetRequiredService(); + + // Act + int counter = 0; + await foreach (var response in mediator.CreateStream(Fixture.AnyStreamRequest, CancellationToken.None)) + { + counter++; + } + + // Assert + Assert.Equal(1, counter); + spyPipelineOneMock.Verify(p => p.Handle(It.IsAny(), It.IsAny()), Times.Exactly(1)); + } +} \ No newline at end of file diff --git a/tests/DispatchR.TestCommon/Fixtures/Fixture.cs b/tests/DispatchR.TestCommon/Fixtures/Fixture.cs index a4af0dd..6331b01 100644 --- a/tests/DispatchR.TestCommon/Fixtures/Fixture.cs +++ b/tests/DispatchR.TestCommon/Fixtures/Fixture.cs @@ -1,5 +1,6 @@ using DispatchR.TestCommon.Fixtures.SendRequest.Task; using DispatchR.TestCommon.Fixtures.SendRequest.ValueTask; +using DispatchR.TestCommon.Fixtures.StreamRequest; namespace DispatchR.TestCommon.Fixtures; @@ -10,4 +11,7 @@ public class Fixture public static PingValueTask AnyRequestWithPipeline => new(); public static PingValueTaskHandler AnyHandlerRequestWithPipeline => new(); + + public static CounterStreamRequest AnyStreamRequest => new(); + public static CounterStreamHandler AnyStreamHandler => new(); } \ No newline at end of file diff --git a/tests/DispatchR.UnitTest/StreamRequestHandlerTests.cs b/tests/DispatchR.UnitTest/StreamRequestHandlerTests.cs new file mode 100644 index 0000000..8829949 --- /dev/null +++ b/tests/DispatchR.UnitTest/StreamRequestHandlerTests.cs @@ -0,0 +1,69 @@ +using DispatchR.Configuration; +using DispatchR.Exceptions; +using DispatchR.Extensions; +using DispatchR.Requests; +using DispatchR.TestCommon.Fixtures; +using DispatchR.TestCommon.Fixtures.SendRequest; +using DispatchR.TestCommon.Fixtures.SendRequest.ReusedInScopedLifetime; +using DispatchR.TestCommon.Fixtures.SendRequest.Sync; +using DispatchR.TestCommon.Fixtures.SendRequest.Task; +using DispatchR.TestCommon.Fixtures.SendRequest.ValueTask; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; + +namespace DispatchR.UnitTest; + +public class StreamRequestHandlerTests +{ + [Fact] + public async Task CreaseStream_StreamHandlerReturnsExpectedResponse_WhenSingleHandlerIsIncluded() + { + // Arrange + var services = new ServiceCollection(); + services.AddDispatchR(cfg => + { + cfg.Assemblies.Add(typeof(Fixture).Assembly); + cfg.RegisterPipelines = false; + cfg.RegisterNotifications = false; + cfg.IncludeHandlers = [Fixture.AnyStreamHandler.GetType()]; + }); + var serviceProvider = services.BuildServiceProvider(); + var mediator = serviceProvider.GetRequiredService(); + + // Act + int counter = 0; + await foreach (var response in mediator.CreateStream(Fixture.AnyStreamRequest, CancellationToken.None)) + { + counter++; + } + + // Assert + Assert.Equal(1, counter); + } + + [Fact] + public async Task Send_UsesPipelineBehaviors_RequestWithPipelines() + { + // Arrange + var services = new ServiceCollection(); + services.AddDispatchR(cfg => + { + cfg.Assemblies.Add(typeof(Fixture).Assembly); + cfg.RegisterPipelines = true; + cfg.RegisterNotifications = false; + cfg.IncludeHandlers = [Fixture.AnyStreamHandler.GetType()]; + }); + var serviceProvider = services.BuildServiceProvider(); + var mediator = serviceProvider.GetRequiredService(); + + // Act + int counter = 0; + await foreach (var response in mediator.CreateStream(Fixture.AnyStreamRequest, CancellationToken.None)) + { + counter++; + } + + // Assert + Assert.Equal(1, counter); + } +} \ No newline at end of file From 6ded9745eb8c7675da096aff8829f007e9c82047 Mon Sep 17 00:00:00 2001 From: hasanxdev Date: Sun, 3 Aug 2025 02:22:47 +0330 Subject: [PATCH 06/23] =?UTF-8?q?=E2=9C=A8=20Rename=20tests=20for=20clarit?= =?UTF-8?q?y=20and=20add=20notification=20registration=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NotificationTests.cs | 51 +++++++++++++++++++ .../StreamRequestHandlerTests.cs | 2 +- ...s.cs => AddDispatchRConfigurationTests.cs} | 45 ++++++++++++---- 3 files changed, 88 insertions(+), 10 deletions(-) create mode 100644 tests/DispatchR.IntegrationTest/NotificationTests.cs rename tests/DispatchR.UnitTest/{AddDispachRConfigurationTests.cs => AddDispatchRConfigurationTests.cs} (78%) diff --git a/tests/DispatchR.IntegrationTest/NotificationTests.cs b/tests/DispatchR.IntegrationTest/NotificationTests.cs new file mode 100644 index 0000000..477a342 --- /dev/null +++ b/tests/DispatchR.IntegrationTest/NotificationTests.cs @@ -0,0 +1,51 @@ +using DispatchR.Extensions; +using DispatchR.Requests; +using DispatchR.Requests.Notification; +using DispatchR.Requests.Send; +using DispatchR.TestCommon.Fixtures; +using DispatchR.TestCommon.Fixtures.Notification; +using DispatchR.TestCommon.Fixtures.SendRequest.ValueTask; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Moq; + +namespace DispatchR.IntegrationTest; + +public class NotificationTests +{ + [Fact] + public async Task Publish_CallsAllHandlers_WhenMultipleHandlersAreRegistered() + { + // Arrange + var services = new ServiceCollection(); + services.AddDispatchR(cfg => + { + cfg.Assemblies.Add(typeof(Fixture).Assembly); + cfg.RegisterPipelines = false; + cfg.RegisterNotifications = true; + }); + + var spyPipelineOneMock = new Mock>(); + var spyPipelineTwoMock = new Mock>(); + var spyPipelineThreeMock = new Mock>(); + + spyPipelineOneMock.Setup(p => p.Handle(It.IsAny(), It.IsAny())); + spyPipelineTwoMock.Setup(p => p.Handle(It.IsAny(), It.IsAny())); + spyPipelineThreeMock.Setup(p => p.Handle(It.IsAny(), It.IsAny())); + + services.AddScoped>(sp => spyPipelineOneMock.Object); + services.AddScoped>(sp => spyPipelineTwoMock.Object); + services.AddScoped>(sp => spyPipelineThreeMock.Object); + + var serviceProvider = services.BuildServiceProvider(); + var mediator = serviceProvider.GetRequiredService(); + + // Act + await mediator.Publish(new MultiHandlersNotification(Guid.Empty), CancellationToken.None); + + // Assert + spyPipelineOneMock.Verify(p => p.Handle(It.IsAny(), It.IsAny()), Times.Exactly(1)); + spyPipelineTwoMock.Verify(p => p.Handle(It.IsAny(), It.IsAny()), Times.Exactly(1)); + spyPipelineThreeMock.Verify(p => p.Handle(It.IsAny(), It.IsAny()), Times.Exactly(1)); + } +} \ No newline at end of file diff --git a/tests/DispatchR.IntegrationTest/StreamRequestHandlerTests.cs b/tests/DispatchR.IntegrationTest/StreamRequestHandlerTests.cs index d14503e..181d239 100644 --- a/tests/DispatchR.IntegrationTest/StreamRequestHandlerTests.cs +++ b/tests/DispatchR.IntegrationTest/StreamRequestHandlerTests.cs @@ -16,7 +16,7 @@ namespace DispatchR.IntegrationTest; public class StreamRequestHandlerTests { [Fact] - public async Task Send_UsesPipelineBehaviors_RequestWithSinglePipelines() + public async Task CreateStream_UsesPipelineBehavior_WithSinglePipeline() { // Arrange var services = new ServiceCollection(); diff --git a/tests/DispatchR.UnitTest/AddDispachRConfigurationTests.cs b/tests/DispatchR.UnitTest/AddDispatchRConfigurationTests.cs similarity index 78% rename from tests/DispatchR.UnitTest/AddDispachRConfigurationTests.cs rename to tests/DispatchR.UnitTest/AddDispatchRConfigurationTests.cs index 6009e19..b7fa504 100644 --- a/tests/DispatchR.UnitTest/AddDispachRConfigurationTests.cs +++ b/tests/DispatchR.UnitTest/AddDispatchRConfigurationTests.cs @@ -3,16 +3,17 @@ using DispatchR.Requests; using DispatchR.Requests.Stream; using DispatchR.TestCommon.Fixtures; +using DispatchR.TestCommon.Fixtures.Notification; using DispatchR.TestCommon.Fixtures.SendRequest; using DispatchR.TestCommon.Fixtures.SendRequest.ValueTask; using Microsoft.Extensions.DependencyInjection; namespace DispatchR.UnitTest; -public class AddDispachRConfigurationTests +public class AddDispatchRConfigurationTests { [Fact] - public void Send_ReturnsExpectedResponse_DefaultHandlers() + public void AddDispatchR_ReturnsExpectedResponse_DefaultHandlers() { // Arrange var services = new ServiceCollection(); @@ -36,7 +37,7 @@ public void Send_ReturnsExpectedResponse_DefaultHandlers() } [Fact] - public void Send_ReturnsExpectedResponse_IncludeSingleHandler() + public void AddDispatchR_ReturnsExpectedResponse_IncludeSingleHandler() { // Arrange var services = new ServiceCollection(); @@ -59,7 +60,7 @@ public void Send_ReturnsExpectedResponse_IncludeSingleHandler() } [Fact] - public void Send_ReturnsExpectedResponse_ExcludeSingleHandler() + public void AddDispatchR_ReturnsExpectedResponse_ExcludeSingleHandler() { // Arrange var services = new ServiceCollection(); @@ -82,7 +83,7 @@ public void Send_ReturnsExpectedResponse_ExcludeSingleHandler() } [Fact] - public void Send_ReturnsExpectedResponse_IncludeAndExcludeOneHandlers() + public void AddDispatchR_ReturnsExpectedResponse_IncludeAndExcludeOneHandlers() { // Arrange var services = new ServiceCollection(); @@ -106,7 +107,7 @@ public void Send_ReturnsExpectedResponse_IncludeAndExcludeOneHandlers() } [Fact] - public void Send_ThrowsException_WhenIncludeHandlersBeEmpty() + public void AddDispatchR_ThrowsException_WhenIncludeHandlersBeEmpty() { // Arrange var services = new ServiceCollection(); @@ -125,7 +126,7 @@ public void Send_ThrowsException_WhenIncludeHandlersBeEmpty() } [Fact] - public void Send_ThrowsException_WhenExcludeHandlersBeEmpty() + public void AddDispatchR_ThrowsException_WhenExcludeHandlersBeEmpty() { // Arrange var services = new ServiceCollection(); @@ -144,7 +145,7 @@ public void Send_ThrowsException_WhenExcludeHandlersBeEmpty() } [Fact] - public async Task Send_UsesPipelineBehaviorsInCorrectOrder_RequestWithMultiplePipelines() + public async Task AddDispatchR_UsesPipelineBehaviorsInCorrectOrder_RequestWithMultiplePipelines() { // Arrange var services = new ServiceCollection(); @@ -172,7 +173,7 @@ public async Task Send_UsesPipelineBehaviorsInCorrectOrder_RequestWithMultiplePi } [Fact] - public void Send_RegisterGenericPipeline_IncludeGenericPipeline() + public void AddDispatchR_RegisterGenericPipeline_IncludeGenericPipeline() { // Arrange var services = new ServiceCollection(); @@ -194,4 +195,30 @@ public void Send_RegisterGenericPipeline_IncludeGenericPipeline() p.KeyedImplementationType?.GetGenericTypeDefinition() == typeof(GenericPipelineBehavior<,>).GetGenericTypeDefinition()); Assert.Equal(1, countOfAllSimpleHandlers); } + + [Fact] + public void AddDispatchR_RegisterNotifications_FindNotifications() + { + // Arrange + var services = new ServiceCollection(); + + // Act + services.AddDispatchR(cfg => + { + cfg.Assemblies.Add(typeof(Fixture).Assembly); + cfg.RegisterPipelines = false; + cfg.RegisterNotifications = true; + cfg.IncludeHandlers = [Fixture.AnyHandlerRequestWithoutPipeline.GetType()]; + }); + + // Assert + var countOfAllSimpleHandlers = services + .Count(p => + p.IsKeyedService is false && + (p.ImplementationType == typeof(NotificationOneHandler) || + p.ImplementationType == typeof(NotificationTwoHandler) || + p.ImplementationType == typeof(NotificationThreeHandler))); + + Assert.Equal(3, countOfAllSimpleHandlers); + } } \ No newline at end of file From dc58d5290c4b5ad28a498fdacf71df414f2f3fa5 Mon Sep 17 00:00:00 2001 From: hasanxdev Date: Sun, 3 Aug 2025 11:23:36 +0330 Subject: [PATCH 07/23] =?UTF-8?q?=E2=9C=A8=20Add=20unit=20tests=20for=20ne?= =?UTF-8?q?w=20pipeline=20behaviors=20and=20update=20service=20registratio?= =?UTF-8?q?n=20logic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build-release.yml | 14 ++++++ .../Configuration/ServiceRegistrator.cs | 37 +++++++++----- .../Requests/Send/IRequestHandler.cs | 3 ++ .../DispatchR.TestCommon/Fixtures/Fixture.cs | 6 ++- .../Notification/NotificationOneHandler.cs | 4 +- ...=> GenericPipelineBehaviorWithResponse.cs} | 2 +- .../GenericPipelineBehaviorWithoutResponse.cs | 15 ++++++ .../PingValueTaskFirstPipelineBehavior.cs | 5 +- .../ValueTaskWithOutResponse/PingValueTask.cs | 7 +++ .../PingValueTaskFirstPipelineBehavior.cs | 15 ++++++ .../PingValueTaskHandler.cs | 11 +++++ .../PingValueTaskSecondPipelineBehavior.cs | 16 +++++++ .../AddDispatchRConfigurationTests.cs | 19 +++++++- .../DispatchR.UnitTest/RequestHandlerTests.cs | 48 +++++++++++++++++++ tests/DispatchR.UnitTest/SendRequests.cs | 9 ---- 15 files changed, 182 insertions(+), 29 deletions(-) rename tests/DispatchR.TestCommon/Fixtures/SendRequest/{GenericPipelineBehavior.cs => GenericPipelineBehaviorWithResponse.cs} (87%) create mode 100644 tests/DispatchR.TestCommon/Fixtures/SendRequest/GenericPipelineBehaviorWithoutResponse.cs create mode 100644 tests/DispatchR.TestCommon/Fixtures/SendRequest/ValueTaskWithOutResponse/PingValueTask.cs create mode 100644 tests/DispatchR.TestCommon/Fixtures/SendRequest/ValueTaskWithOutResponse/PingValueTaskFirstPipelineBehavior.cs create mode 100644 tests/DispatchR.TestCommon/Fixtures/SendRequest/ValueTaskWithOutResponse/PingValueTaskHandler.cs create mode 100644 tests/DispatchR.TestCommon/Fixtures/SendRequest/ValueTaskWithOutResponse/PingValueTaskSecondPipelineBehavior.cs delete mode 100644 tests/DispatchR.UnitTest/SendRequests.cs diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index c53f98c..fc674ed 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -4,6 +4,9 @@ on: push: tags: - '*.*.*' + pull_request: + branches: + - main jobs: build: @@ -29,6 +32,17 @@ jobs: - name: Build run: dotnet build src/DispatchR/DispatchR.csproj --configuration Release --no-restore + - name: Run Unit Tests + run: dotnet test tests/DispatchR.UnitTest/DispatchR.UnitTest.csproj --configuration Release --no-build --filter "TestCategory=Unit" --collect:"XPlat Code Coverage" + + - name: Run Integration Tests + run: dotnet test tests/DispatchR.IntegrationTest/DispatchR.IntegrationTest.csproj --configuration Release --no-build --filter "TestCategory=Integration" --collect:"XPlat Code Coverage" + + - name: Upload Coverage to Codecov + uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODECOV_TOKEN }} + - name: Extract version from tag id: get_version run: echo "version=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT" diff --git a/src/DispatchR/Configuration/ServiceRegistrator.cs b/src/DispatchR/Configuration/ServiceRegistrator.cs index 3bfd207..aa76b4f 100644 --- a/src/DispatchR/Configuration/ServiceRegistrator.cs +++ b/src/DispatchR/Configuration/ServiceRegistrator.cs @@ -65,10 +65,10 @@ public static void RegisterHandlers(IServiceCollection services, List allT } return interfaces - .FirstOrDefault(inter => - inter.IsGenericType && - inter.GetGenericTypeDefinition() == behaviorType) - ?.GetInterfaces().First() == handlerInterface; + .FirstOrDefault(inter => + inter.IsGenericType && + inter.GetGenericTypeDefinition() == behaviorType) + ?.GetInterfaces().First() == handlerInterface; }).ToList(); // Sort pipelines by the specified order passed via ConfigurationOptions @@ -102,17 +102,28 @@ public static void RegisterHandlers(IServiceCollection services, List allT var responseTypeArg = handlerInterface.GenericTypeArguments[1]; if (genericHandlerResponseIsAwaitable && handlerResponseTypeIsAwaitable) { - if (genericHandlerResponseType.GetGenericTypeDefinition() != - handlerInterface.GenericTypeArguments[1].GetGenericTypeDefinition()) + var areGenericTypeArgumentsInHandlerInterfaceMismatched = + genericHandlerResponseType.IsGenericType && + handlerInterface.GenericTypeArguments[1].IsGenericType && + genericHandlerResponseType.GetGenericTypeDefinition() != + handlerInterface.GenericTypeArguments[1].GetGenericTypeDefinition(); + + if (areGenericTypeArgumentsInHandlerInterfaceMismatched || + genericHandlerResponseType.IsGenericType ^ + handlerInterface.GenericTypeArguments[1].IsGenericType) { continue; } // register async generic pipelines - responseTypeArg = responseTypeArg.GenericTypeArguments[0]; + if (responseTypeArg.GenericTypeArguments.Any()) + { + responseTypeArg = responseTypeArg.GenericTypeArguments[0]; + } } - var closedGenericType = pipeline.MakeGenericType(handlerInterface.GenericTypeArguments[0], responseTypeArg); + var closedGenericType = pipeline.MakeGenericType(handlerInterface.GenericTypeArguments[0], + responseTypeArg); services.AddKeyedScoped(typeof(IRequestHandler), key, closedGenericType); } else @@ -140,7 +151,8 @@ public static void RegisterHandlers(IServiceCollection services, List allT } } - public static void RegisterNotification(IServiceCollection services, List allTypes, Type syncNotificationHandlerType) + public static void RegisterNotification(IServiceCollection services, List allTypes, + Type syncNotificationHandlerType) { var allNotifications = allTypes .Where(p => @@ -150,7 +162,7 @@ public static void RegisterNotification(IServiceCollection services, List .Select(i => i.GetGenericTypeDefinition()) .Any(i => new[] { - syncNotificationHandlerType + syncNotificationHandlerType }.Contains(i)); }) .GroupBy(p => @@ -159,7 +171,7 @@ public static void RegisterNotification(IServiceCollection services, List .Where(i => i.IsGenericType) .First(i => new[] { - syncNotificationHandlerType + syncNotificationHandlerType }.Contains(i.GetGenericTypeDefinition())); return @interface.GenericTypeArguments.First(); }) @@ -182,7 +194,8 @@ private static bool IsAwaitable(Type type) if (type.IsGenericType) { var genericDef = type.GetGenericTypeDefinition(); - return genericDef == typeof(Task<>) || genericDef == typeof(ValueTask<>) || genericDef == typeof(IAsyncEnumerable<>); + return genericDef == typeof(Task<>) || genericDef == typeof(ValueTask<>) || + genericDef == typeof(IAsyncEnumerable<>); } return false; diff --git a/src/DispatchR/Requests/Send/IRequestHandler.cs b/src/DispatchR/Requests/Send/IRequestHandler.cs index 6ce2084..ad6ad24 100644 --- a/src/DispatchR/Requests/Send/IRequestHandler.cs +++ b/src/DispatchR/Requests/Send/IRequestHandler.cs @@ -1,7 +1,10 @@ +using System.Diagnostics.CodeAnalysis; + namespace DispatchR.Requests.Send; public interface IRequestHandler { + [ExcludeFromCodeCoverage] internal void SetNext(object handler) { } diff --git a/tests/DispatchR.TestCommon/Fixtures/Fixture.cs b/tests/DispatchR.TestCommon/Fixtures/Fixture.cs index 6331b01..d4ff657 100644 --- a/tests/DispatchR.TestCommon/Fixtures/Fixture.cs +++ b/tests/DispatchR.TestCommon/Fixtures/Fixture.cs @@ -1,17 +1,19 @@ using DispatchR.TestCommon.Fixtures.SendRequest.Task; using DispatchR.TestCommon.Fixtures.SendRequest.ValueTask; +using DispatchR.TestCommon.Fixtures.SendRequest.ValueTaskWithOutResponse; using DispatchR.TestCommon.Fixtures.StreamRequest; namespace DispatchR.TestCommon.Fixtures; public class Fixture { - public static PingTask AnyRequestWithoutPipeline => new(); public static PingTaskHandler AnyHandlerRequestWithoutPipeline => new(); - public static PingValueTask AnyRequestWithPipeline => new(); public static PingValueTaskHandler AnyHandlerRequestWithPipeline => new(); + public static PingValueTaskWithoutResponse AnyRequestWithoutResponsePipeline => new(); + public static PingValueTaskWithoutResponseHandler AnyHandlerRequestWithoutResponseWithPipeline => new(); + public static CounterStreamRequest AnyStreamRequest => new(); public static CounterStreamHandler AnyStreamHandler => new(); } \ No newline at end of file diff --git a/tests/DispatchR.TestCommon/Fixtures/Notification/NotificationOneHandler.cs b/tests/DispatchR.TestCommon/Fixtures/Notification/NotificationOneHandler.cs index 1e09860..284de16 100644 --- a/tests/DispatchR.TestCommon/Fixtures/Notification/NotificationOneHandler.cs +++ b/tests/DispatchR.TestCommon/Fixtures/Notification/NotificationOneHandler.cs @@ -4,8 +4,8 @@ namespace DispatchR.TestCommon.Fixtures.Notification; public sealed class NotificationOneHandler() : INotificationHandler { - public ValueTask Handle(MultiHandlersNotification request, CancellationToken cancellationToken) + public async ValueTask Handle(MultiHandlersNotification request, CancellationToken cancellationToken) { - return ValueTask.CompletedTask; + await Task.Delay(100, cancellationToken); } } \ No newline at end of file diff --git a/tests/DispatchR.TestCommon/Fixtures/SendRequest/GenericPipelineBehavior.cs b/tests/DispatchR.TestCommon/Fixtures/SendRequest/GenericPipelineBehaviorWithResponse.cs similarity index 87% rename from tests/DispatchR.TestCommon/Fixtures/SendRequest/GenericPipelineBehavior.cs rename to tests/DispatchR.TestCommon/Fixtures/SendRequest/GenericPipelineBehaviorWithResponse.cs index 63f7b3e..a34bef2 100644 --- a/tests/DispatchR.TestCommon/Fixtures/SendRequest/GenericPipelineBehavior.cs +++ b/tests/DispatchR.TestCommon/Fixtures/SendRequest/GenericPipelineBehaviorWithResponse.cs @@ -2,7 +2,7 @@ namespace DispatchR.TestCommon.Fixtures.SendRequest; -public class GenericPipelineBehavior() +public class GenericPipelineBehaviorWithResponse() : IPipelineBehavior> where TRequest : class, IRequest>, new() { diff --git a/tests/DispatchR.TestCommon/Fixtures/SendRequest/GenericPipelineBehaviorWithoutResponse.cs b/tests/DispatchR.TestCommon/Fixtures/SendRequest/GenericPipelineBehaviorWithoutResponse.cs new file mode 100644 index 0000000..419dcc2 --- /dev/null +++ b/tests/DispatchR.TestCommon/Fixtures/SendRequest/GenericPipelineBehaviorWithoutResponse.cs @@ -0,0 +1,15 @@ +using DispatchR.Requests.Send; + +namespace DispatchR.TestCommon.Fixtures.SendRequest; + +public class GenericPipelineBehaviorWithOutResponse() + : IPipelineBehavior + where TRequest : class, IRequest, new() +{ + public System.Threading.Tasks.ValueTask Handle(TRequest request, CancellationToken cancellationToken) + { + return NextPipeline.Handle(request, cancellationToken); + } + + public required IRequestHandler NextPipeline { get; set; } +} \ No newline at end of file diff --git a/tests/DispatchR.TestCommon/Fixtures/SendRequest/ValueTask/PingValueTaskFirstPipelineBehavior.cs b/tests/DispatchR.TestCommon/Fixtures/SendRequest/ValueTask/PingValueTaskFirstPipelineBehavior.cs index 4869146..10a1b80 100644 --- a/tests/DispatchR.TestCommon/Fixtures/SendRequest/ValueTask/PingValueTaskFirstPipelineBehavior.cs +++ b/tests/DispatchR.TestCommon/Fixtures/SendRequest/ValueTask/PingValueTaskFirstPipelineBehavior.cs @@ -6,9 +6,10 @@ public class PingValueTaskFirstPipelineBehavior() : IPipelineBehavior> NextPipeline { get; set; } - public ValueTask Handle(PingValueTask request, CancellationToken cancellationToken) + public async ValueTask Handle(PingValueTask request, CancellationToken cancellationToken) { ExecutionTime = DateTime.Now; - return NextPipeline.Handle(request, cancellationToken); + await System.Threading.Tasks.Task.Delay(100, cancellationToken).ConfigureAwait(false); + return await NextPipeline.Handle(request, cancellationToken); } } \ No newline at end of file diff --git a/tests/DispatchR.TestCommon/Fixtures/SendRequest/ValueTaskWithOutResponse/PingValueTask.cs b/tests/DispatchR.TestCommon/Fixtures/SendRequest/ValueTaskWithOutResponse/PingValueTask.cs new file mode 100644 index 0000000..31ff982 --- /dev/null +++ b/tests/DispatchR.TestCommon/Fixtures/SendRequest/ValueTaskWithOutResponse/PingValueTask.cs @@ -0,0 +1,7 @@ +using DispatchR.Requests.Send; + +namespace DispatchR.TestCommon.Fixtures.SendRequest.ValueTaskWithOutResponse; + +public class PingValueTaskWithoutResponse : IRequest +{ +} \ No newline at end of file diff --git a/tests/DispatchR.TestCommon/Fixtures/SendRequest/ValueTaskWithOutResponse/PingValueTaskFirstPipelineBehavior.cs b/tests/DispatchR.TestCommon/Fixtures/SendRequest/ValueTaskWithOutResponse/PingValueTaskFirstPipelineBehavior.cs new file mode 100644 index 0000000..584f206 --- /dev/null +++ b/tests/DispatchR.TestCommon/Fixtures/SendRequest/ValueTaskWithOutResponse/PingValueTaskFirstPipelineBehavior.cs @@ -0,0 +1,15 @@ +using DispatchR.Requests.Send; + +namespace DispatchR.TestCommon.Fixtures.SendRequest.ValueTaskWithOutResponse; + +public class PingValueTaskFirstPipelineBehavior() : IPipelineBehavior +{ + public static DateTime ExecutionTime { get; private set; } + public required IRequestHandler NextPipeline { get; set; } + public async System.Threading.Tasks.ValueTask Handle(PingValueTaskWithoutResponse request, CancellationToken cancellationToken) + { + ExecutionTime = DateTime.Now; + await System.Threading.Tasks.Task.Delay(100, cancellationToken).ConfigureAwait(false); + await NextPipeline.Handle(request, cancellationToken); + } +} \ No newline at end of file diff --git a/tests/DispatchR.TestCommon/Fixtures/SendRequest/ValueTaskWithOutResponse/PingValueTaskHandler.cs b/tests/DispatchR.TestCommon/Fixtures/SendRequest/ValueTaskWithOutResponse/PingValueTaskHandler.cs new file mode 100644 index 0000000..9b54119 --- /dev/null +++ b/tests/DispatchR.TestCommon/Fixtures/SendRequest/ValueTaskWithOutResponse/PingValueTaskHandler.cs @@ -0,0 +1,11 @@ +using DispatchR.Requests.Send; + +namespace DispatchR.TestCommon.Fixtures.SendRequest.ValueTaskWithOutResponse; + +public class PingValueTaskWithoutResponseHandler() : IRequestHandler +{ + public System.Threading.Tasks.ValueTask Handle(PingValueTaskWithoutResponse request, CancellationToken cancellationToken) + { + return System.Threading.Tasks.ValueTask.CompletedTask; + } +} \ No newline at end of file diff --git a/tests/DispatchR.TestCommon/Fixtures/SendRequest/ValueTaskWithOutResponse/PingValueTaskSecondPipelineBehavior.cs b/tests/DispatchR.TestCommon/Fixtures/SendRequest/ValueTaskWithOutResponse/PingValueTaskSecondPipelineBehavior.cs new file mode 100644 index 0000000..f04f92e --- /dev/null +++ b/tests/DispatchR.TestCommon/Fixtures/SendRequest/ValueTaskWithOutResponse/PingValueTaskSecondPipelineBehavior.cs @@ -0,0 +1,16 @@ +using DispatchR.Requests.Send; + +namespace DispatchR.TestCommon.Fixtures.SendRequest.ValueTaskWithOutResponse +{ + public class PingValueTaskWithoutResponseSecondPipelineBehavior() : IPipelineBehavior + { + public static DateTime ExecutionTime { get; private set; } + public required IRequestHandler NextPipeline { get; set; } + + public System.Threading.Tasks.ValueTask Handle(PingValueTaskWithoutResponse request, CancellationToken cancellationToken) + { + ExecutionTime = DateTime.Now; + return NextPipeline.Handle(request, cancellationToken); + } + } +} \ No newline at end of file diff --git a/tests/DispatchR.UnitTest/AddDispatchRConfigurationTests.cs b/tests/DispatchR.UnitTest/AddDispatchRConfigurationTests.cs index b7fa504..f8c3b25 100644 --- a/tests/DispatchR.UnitTest/AddDispatchRConfigurationTests.cs +++ b/tests/DispatchR.UnitTest/AddDispatchRConfigurationTests.cs @@ -12,6 +12,23 @@ namespace DispatchR.UnitTest; public class AddDispatchRConfigurationTests { + [Fact] + public void TraditionalAddDispatchR_ReturnsExpectedResponse_DefaultHandlers() + { + // Arrange + var services = new ServiceCollection(); + + // Act + services.AddDispatchR(typeof(Fixture).Assembly, withPipelines: true, withNotifications: false); + + // Assert + var countOfAllSimpleHandlers = services + .Count(p => + p.IsKeyedService && + p.KeyedImplementationType!.GetInterface(typeof(IStreamRequestHandler<,>).Name, true) is null); + Assert.True(countOfAllSimpleHandlers > 1); + } + [Fact] public void AddDispatchR_ReturnsExpectedResponse_DefaultHandlers() { @@ -192,7 +209,7 @@ public void AddDispatchR_RegisterGenericPipeline_IncludeGenericPipeline() .Count(p => p.IsKeyedService && p.KeyedImplementationType!.IsGenericType && - p.KeyedImplementationType?.GetGenericTypeDefinition() == typeof(GenericPipelineBehavior<,>).GetGenericTypeDefinition()); + p.KeyedImplementationType?.GetGenericTypeDefinition() == typeof(GenericPipelineBehaviorWithResponse<,>).GetGenericTypeDefinition()); Assert.Equal(1, countOfAllSimpleHandlers); } diff --git a/tests/DispatchR.UnitTest/RequestHandlerTests.cs b/tests/DispatchR.UnitTest/RequestHandlerTests.cs index 0bca149..fb78e28 100644 --- a/tests/DispatchR.UnitTest/RequestHandlerTests.cs +++ b/tests/DispatchR.UnitTest/RequestHandlerTests.cs @@ -103,6 +103,54 @@ public async Task Send_UsesPipelineBehaviors_RequestWithPipelines() Assert.Equal(1, result); } + [Fact] + public async Task Send_UsesPipelineBehaviors_RequestWithOutResponseWithPipelines() + { + // Arrange + var services = new ServiceCollection(); + services.AddDispatchR(cfg => + { + cfg.Assemblies.Add(typeof(Fixture).Assembly); + cfg.RegisterPipelines = true; + cfg.RegisterNotifications = false; + cfg.IncludeHandlers = [Fixture.AnyHandlerRequestWithoutResponseWithPipeline.GetType()]; + }); + var serviceProvider = services.BuildServiceProvider(); + var mediator = serviceProvider.GetRequiredService(); + + // Act + await mediator.Send(Fixture.AnyRequestWithoutResponsePipeline, CancellationToken.None); + + // Assert + // Just checking if it runs without exceptions + } + + [Fact] + public async Task Send_UsesPipelineBehaviors_ChangePipelineOrdering() + { + // Arrange + var services = new ServiceCollection(); + services.AddDispatchR(cfg => + { + cfg.Assemblies.Add(typeof(Fixture).Assembly); + cfg.RegisterPipelines = true; + cfg.RegisterNotifications = false; + cfg.PipelineOrder = + [ + typeof(PingValueTaskFirstPipelineBehavior) + ]; + cfg.IncludeHandlers = [Fixture.AnyHandlerRequestWithPipeline.GetType()]; + }); + var serviceProvider = services.BuildServiceProvider(); + var mediator = serviceProvider.GetRequiredService(); + + // Act + var result = await mediator.Send(new PingValueTask(), CancellationToken.None); + + // Assert + Assert.Equal(1, result); + } + [Fact] public void Send_ThrowsException_WhenNoHandlerIsRegistered() { diff --git a/tests/DispatchR.UnitTest/SendRequests.cs b/tests/DispatchR.UnitTest/SendRequests.cs deleted file mode 100644 index e9b1731..0000000 --- a/tests/DispatchR.UnitTest/SendRequests.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace DispatchR.UnitTest; - -public class SendRequests -{ - [Fact] - public void Send() - { - } -} \ No newline at end of file From b9b20c652d4ca3d78106946542270a2dc5568b06 Mon Sep 17 00:00:00 2001 From: hasanxdev Date: Sun, 3 Aug 2025 11:27:05 +0330 Subject: [PATCH 08/23] =?UTF-8?q?=E2=9C=A8=20Update=20build-release.yml=20?= =?UTF-8?q?to=20conditionally=20execute=20steps=20for=20versioned=20tags?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build-release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index fc674ed..4a148a4 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -45,10 +45,13 @@ jobs: - name: Extract version from tag id: get_version + if: startsWith(github.ref, 'refs/tags/v') run: echo "version=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT" - name: Pack project + if: startsWith(github.ref, 'refs/tags/v') run: dotnet pack src/DispatchR/DispatchR.csproj --configuration Release --no-build -o ./nupkgs /p:PackageVersion=${{ steps.get_version.outputs.version }} - name: Push to NuGet + if: startsWith(github.ref, 'refs/tags/v') run: dotnet nuget push "./nupkgs/*.nupkg" --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json \ No newline at end of file From c69ac258021a7743e7a46947f28a3b85f8618c9e Mon Sep 17 00:00:00 2001 From: hasanxdev Date: Sun, 3 Aug 2025 11:37:34 +0330 Subject: [PATCH 09/23] =?UTF-8?q?=E2=9C=A8=20Update=20build-release.yml=20?= =?UTF-8?q?to=20include=20coverage=20report=20file=20in=20Codecov=20action?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build-release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 4a148a4..b0425a2 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -42,6 +42,7 @@ jobs: uses: codecov/codecov-action@v3 with: token: ${{ secrets.CODECOV_TOKEN }} + files: '**/coverage.cobertura.xml' - name: Extract version from tag id: get_version From 664d86a08440c69fa79ca2c38014c2d8202a3dc1 Mon Sep 17 00:00:00 2001 From: hasanxdev Date: Sun, 3 Aug 2025 11:50:31 +0330 Subject: [PATCH 10/23] =?UTF-8?q?=E2=9C=A8=20Update=20Codecov=20action=20t?= =?UTF-8?q?o=20version=205=20and=20adjust=20token=20handling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build-release.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index b0425a2..ad4d900 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -39,10 +39,9 @@ jobs: run: dotnet test tests/DispatchR.IntegrationTest/DispatchR.IntegrationTest.csproj --configuration Release --no-build --filter "TestCategory=Integration" --collect:"XPlat Code Coverage" - name: Upload Coverage to Codecov - uses: codecov/codecov-action@v3 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: '**/coverage.cobertura.xml' + uses: codecov/codecov-action@v5 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - name: Extract version from tag id: get_version From 631424f71fb6b32a1b1dfcff4e556d23bfa0681b Mon Sep 17 00:00:00 2001 From: hasanxdev Date: Sun, 3 Aug 2025 11:56:54 +0330 Subject: [PATCH 11/23] =?UTF-8?q?=E2=9C=A8=20Update=20build-release.yml=20?= =?UTF-8?q?to=20specify=20coverage=20report=20file=20for=20Codecov=20actio?= =?UTF-8?q?n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build-release.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index ad4d900..ab3765e 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -42,6 +42,8 @@ jobs: uses: codecov/codecov-action@v5 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + with: + files: '**/coverage.cobertura.xml' - name: Extract version from tag id: get_version From d42d4b9edc8da9d48495f49f7570317f7c703184 Mon Sep 17 00:00:00 2001 From: hasanxdev Date: Sun, 3 Aug 2025 11:59:26 +0330 Subject: [PATCH 12/23] =?UTF-8?q?=E2=9C=A8=20Update=20build-release.yml=20?= =?UTF-8?q?to=20change=20coverage=20report=20file=20path=20for=20Codecov?= =?UTF-8?q?=20action?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index ab3765e..9244028 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -43,7 +43,7 @@ jobs: env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: - files: '**/coverage.cobertura.xml' + files: './TestResults/**/coverage.cobertura.xml' - name: Extract version from tag id: get_version From aa9cd35d96f0fba185b9c9caf5cea70eeef81860 Mon Sep 17 00:00:00 2001 From: hasanxdev Date: Sun, 3 Aug 2025 12:00:28 +0330 Subject: [PATCH 13/23] =?UTF-8?q?=E2=9C=A8=20Update=20build-release.yml=20?= =?UTF-8?q?to=20ensure=20consistent=20code=20coverage=20collection=20for?= =?UTF-8?q?=20unit=20and=20integration=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build-release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 9244028..5a102d5 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -33,10 +33,10 @@ jobs: run: dotnet build src/DispatchR/DispatchR.csproj --configuration Release --no-restore - name: Run Unit Tests - run: dotnet test tests/DispatchR.UnitTest/DispatchR.UnitTest.csproj --configuration Release --no-build --filter "TestCategory=Unit" --collect:"XPlat Code Coverage" + run: dotnet test tests/DispatchR.UnitTest/DispatchR.UnitTest.csproj --configuration Release --no-build --collect:"XPlat Code Coverage" - name: Run Integration Tests - run: dotnet test tests/DispatchR.IntegrationTest/DispatchR.IntegrationTest.csproj --configuration Release --no-build --filter "TestCategory=Integration" --collect:"XPlat Code Coverage" + run: dotnet test tests/DispatchR.IntegrationTest/DispatchR.IntegrationTest.csproj --configuration Release --no-build --collect:"XPlat Code Coverage" - name: Upload Coverage to Codecov uses: codecov/codecov-action@v5 From ab605989c97305bfe1977de728ecabd85dc73d5c Mon Sep 17 00:00:00 2001 From: hasanxdev Date: Sun, 3 Aug 2025 12:02:07 +0330 Subject: [PATCH 14/23] =?UTF-8?q?=E2=9C=A8=20Update=20build-release.yml=20?= =?UTF-8?q?to=20adjust=20coverage=20report=20file=20path=20for=20Codecov?= =?UTF-8?q?=20action?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 5a102d5..83ff91f 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -43,7 +43,7 @@ jobs: env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: - files: './TestResults/**/coverage.cobertura.xml' + files: '**/coverage.cobertura.xml' - name: Extract version from tag id: get_version From 3c7c8d0409e5277d53ee362b8e01453d84d184a2 Mon Sep 17 00:00:00 2001 From: hasanxdev Date: Sun, 3 Aug 2025 12:04:40 +0330 Subject: [PATCH 15/23] =?UTF-8?q?=E2=9C=A8=20Update=20build-release.yml=20?= =?UTF-8?q?to=20consolidate=20unit=20and=20integration=20tests=20into=20a?= =?UTF-8?q?=20single=20test=20step=20with=20coverage=20collection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build-release.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 83ff91f..87c6ff2 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -32,11 +32,8 @@ jobs: - name: Build run: dotnet build src/DispatchR/DispatchR.csproj --configuration Release --no-restore - - name: Run Unit Tests - run: dotnet test tests/DispatchR.UnitTest/DispatchR.UnitTest.csproj --configuration Release --no-build --collect:"XPlat Code Coverage" - - - name: Run Integration Tests - run: dotnet test tests/DispatchR.IntegrationTest/DispatchR.IntegrationTest.csproj --configuration Release --no-build --collect:"XPlat Code Coverage" + - name: Run Tests + run: dotnet test --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=opencover - name: Upload Coverage to Codecov uses: codecov/codecov-action@v5 From 6b6ad0cbdfcde0d1485a937d3071d82d5b553566 Mon Sep 17 00:00:00 2001 From: hasanxdev Date: Sun, 3 Aug 2025 12:05:34 +0330 Subject: [PATCH 16/23] =?UTF-8?q?=E2=9C=A8=20Update=20build-release.yml=20?= =?UTF-8?q?to=20remove=20redundant=20coverage=20report=20file=20specificat?= =?UTF-8?q?ion=20for=20Codecov=20action?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build-release.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 87c6ff2..e8bd06d 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -39,8 +39,6 @@ jobs: uses: codecov/codecov-action@v5 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - with: - files: '**/coverage.cobertura.xml' - name: Extract version from tag id: get_version From 50a2cf111c5a784a4155417bbae4b50eb1c3a5f4 Mon Sep 17 00:00:00 2001 From: hasanxdev Date: Sun, 3 Aug 2025 12:10:37 +0330 Subject: [PATCH 17/23] =?UTF-8?q?=E2=9C=A8=20Update=20build-release.yml=20?= =?UTF-8?q?to=20add=20a=20step=20for=20listing=20files=20before=20uploadin?= =?UTF-8?q?g=20coverage=20to=20Codecov?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build-release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index e8bd06d..b0fdb05 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -35,6 +35,9 @@ jobs: - name: Run Tests run: dotnet test --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=opencover + - name: List files + run: ls -alh + - name: Upload Coverage to Codecov uses: codecov/codecov-action@v5 env: From ee9cba3110ce2c1136527d27453a58d556302a24 Mon Sep 17 00:00:00 2001 From: hasanxdev Date: Sun, 3 Aug 2025 12:11:58 +0330 Subject: [PATCH 18/23] =?UTF-8?q?=E2=9C=A8=20Update=20build-release.yml=20?= =?UTF-8?q?to=20list=20files=20in=20the=20tests=20directory=20before=20upl?= =?UTF-8?q?oading=20coverage=20to=20Codecov?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index b0fdb05..979aa75 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -36,7 +36,7 @@ jobs: run: dotnet test --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=opencover - name: List files - run: ls -alh + run: ls -alh tests - name: Upload Coverage to Codecov uses: codecov/codecov-action@v5 From 2c044e820c0b9b53d8e7e33d7a67e6e0f749cb98 Mon Sep 17 00:00:00 2001 From: hasanxdev Date: Sun, 3 Aug 2025 12:17:00 +0330 Subject: [PATCH 19/23] =?UTF-8?q?=E2=9C=A8=20Update=20test=20project=20fil?= =?UTF-8?q?es=20to=20upgrade=20coverlet=20packages=20to=20version=206.0.4?= =?UTF-8?q?=20with=20updated=20asset=20settings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DispatchR.IntegrationTest.csproj | 10 ++++++++-- tests/DispatchR.UnitTest/DispatchR.UnitTest.csproj | 9 ++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/tests/DispatchR.IntegrationTest/DispatchR.IntegrationTest.csproj b/tests/DispatchR.IntegrationTest/DispatchR.IntegrationTest.csproj index bb4c3da..4594ed9 100644 --- a/tests/DispatchR.IntegrationTest/DispatchR.IntegrationTest.csproj +++ b/tests/DispatchR.IntegrationTest/DispatchR.IntegrationTest.csproj @@ -9,8 +9,14 @@ - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/tests/DispatchR.UnitTest/DispatchR.UnitTest.csproj b/tests/DispatchR.UnitTest/DispatchR.UnitTest.csproj index 006b9ff..69524d7 100644 --- a/tests/DispatchR.UnitTest/DispatchR.UnitTest.csproj +++ b/tests/DispatchR.UnitTest/DispatchR.UnitTest.csproj @@ -8,8 +8,15 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + - From 9022fe0b5783c08035d3b91b6cacb95d162c27a2 Mon Sep 17 00:00:00 2001 From: hasanxdev Date: Sun, 3 Aug 2025 12:18:14 +0330 Subject: [PATCH 20/23] =?UTF-8?q?=E2=9C=A8=20Update=20build-release.yml=20?= =?UTF-8?q?to=20list=20files=20in=20the=20DispatchR.IntegrationTest=20dire?= =?UTF-8?q?ctory=20before=20uploading=20coverage=20to=20Codecov?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 979aa75..5beaa3a 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -36,7 +36,7 @@ jobs: run: dotnet test --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=opencover - name: List files - run: ls -alh tests + run: ls -alh tests/DispatchR.IntegrationTest - name: Upload Coverage to Codecov uses: codecov/codecov-action@v5 From 6e285ef80183aa10fb12feaa879e2f17b2abcabd Mon Sep 17 00:00:00 2001 From: hasanxdev Date: Sun, 3 Aug 2025 12:23:08 +0330 Subject: [PATCH 21/23] =?UTF-8?q?=E2=9C=A8=20Update=20build-release.yml=20?= =?UTF-8?q?to=20install=20Coverlet=20tool=20and=20modify=20test=20command?= =?UTF-8?q?=20for=20improved=20coverage=20collection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build-release.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 5beaa3a..06dd49b 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -26,6 +26,9 @@ jobs: with: dotnet-version: '9.x' + - name: Install Coverlet + run: dotnet tool install --global coverlet.console + - name: Restore run: dotnet restore src/DispatchR/DispatchR.csproj @@ -33,7 +36,7 @@ jobs: run: dotnet build src/DispatchR/DispatchR.csproj --configuration Release --no-restore - name: Run Tests - run: dotnet test --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=opencover + run: dotnet test --no-build --configuration Release --collect:"XPlat Code Coverage" - name: List files run: ls -alh tests/DispatchR.IntegrationTest From 1b72bfa4f2f3bd0e7b5fc0ae73fbefdd2ccace0a Mon Sep 17 00:00:00 2001 From: hasanxdev Date: Sun, 3 Aug 2025 12:27:59 +0330 Subject: [PATCH 22/23] =?UTF-8?q?=E2=9C=A8=20Update=20build-release.yml=20?= =?UTF-8?q?to=20remove=20unnecessary=20build=20step=20from=20test=20comman?= =?UTF-8?q?d?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 06dd49b..01de3b6 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -36,7 +36,7 @@ jobs: run: dotnet build src/DispatchR/DispatchR.csproj --configuration Release --no-restore - name: Run Tests - run: dotnet test --no-build --configuration Release --collect:"XPlat Code Coverage" + run: dotnet test --collect:"XPlat Code Coverage" - name: List files run: ls -alh tests/DispatchR.IntegrationTest From 31b4fd3a2d8b6b92a72451776a1a7af75cfba1e7 Mon Sep 17 00:00:00 2001 From: hasanxdev Date: Sun, 3 Aug 2025 12:31:58 +0330 Subject: [PATCH 23/23] =?UTF-8?q?=E2=9C=A8=20Update=20README.md=20to=20add?= =?UTF-8?q?=20Codecov=20badge=20for=20coverage=20tracking?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 650572d..c755363 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # DispatchR 🚀 ![CI](https://github.com/hasanxdev/DispatchR/workflows/Release/badge.svg) +[![codecov](https://codecov.io/github/hasanxdev/dispatchr/graph/badge.svg?token=1FUG5DPUOE)](https://codecov.io/github/hasanxdev/dispatchr) [![NuGet](https://img.shields.io/nuget/dt/DispatchR.Mediator.svg)](https://www.nuget.org/packages/DispatchR.Mediator) [![NuGet](https://img.shields.io/nuget/vpre/DispatchR.Mediator.svg)](https://www.nuget.org/packages/DispatchR.Mediator)