From 5bb052957692c808a76d129d81b760ab9c9375d8 Mon Sep 17 00:00:00 2001 From: "Hasan A.B." Date: Thu, 9 Jul 2026 01:30:47 +0330 Subject: [PATCH 1/2] ignore abstract classes in registration --- .../Configuration/ServiceRegistrator.cs | 41 ++++++++++--------- .../AbstractNotificationHandler.cs | 8 ++++ .../Notification/ConcreteWorkflowEvent.cs | 3 ++ .../ConcreteWorkflowNotificationHandler.cs | 11 +++++ .../Fixtures/Notification/IWorkflowEvent.cs | 3 ++ .../IWorkflowNotificationHandler.cs | 6 +++ .../Notification/WorkflowNotification.cs | 6 +++ .../AddDispatchRConfigurationTests.cs | 28 +++++++++++++ 8 files changed, 86 insertions(+), 20 deletions(-) create mode 100644 tests/DispatchR.TestCommon/Fixtures/Notification/AbstractNotificationHandler.cs create mode 100644 tests/DispatchR.TestCommon/Fixtures/Notification/ConcreteWorkflowEvent.cs create mode 100644 tests/DispatchR.TestCommon/Fixtures/Notification/ConcreteWorkflowNotificationHandler.cs create mode 100644 tests/DispatchR.TestCommon/Fixtures/Notification/IWorkflowEvent.cs create mode 100644 tests/DispatchR.TestCommon/Fixtures/Notification/IWorkflowNotificationHandler.cs create mode 100644 tests/DispatchR.TestCommon/Fixtures/Notification/WorkflowNotification.cs diff --git a/src/DispatchR/Configuration/ServiceRegistrator.cs b/src/DispatchR/Configuration/ServiceRegistrator.cs index b88950a..c250bfe 100644 --- a/src/DispatchR/Configuration/ServiceRegistrator.cs +++ b/src/DispatchR/Configuration/ServiceRegistrator.cs @@ -170,28 +170,29 @@ public static void RegisterHandlers(IServiceCollection services, List allT } } - public static void RegisterNotification(IServiceCollection services, List allTypes, - Type syncNotificationHandlerType) - { - var allNotifications = allTypes - .SelectMany(handlerType => handlerType.GetInterfaces() - .Where(i => i.IsGenericType && syncNotificationHandlerType == i.GetGenericTypeDefinition()) - .Select(i => new { HandlerType = handlerType, Interface = i })) - .ToList(); - - foreach (var notification in allNotifications) - { - var serviceType = notification.Interface; - var implementationType = notification.HandlerType; - + public static void RegisterNotification(IServiceCollection services, List allTypes, + Type syncNotificationHandlerType) + { + var allNotifications = allTypes + .Where(handlerType => handlerType.IsClass && !handlerType.IsAbstract) + .SelectMany(handlerType => handlerType.GetInterfaces() + .Where(i => i.IsGenericType && syncNotificationHandlerType == i.GetGenericTypeDefinition()) + .Select(i => new { HandlerType = handlerType, Interface = i })) + .ToList(); + + foreach (var notification in allNotifications) + { + var serviceType = notification.Interface; + var implementationType = notification.HandlerType; + if (serviceType.ContainsGenericParameters) { serviceType = serviceType.GetGenericTypeDefinition(); - } - - services.AddScoped(serviceType, implementationType); - } - } + } + + services.AddScoped(serviceType, implementationType); + } + } private static bool IsAwaitable(Type type) { @@ -208,4 +209,4 @@ private static bool IsAwaitable(Type type) return false; } } -} +} diff --git a/tests/DispatchR.TestCommon/Fixtures/Notification/AbstractNotificationHandler.cs b/tests/DispatchR.TestCommon/Fixtures/Notification/AbstractNotificationHandler.cs new file mode 100644 index 0000000..e3d0938 --- /dev/null +++ b/tests/DispatchR.TestCommon/Fixtures/Notification/AbstractNotificationHandler.cs @@ -0,0 +1,8 @@ +using DispatchR.Abstractions.Notification; + +namespace DispatchR.TestCommon.Fixtures.Notification; + +public abstract class AbstractNotificationHandler : INotificationHandler +{ + public abstract ValueTask Handle(OpenGenericTargetNotification request, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/tests/DispatchR.TestCommon/Fixtures/Notification/ConcreteWorkflowEvent.cs b/tests/DispatchR.TestCommon/Fixtures/Notification/ConcreteWorkflowEvent.cs new file mode 100644 index 0000000..e5c47c8 --- /dev/null +++ b/tests/DispatchR.TestCommon/Fixtures/Notification/ConcreteWorkflowEvent.cs @@ -0,0 +1,3 @@ +namespace DispatchR.TestCommon.Fixtures.Notification; + +public sealed record ConcreteWorkflowEvent : IWorkflowEvent; \ No newline at end of file diff --git a/tests/DispatchR.TestCommon/Fixtures/Notification/ConcreteWorkflowNotificationHandler.cs b/tests/DispatchR.TestCommon/Fixtures/Notification/ConcreteWorkflowNotificationHandler.cs new file mode 100644 index 0000000..197ffda --- /dev/null +++ b/tests/DispatchR.TestCommon/Fixtures/Notification/ConcreteWorkflowNotificationHandler.cs @@ -0,0 +1,11 @@ +using DispatchR.Abstractions.Notification; + +namespace DispatchR.TestCommon.Fixtures.Notification; + +public sealed class ConcreteWorkflowNotificationHandler : INotificationHandler> +{ + public ValueTask Handle(WorkflowNotification request, CancellationToken cancellationToken) + { + return ValueTask.CompletedTask; + } +} \ No newline at end of file diff --git a/tests/DispatchR.TestCommon/Fixtures/Notification/IWorkflowEvent.cs b/tests/DispatchR.TestCommon/Fixtures/Notification/IWorkflowEvent.cs new file mode 100644 index 0000000..225a843 --- /dev/null +++ b/tests/DispatchR.TestCommon/Fixtures/Notification/IWorkflowEvent.cs @@ -0,0 +1,3 @@ +namespace DispatchR.TestCommon.Fixtures.Notification; + +public interface IWorkflowEvent; \ No newline at end of file diff --git a/tests/DispatchR.TestCommon/Fixtures/Notification/IWorkflowNotificationHandler.cs b/tests/DispatchR.TestCommon/Fixtures/Notification/IWorkflowNotificationHandler.cs new file mode 100644 index 0000000..481db0d --- /dev/null +++ b/tests/DispatchR.TestCommon/Fixtures/Notification/IWorkflowNotificationHandler.cs @@ -0,0 +1,6 @@ +using DispatchR.Abstractions.Notification; + +namespace DispatchR.TestCommon.Fixtures.Notification; + +public interface IWorkflowNotificationHandler : INotificationHandler> + where TNotification : IWorkflowEvent; \ No newline at end of file diff --git a/tests/DispatchR.TestCommon/Fixtures/Notification/WorkflowNotification.cs b/tests/DispatchR.TestCommon/Fixtures/Notification/WorkflowNotification.cs new file mode 100644 index 0000000..13dc764 --- /dev/null +++ b/tests/DispatchR.TestCommon/Fixtures/Notification/WorkflowNotification.cs @@ -0,0 +1,6 @@ +using DispatchR.Abstractions.Notification; + +namespace DispatchR.TestCommon.Fixtures.Notification; + +public sealed record WorkflowNotification(TNotification Event) : INotification + where TNotification : IWorkflowEvent; \ No newline at end of file diff --git a/tests/DispatchR.UnitTest/AddDispatchRConfigurationTests.cs b/tests/DispatchR.UnitTest/AddDispatchRConfigurationTests.cs index 835526c..ec3f932 100644 --- a/tests/DispatchR.UnitTest/AddDispatchRConfigurationTests.cs +++ b/tests/DispatchR.UnitTest/AddDispatchRConfigurationTests.cs @@ -327,4 +327,32 @@ p.ImplementationType is not null && Assert.NotNull(openGenericHandler); } + + [Fact] + public void AddDispatchR_RegisterNotifications_SkipsInterfaceAndAbstractHandlers() + { + // Arrange + var services = new ServiceCollection(); + + // Act + services.AddDispatchR(cfg => + { + cfg.Assemblies.Add(typeof(Fixture).Assembly); + cfg.RegisterPipelines = false; + cfg.RegisterNotifications = true; + }); + + // Assert + Assert.DoesNotContain(services, p => + p.IsKeyedService is false && + p.ImplementationType == typeof(AbstractNotificationHandler)); + + Assert.DoesNotContain(services, p => + p.IsKeyedService is false && + p.ImplementationType == typeof(IWorkflowNotificationHandler<>)); + + Assert.Contains(services, p => + p.IsKeyedService is false && + p.ImplementationType == typeof(ConcreteWorkflowNotificationHandler)); + } } From 195e939f9acc62ef10ce0cdf678786f6be2978f2 Mon Sep 17 00:00:00 2001 From: "Hasan A.B." Date: Thu, 9 Jul 2026 01:43:42 +0330 Subject: [PATCH 2/2] add copilot-instructions.md --- .github/copilot-instructions.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .github/copilot-instructions.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..3ea53bc --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,8 @@ +# Repository Instructions + +- Treat 100% test coverage as a hard requirement for this repository. +- Before finishing any code change, add or update tests until the changed area reaches 100% coverage. +- Run the relevant tests and coverage check for the affected project or slice before reporting completion. +- If 100% coverage is not achievable because of an existing gap, call it out explicitly and do not claim the task is fully done until the gap is addressed or the user approves the exception. +- Treat the codebase as two phases: before application start and after application start. +- Any changes in the after-application-start phase must be zero-allocation.