From f87c13517c64f2ef1f0d1212a38bc8cb5629790e Mon Sep 17 00:00:00 2001 From: Artem Dudarev Date: Thu, 23 May 2024 19:18:52 +0200 Subject: [PATCH] VCST-1230: Add UnregisterEventHandler() (#2797) --- .../Bus/HandlerWrapper.cs | 2 + .../Bus/IHandlerRegistrar.cs | 2 +- .../Bus/InProcessBus.cs | 61 +++++++++- .../Events/ApplicationBuilderExtensions.cs | 43 ++++++- .../Events/IEventHandlerRegistrar.cs | 9 ++ .../ApplicationBuilderExtensions.cs | 5 +- .../Extensions/RecurringJobExtensions.cs | 6 + .../Extensions/ServiceCollectionExtensions.cs | 3 + .../IRecurringJobService.cs | 21 ++++ .../RecurringJobService.cs | 108 ++++++++++++++++++ .../Security/ApplicationBuilderExtensions.cs | 8 +- .../Events/EventHandlerTests.cs | 92 ++++++++++++--- .../UnitTests/JobSettingsWatcherUnitTests.cs | 14 +-- 13 files changed, 335 insertions(+), 39 deletions(-) create mode 100644 src/VirtoCommerce.Platform.Hangfire/IRecurringJobService.cs create mode 100644 src/VirtoCommerce.Platform.Hangfire/RecurringJobService.cs diff --git a/src/VirtoCommerce.Platform.Core/Bus/HandlerWrapper.cs b/src/VirtoCommerce.Platform.Core/Bus/HandlerWrapper.cs index 80742bb69b2..784c1027647 100644 --- a/src/VirtoCommerce.Platform.Core/Bus/HandlerWrapper.cs +++ b/src/VirtoCommerce.Platform.Core/Bus/HandlerWrapper.cs @@ -13,6 +13,8 @@ public sealed class HandlerWrapper { public Type EventType { get; set; } + public Type HandlerType { get; set; } + public string HandlerModuleName { get; set; } public Func Handler { get; set; } diff --git a/src/VirtoCommerce.Platform.Core/Bus/IHandlerRegistrar.cs b/src/VirtoCommerce.Platform.Core/Bus/IHandlerRegistrar.cs index 011f8c5a300..65193aa4b84 100644 --- a/src/VirtoCommerce.Platform.Core/Bus/IHandlerRegistrar.cs +++ b/src/VirtoCommerce.Platform.Core/Bus/IHandlerRegistrar.cs @@ -7,7 +7,7 @@ namespace VirtoCommerce.Platform.Core.Bus { public interface IHandlerRegistrar { - [Obsolete("Use IApplicationBuilder.RegisterEventHandler<>()", DiagnosticId = "VC0008", UrlFormat = "https://docs.virtocommerce.org/products/products-virto3-versions")] + [Obsolete("Use IApplicationBuilder.RegisterEventHandler()", DiagnosticId = "VC0008", UrlFormat = "https://docs.virtocommerce.org/products/products-virto3-versions")] void RegisterHandler(Func handler) where T : IMessage; } } diff --git a/src/VirtoCommerce.Platform.Core/Bus/InProcessBus.cs b/src/VirtoCommerce.Platform.Core/Bus/InProcessBus.cs index 351b4a79a0b..159f7a035b2 100644 --- a/src/VirtoCommerce.Platform.Core/Bus/InProcessBus.cs +++ b/src/VirtoCommerce.Platform.Core/Bus/InProcessBus.cs @@ -19,6 +19,7 @@ public InProcessBus(ILogger logger) _logger = logger; } + [Obsolete("Use IApplicationBuilder.RegisterEventHandler()", DiagnosticId = "VC0008", UrlFormat = "https://docs.virtocommerce.org/products/products-virto3-versions")] public void RegisterEventHandler(Func handler) where T : IEvent { @@ -35,15 +36,14 @@ public void RegisterEventHandler(Func handler) _handlers.Add(handlerWrapper); } + [Obsolete("Use IApplicationBuilder.RegisterEventHandler()", DiagnosticId = "VC0008", UrlFormat = "https://docs.virtocommerce.org/products/products-virto3-versions")] public void RegisterEventHandler(Func handler) where T : IEvent { -#pragma warning disable VC0008 // Type or member is obsolete RegisterHandler(handler); -#pragma warning restore VC0008 // Type or member is obsolete } - [Obsolete("Use IApplicationBuilder.RegisterEventHandler<>()", DiagnosticId = "VC0008", UrlFormat = "https://docs.virtocommerce.org/products/products-virto3-versions")] + [Obsolete("Use IApplicationBuilder.RegisterEventHandler()", DiagnosticId = "VC0008", UrlFormat = "https://docs.virtocommerce.org/products/products-virto3-versions")] public void RegisterHandler(Func handler) where T : IMessage { @@ -60,6 +60,61 @@ public void RegisterHandler(Func handler) _handlers.Add(handlerWrapper); } + public void RegisterEventHandler(IEventHandler handler) + where T : IEvent + { + var eventType = typeof(T); + var handlerType = handler.GetType(); + + var handlerWrapper = new HandlerWrapper + { + EventType = eventType, + HandlerType = handlerType, + HandlerModuleName = handlerType.Module.Assembly.GetName().Name, + Handler = (message, _) => handler.Handle((T)message), + Logger = _logger + }; + + _handlers.Add(handlerWrapper); + } + + public void RegisterEventHandler(ICancellableEventHandler handler) + where T : IEvent + { + var eventType = typeof(T); + var handlerType = handler.GetType(); + + var handlerWrapper = new HandlerWrapper + { + EventType = eventType, + HandlerType = handlerType, + HandlerModuleName = handlerType.Module.Assembly.GetName().Name, + Handler = (message, cancellationToken) => handler.Handle((T)message, cancellationToken), + Logger = _logger + }; + + _handlers.Add(handlerWrapper); + } + + public void UnregisterEventHandler(Type handlerType = null) + where T : IEvent + { + var eventType = typeof(T); + + var handlersToRemove = _handlers + .Where(x => + x.EventType.IsAssignableFrom(eventType) && + (handlerType is null || x.HandlerType == handlerType)) + .ToList(); + + handlersToRemove.ForEach(x => _handlers.Remove(x)); + } + + public void UnregisterAllEventHandlers() + { + _handlers.Clear(); + } + public async Task Publish(T @event, CancellationToken cancellationToken = default) where T : IEvent { diff --git a/src/VirtoCommerce.Platform.Core/Events/ApplicationBuilderExtensions.cs b/src/VirtoCommerce.Platform.Core/Events/ApplicationBuilderExtensions.cs index 8c1c5152b25..128a9243372 100644 --- a/src/VirtoCommerce.Platform.Core/Events/ApplicationBuilderExtensions.cs +++ b/src/VirtoCommerce.Platform.Core/Events/ApplicationBuilderExtensions.cs @@ -12,10 +12,13 @@ public static IApplicationBuilder RegisterEventHandler(this IA where TEvent : IEvent where THandler : IEventHandler { + var registrar = applicationBuilder.ApplicationServices.GetRequiredService(); var handler = applicationBuilder.ApplicationServices.GetRequiredService(); - return applicationBuilder.RegisterEventHandler(handler.Handle); + registrar.RegisterEventHandler(handler); + return applicationBuilder; } + [Obsolete("Use IApplicationBuilder.RegisterEventHandler()", DiagnosticId = "VC0008", UrlFormat = "https://docs.virtocommerce.org/products/products-virto3-versions")] public static IApplicationBuilder RegisterEventHandler(this IApplicationBuilder applicationBuilder, Func handler) where TEvent : IEvent { @@ -28,10 +31,13 @@ public static IApplicationBuilder RegisterCancellableEventHandler { + var registrar = applicationBuilder.ApplicationServices.GetRequiredService(); var handler = applicationBuilder.ApplicationServices.GetRequiredService(); - return applicationBuilder.RegisterCancellableEventHandler(handler.Handle); + registrar.RegisterEventHandler(handler); + return applicationBuilder; } + [Obsolete("Use IApplicationBuilder.RegisterEventHandler()", DiagnosticId = "VC0008", UrlFormat = "https://docs.virtocommerce.org/products/products-virto3-versions")] public static IApplicationBuilder RegisterCancellableEventHandler(this IApplicationBuilder applicationBuilder, Func handler) where TEvent : IEvent { @@ -39,4 +45,37 @@ public static IApplicationBuilder RegisterCancellableEventHandler(this I registrar.RegisterEventHandler(handler); return applicationBuilder; } + + public static IApplicationBuilder UnregisterEventHandler(this IApplicationBuilder applicationBuilder) + where TEvent : IEvent + where THandler : IEventHandler + { + var registrar = applicationBuilder.ApplicationServices.GetRequiredService(); + registrar.UnregisterEventHandler(typeof(THandler)); + return applicationBuilder; + } + + public static IApplicationBuilder UnregisterCancellableEventHandler(this IApplicationBuilder applicationBuilder) + where TEvent : IEvent + where THandler : ICancellableEventHandler + { + var registrar = applicationBuilder.ApplicationServices.GetRequiredService(); + registrar.UnregisterEventHandler(typeof(THandler)); + return applicationBuilder; + } + + public static IApplicationBuilder UnregisterEventHandlers(this IApplicationBuilder applicationBuilder) + where TEvent : IEvent + { + var registrar = applicationBuilder.ApplicationServices.GetRequiredService(); + registrar.UnregisterEventHandler(); + return applicationBuilder; + } + + public static IApplicationBuilder UnregisterAllEventHandlers(this IApplicationBuilder applicationBuilder) + { + var registrar = applicationBuilder.ApplicationServices.GetRequiredService(); + registrar.UnregisterAllEventHandlers(); + return applicationBuilder; + } } diff --git a/src/VirtoCommerce.Platform.Core/Events/IEventHandlerRegistrar.cs b/src/VirtoCommerce.Platform.Core/Events/IEventHandlerRegistrar.cs index 3ea5a5a24ec..09f38522f28 100644 --- a/src/VirtoCommerce.Platform.Core/Events/IEventHandlerRegistrar.cs +++ b/src/VirtoCommerce.Platform.Core/Events/IEventHandlerRegistrar.cs @@ -6,6 +6,15 @@ namespace VirtoCommerce.Platform.Core.Events; public interface IEventHandlerRegistrar { + [Obsolete("Use IApplicationBuilder.RegisterEventHandler()", DiagnosticId = "VC0008", UrlFormat = "https://docs.virtocommerce.org/products/products-virto3-versions")] void RegisterEventHandler(Func handler) where T : IEvent; + + [Obsolete("Use IApplicationBuilder.RegisterEventHandler()", DiagnosticId = "VC0008", UrlFormat = "https://docs.virtocommerce.org/products/products-virto3-versions")] void RegisterEventHandler(Func handler) where T : IEvent; + + void RegisterEventHandler(IEventHandler handler) where T : IEvent; + void RegisterEventHandler(ICancellableEventHandler handler) where T : IEvent; + + void UnregisterEventHandler(Type handlerType = null) where T : IEvent; + void UnregisterAllEventHandlers(); } diff --git a/src/VirtoCommerce.Platform.Hangfire/Extensions/ApplicationBuilderExtensions.cs b/src/VirtoCommerce.Platform.Hangfire/Extensions/ApplicationBuilderExtensions.cs index c3bcbe3d51f..d1aefdb3d3b 100644 --- a/src/VirtoCommerce.Platform.Hangfire/Extensions/ApplicationBuilderExtensions.cs +++ b/src/VirtoCommerce.Platform.Hangfire/Extensions/ApplicationBuilderExtensions.cs @@ -10,7 +10,6 @@ using Microsoft.Extensions.Options; using VirtoCommerce.Platform.Core.Events; using VirtoCommerce.Platform.Core.Security; -using VirtoCommerce.Platform.Core.Settings; using VirtoCommerce.Platform.Core.Settings.Events; using VirtoCommerce.Platform.Hangfire.Middleware; @@ -61,9 +60,7 @@ public static IApplicationBuilder UseHangfire(this IApplicationBuilder appBuilde var mvcJsonOptions = appBuilder.ApplicationServices.GetService>(); GlobalConfiguration.Configuration.UseSerializerSettings(mvcJsonOptions.Value.SerializerSettings); - var recurringJobManager = appBuilder.ApplicationServices.GetService(); - var settingsManager = appBuilder.ApplicationServices.GetService(); - appBuilder.RegisterEventHandler(message => recurringJobManager.HandleSettingChangeAsync(settingsManager, message)); + appBuilder.RegisterEventHandler(); // Add Hangfire filters/middlewares var userNameResolver = appBuilder.ApplicationServices.CreateScope().ServiceProvider.GetRequiredService(); diff --git a/src/VirtoCommerce.Platform.Hangfire/Extensions/RecurringJobExtensions.cs b/src/VirtoCommerce.Platform.Hangfire/Extensions/RecurringJobExtensions.cs index 7521cd4cc78..77897ebe6fd 100644 --- a/src/VirtoCommerce.Platform.Hangfire/Extensions/RecurringJobExtensions.cs +++ b/src/VirtoCommerce.Platform.Hangfire/Extensions/RecurringJobExtensions.cs @@ -22,6 +22,7 @@ public static class RecurringJobExtensions /// /// /// + [Obsolete("Use IRecurringJobService.WatchJobSetting()", DiagnosticId = "VC0008", UrlFormat = "https://docs.virtocommerce.org/products/products-virto3-versions")] public static void WatchJobSetting(this IRecurringJobManager recurringJobManager, ISettingsManager settingsManager, SettingCronJob settingCronJob) @@ -41,6 +42,7 @@ public static void WatchJobSetting(this IRecurringJobManager recurringJobManager /// /// /// + [Obsolete("Use IRecurringJobService.WatchJobSetting()", DiagnosticId = "VC0008", UrlFormat = "https://docs.virtocommerce.org/products/products-virto3-versions")] public static void WatchJobSetting(this IRecurringJobManager recurringJobManager, ISettingsManager settingsManager, SettingDescriptor enablerSetting, @@ -78,6 +80,7 @@ public static void WatchJobSetting(this IRecurringJobManager recurringJobMana /// /// /// + [Obsolete("Use IRecurringJobService.WatchJobSettingAsync()", DiagnosticId = "VC0008", UrlFormat = "https://docs.virtocommerce.org/products/products-virto3-versions")] public static Task WatchJobSettingAsync(this IRecurringJobManager recurringJobManager, ISettingsManager settingsManager, SettingCronJob settingCronJob) @@ -104,6 +107,7 @@ public static Task WatchJobSettingAsync(this IRecurringJobManager recurringJobMa /// /// /// + [Obsolete("Use RecurringJobService.Handle()", DiagnosticId = "VC0008", UrlFormat = "https://docs.virtocommerce.org/products/products-virto3-versions")] public static Task HandleSettingChangeAsync(this IRecurringJobManager recurringJobManager, ISettingsManager settingsManager, ObjectSettingChangedEvent message) { if (recurringJobManager == null) @@ -117,6 +121,7 @@ public static Task HandleSettingChangeAsync(this IRecurringJobManager recurringJ return recurringJobManager.HandleSettingChangeAsyncIntnl(settingsManager, message); } + [Obsolete("Use RecurringJobService.Handle()", DiagnosticId = "VC0008", UrlFormat = "https://docs.virtocommerce.org/products/products-virto3-versions")] private static async Task HandleSettingChangeAsyncIntnl(this IRecurringJobManager recurringJobManager, ISettingsManager settingsManager, ObjectSettingChangedEvent message) { foreach (var changedEntry in message.ChangedEntries.Where(x => x.EntryState == EntryState.Modified @@ -129,6 +134,7 @@ private static async Task HandleSettingChangeAsyncIntnl(this IRecurringJobManage } } + [Obsolete("Use RecurringJobService.RunOrRemoveJobAsync()", DiagnosticId = "VC0008", UrlFormat = "https://docs.virtocommerce.org/products/products-virto3-versions")] private static async Task RunOrRemoveJobAsync(this IRecurringJobManager recurringJobManager, ISettingsManager settingsManager, SettingCronJob settingCronJob) { var processJobEnableSettingValue = await settingsManager.GetValueAsync(settingCronJob.EnableSetting); diff --git a/src/VirtoCommerce.Platform.Hangfire/Extensions/ServiceCollectionExtensions.cs b/src/VirtoCommerce.Platform.Hangfire/Extensions/ServiceCollectionExtensions.cs index 153e4c9612f..50d3b108ed2 100644 --- a/src/VirtoCommerce.Platform.Hangfire/Extensions/ServiceCollectionExtensions.cs +++ b/src/VirtoCommerce.Platform.Hangfire/Extensions/ServiceCollectionExtensions.cs @@ -39,6 +39,9 @@ public static IGlobalConfiguration AddHangfireStorage(this IGlobalConfiguration public static object AddHangfire(this IServiceCollection services, IConfiguration configuration) { + services.AddSingleton(); + services.AddSingleton(); + var section = configuration.GetSection("VirtoCommerce:Hangfire"); var hangfireOptions = new HangfireOptions(); section.Bind(hangfireOptions); diff --git a/src/VirtoCommerce.Platform.Hangfire/IRecurringJobService.cs b/src/VirtoCommerce.Platform.Hangfire/IRecurringJobService.cs new file mode 100644 index 00000000000..6819bb41e9d --- /dev/null +++ b/src/VirtoCommerce.Platform.Hangfire/IRecurringJobService.cs @@ -0,0 +1,21 @@ +using System; +using System.Linq.Expressions; +using System.Threading.Tasks; +using VirtoCommerce.Platform.Core.Settings; + +namespace VirtoCommerce.Platform.Hangfire; + +public interface IRecurringJobService +{ + void WatchJobSetting( + SettingDescriptor enablerSetting, + SettingDescriptor cronSetting, + Expression> methodCall, + string jobId, + TimeZoneInfo timeZoneInfo, + string queue); + + void WatchJobSetting(SettingCronJob settingCronJob); + + Task WatchJobSettingAsync(SettingCronJob settingCronJob); +} diff --git a/src/VirtoCommerce.Platform.Hangfire/RecurringJobService.cs b/src/VirtoCommerce.Platform.Hangfire/RecurringJobService.cs new file mode 100644 index 00000000000..4277a49f39e --- /dev/null +++ b/src/VirtoCommerce.Platform.Hangfire/RecurringJobService.cs @@ -0,0 +1,108 @@ +// This service is functionally identical to the obsolete RecurringJobExtensions +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Linq.Expressions; +using System.Threading.Tasks; +using Hangfire; +using VirtoCommerce.Platform.Core.Common; +using VirtoCommerce.Platform.Core.Events; +using VirtoCommerce.Platform.Core.Settings; +using VirtoCommerce.Platform.Core.Settings.Events; +using VirtoCommerce.Platform.Hangfire.Extensions; + +namespace VirtoCommerce.Platform.Hangfire; + +public class RecurringJobService : IRecurringJobService, IEventHandler +{ + // Key is the observed setting name, value is the object of setting job + private static readonly ConcurrentDictionary _observedSettingsDict = new(); + + private readonly IRecurringJobManager _recurringJobManager; + private readonly ISettingsManager _settingsManager; + + public RecurringJobService(IRecurringJobManager recurringJobManager, ISettingsManager settingsManager) + { + _recurringJobManager = recurringJobManager; + _settingsManager = settingsManager; + } + + public void WatchJobSetting( + SettingDescriptor enablerSetting, + SettingDescriptor cronSetting, + Expression> methodCall, + string jobId, + TimeZoneInfo timeZoneInfo, + string queue) + { + var settingCronJob = new SettingCronJobBuilder(new SettingCronJob()) + .SetEnablerSetting(enablerSetting) + .SetCronSetting(cronSetting) + .SetJobId(jobId) + .SetQueueName(queue) + .SetTimeZoneInfo(timeZoneInfo) + .ToJob(methodCall) + .Build(); + + WatchJobSetting(settingCronJob); + } + + /// + /// Use SettingCronJobBuilder for creating SettingCronJob + /// + public void WatchJobSetting(SettingCronJob settingCronJob) + { + WatchJobSettingAsync(settingCronJob).GetAwaiter().GetResult(); + } + + /// + /// Use SettingCronJobBuilder for creating SettingCronJob + /// + public Task WatchJobSettingAsync(SettingCronJob settingCronJob) + { + _observedSettingsDict.AddOrUpdate(settingCronJob.EnableSetting.Name, settingCronJob, (_, _) => settingCronJob); + _observedSettingsDict.AddOrUpdate(settingCronJob.CronSetting.Name, settingCronJob, (_, _) => settingCronJob); + + return RunOrRemoveJobAsync(settingCronJob); + } + + public async Task Handle(ObjectSettingChangedEvent message) + { + foreach (var settingName in message.ChangedEntries + .Where(x => x.EntryState is EntryState.Modified or EntryState.Added) + .Select(x => x.NewEntry.Name)) + { + if (_observedSettingsDict.TryGetValue(settingName, out var settingCronJob)) + { + await RunOrRemoveJobAsync(settingCronJob); + } + } + + // Temporary solution for backward compatibility +#pragma warning disable VC0008 // Type or member is obsolete + await _recurringJobManager.HandleSettingChangeAsync(_settingsManager, message); +#pragma warning restore VC0008 // Type or member is obsolete + } + + private async Task RunOrRemoveJobAsync(SettingCronJob settingCronJob) + { + var processJobEnableSettingValue = await _settingsManager.GetValueAsync(settingCronJob.EnableSetting); + var processJobEnable = settingCronJob.EnabledEvaluator(processJobEnableSettingValue); + + if (processJobEnable) + { + var cronExpression = await _settingsManager.GetValueAsync(settingCronJob.CronSetting); + + _recurringJobManager.AddOrUpdate( + settingCronJob.RecurringJobId, + settingCronJob.Job, + cronExpression, + settingCronJob.TimeZone, + settingCronJob.Queue); + } + else + { + _recurringJobManager.RemoveIfExists(settingCronJob.RecurringJobId); + } + } +} diff --git a/src/VirtoCommerce.Platform.Web/Security/ApplicationBuilderExtensions.cs b/src/VirtoCommerce.Platform.Web/Security/ApplicationBuilderExtensions.cs index 5c609814658..bdb44d58e88 100644 --- a/src/VirtoCommerce.Platform.Web/Security/ApplicationBuilderExtensions.cs +++ b/src/VirtoCommerce.Platform.Web/Security/ApplicationBuilderExtensions.cs @@ -7,9 +7,7 @@ using VirtoCommerce.Platform.Core.Events; using VirtoCommerce.Platform.Core.Security; using VirtoCommerce.Platform.Core.Security.Events; -using VirtoCommerce.Platform.Core.Settings; using VirtoCommerce.Platform.Hangfire; -using VirtoCommerce.Platform.Hangfire.Extensions; using VirtoCommerce.Platform.Security.Handlers; using VirtoCommerce.Platform.Web.Security.BackgroundJobs; @@ -50,11 +48,9 @@ public static IApplicationBuilder UseSecurityHandlers(this IApplicationBuilder a /// public static IApplicationBuilder UsePruneExpiredTokensJob(this IApplicationBuilder appBuilder) { - var recurringJobManager = appBuilder.ApplicationServices.GetService(); - var settingsManager = appBuilder.ApplicationServices.GetService(); + var recurringJobService = appBuilder.ApplicationServices.GetService(); - recurringJobManager.WatchJobSetting( - settingsManager, + recurringJobService.WatchJobSetting( new SettingCronJobBuilder() .SetEnablerSetting(PlatformConstants.Settings.Security.EnablePruneExpiredTokensJob) .SetCronSetting(PlatformConstants.Settings.Security.CronPruneExpiredTokensJob) diff --git a/tests/VirtoCommerce.Platform.Tests/Events/EventHandlerTests.cs b/tests/VirtoCommerce.Platform.Tests/Events/EventHandlerTests.cs index 2fc7bf3a0eb..5fb6e70f136 100644 --- a/tests/VirtoCommerce.Platform.Tests/Events/EventHandlerTests.cs +++ b/tests/VirtoCommerce.Platform.Tests/Events/EventHandlerTests.cs @@ -1,6 +1,8 @@ +using System; using System.Collections.Generic; using System.Threading.Tasks; using FluentAssertions; +using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Logging; using Moq; using VirtoCommerce.Platform.Core.Bus; @@ -16,31 +18,91 @@ public class EventHandlerTests [Fact] public async Task HandleMultipleEventTypesWithSingleRegistration() { - var bus = new InProcessBus(new Mock>().Object); + var handler = new Handler(); + + var serviceProviderMock = new Mock(); + serviceProviderMock.Setup(x => x.GetService(typeof(Handler))).Returns(handler); - var domainHandlerEvents = new List(); - var userLoginHandlerEvents = new List(); - var userChangedHandlerEvents = new List(); + var (applicationBuilder, publisher) = GetServices(serviceProviderMock); // This handler should handle all event types - bus.RegisterEventHandler(message => Handle(message, domainHandlerEvents)); + applicationBuilder.RegisterEventHandler(); // These handlers should handle only specific event types - bus.RegisterEventHandler(message => Handle(message, userLoginHandlerEvents)); - bus.RegisterEventHandler(message => Handle(message, userChangedHandlerEvents)); + applicationBuilder.RegisterEventHandler(); + applicationBuilder.RegisterEventHandler(); + + await publisher.Publish(new UserLoginEvent(user: null)); + await publisher.Publish(new UserChangedEvent(changedEntries: null)); + + handler.DomainEvents.Should().BeEquivalentTo([nameof(UserLoginEvent), nameof(UserChangedEvent)]); + handler.UserLoginEvents.Should().BeEquivalentTo([nameof(UserLoginEvent)]); + handler.UserChangedEvents.Should().BeEquivalentTo([nameof(UserChangedEvent)]); + } + + [Fact] + public async Task UnregisterEventHandler() + { + var handler1 = new Handler(); + var handler2 = new Handler2(); + + var serviceProviderMock = new Mock(); + serviceProviderMock.Setup(x => x.GetService(typeof(Handler))).Returns(handler1); + serviceProviderMock.Setup(x => x.GetService(typeof(Handler2))).Returns(handler2); + + var (applicationBuilder, publisher) = GetServices(serviceProviderMock); + + applicationBuilder.RegisterEventHandler(); + applicationBuilder.RegisterEventHandler(); + + applicationBuilder.UnregisterEventHandler(); - await bus.Publish(new UserLoginEvent(user: null)); - await bus.Publish(new UserChangedEvent(changedEntries: null)); + await publisher.Publish(new UserLoginEvent(user: null)); - domainHandlerEvents.Should().BeEquivalentTo([nameof(UserLoginEvent), nameof(UserChangedEvent)]); - userLoginHandlerEvents.Should().BeEquivalentTo([nameof(UserLoginEvent)]); - userChangedHandlerEvents.Should().BeEquivalentTo([nameof(UserChangedEvent)]); + handler1.UserLoginEvents.Should().BeEmpty(); + handler2.UserLoginEvents.Should().BeEquivalentTo([nameof(UserLoginEvent)]); } - private static Task Handle(T message, List events) where T : IEvent + + private static (IApplicationBuilder, IEventPublisher) GetServices(Mock serviceProviderMock = null) + { + serviceProviderMock ??= new Mock(); + + var bus = new InProcessBus(new Mock>().Object); + serviceProviderMock.Setup(x => x.GetService(typeof(IEventHandlerRegistrar))).Returns(bus); + serviceProviderMock.Setup(x => x.GetService(typeof(IEventPublisher))).Returns(bus); + + var applicationBuilderMock = new Mock(); + + applicationBuilderMock.Setup(x => x.ApplicationServices).Returns(serviceProviderMock.Object); + + return (applicationBuilderMock.Object, bus); + } + + private class Handler2 : Handler; + + private class Handler : IEventHandler, IEventHandler, IEventHandler { - events.Add(message.GetType().Name); + public readonly List DomainEvents = []; + public readonly List UserLoginEvents = []; + public readonly List UserChangedEvents = []; + + public Task Handle(DomainEvent message) + { + DomainEvents.Add(message.GetType().Name); + return Task.CompletedTask; + } + + public Task Handle(UserLoginEvent message) + { + UserLoginEvents.Add(message.GetType().Name); + return Task.CompletedTask; + } - return Task.CompletedTask; + public Task Handle(UserChangedEvent message) + { + UserChangedEvents.Add(message.GetType().Name); + return Task.CompletedTask; + } } } diff --git a/tests/VirtoCommerce.Platform.Tests/UnitTests/JobSettingsWatcherUnitTests.cs b/tests/VirtoCommerce.Platform.Tests/UnitTests/JobSettingsWatcherUnitTests.cs index 03ad2ef5b02..98ca473ae40 100644 --- a/tests/VirtoCommerce.Platform.Tests/UnitTests/JobSettingsWatcherUnitTests.cs +++ b/tests/VirtoCommerce.Platform.Tests/UnitTests/JobSettingsWatcherUnitTests.cs @@ -6,7 +6,6 @@ using Moq; using VirtoCommerce.Platform.Core.Settings; using VirtoCommerce.Platform.Hangfire; -using VirtoCommerce.Platform.Hangfire.Extensions; using Xunit; namespace VirtoCommerce.Platform.Tests.UnitTests @@ -23,14 +22,14 @@ public JobSettingsWatcherUnitTests() } [Fact] - public void WatchJobSetting_WithBuilder_AddReccuringJob() + public void WatchJobSetting_WithBuilder_AddRecurringJob() { _settingsManagerMock.Setup(x => x.GetObjectSettingAsync("enablername", null, null)).ReturnsAsync(new ObjectSettingEntry()); _settingsManagerMock.Setup(x => x.GetObjectSettingAsync("cronname", null, null)).ReturnsAsync(new ObjectSettingEntry()); + var recurringJobService = new RecurringJobService(_recurringJobManagerMock.Object, _settingsManagerMock.Object); //Act - RecurringJobExtensions.WatchJobSettingAsync(_recurringJobManagerMock.Object, - _settingsManagerMock.Object, + recurringJobService.WatchJobSettingAsync( new SettingCronJobBuilder() .SetEnablerSetting(new SettingDescriptor { DefaultValue = true, Name = "enablername" }) .SetCronSetting(new SettingDescriptor { DefaultValue = "* * * *", Name = "cronname" }) @@ -46,15 +45,14 @@ public void WatchJobSetting_WithBuilder_AddReccuringJob() } [Fact] - public void WatchJobSetting_WithoutBuilder_AddReccuringJob() + public void WatchJobSetting_WithoutBuilder_AddRecurringJob() { _settingsManagerMock.Setup(x => x.GetObjectSettingAsync("enablername", null, null)).ReturnsAsync(new ObjectSettingEntry()); _settingsManagerMock.Setup(x => x.GetObjectSettingAsync("cronname", null, null)).ReturnsAsync(new ObjectSettingEntry()); + var recurringJobService = new RecurringJobService(_recurringJobManagerMock.Object, _settingsManagerMock.Object); //Act - RecurringJobExtensions.WatchJobSetting( - _recurringJobManagerMock.Object, - _settingsManagerMock.Object, + recurringJobService.WatchJobSetting( new SettingDescriptor { DefaultValue = true, Name = "enablername" }, new SettingDescriptor { DefaultValue = "* * * *", Name = "cronname" }, x => x.Process(),