elsa-core/test/integration/Elsa.ServiceBus.IntegrationTests/Scenarios/ServiceBus/ServiceBusTests.cs
jdevillard 9175322774
Add a more generic UIHandler to customize how inputAttributes can be handle by UI (#4688)
* add a more generic UIHandler to customize how inputAttributes can be handle by the ui

* Add IPropertyUIHandlerResolver and update PropertyUIHandlerResolver

Introduced a new interface, IPropertyUIHandlerResolver, to resolve UI options for a property. Refactored PropertyUIHandlerResolver to implement this interface and removed the unnecessary partial class structure. Also, cleaned up some unnecessary usings in various files for better code organization.

* Refactor variable name and description in InputDescriptor

The 'uISpecifications' variable in the InputDescriptor model is renamed to 'uiSpecifications' for better readability. Additionally, the associated comment was revised to explain that the dictionary is used by the UI.

* "Refactor codebase for improved organization and cleaner architecture"

The codebase has been significantly refactored, moving several classes to more appropriate namespaces for improved organization and cleaner architecture. This includes shifting UI hint handlers, activities, and memory-related components, amongst others. The changes should improve code readability and maintainability, but as this is a broad refactoring effort, thorough regression testing is advised.

* Add CheckList UIHint with associated handler and provider

This update introduces a new UIHint called CheckList to the Elsa.Workflows.Core. This includes the necessary handler and provider classes. The handler is registered in the WorkflowsFeature.cs, and the CheckList UIHint key has been added to the InputUIHints.cs. Various associated files have been created in both the Elsa.Api.Client and Elsa.Workflows.Core project to support this new UIHint.

---------

Co-authored-by: Jérémie DEVILLARD <jdevillard@users.noreply.github.com>
Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
2023-12-26 18:56:29 +01:00

374 lines
16 KiB
C#

using Azure.Messaging.ServiceBus;
using Elsa.AzureServiceBus.Contracts;
using Elsa.AzureServiceBus.Services;
using Elsa.Testing.Shared;
using Microsoft.Extensions.DependencyInjection;
using Xunit.Abstractions;
using Elsa.Mediator.HostedServices;
using Elsa.Mediator.Options;
using Elsa.ServiceBus.IntegrationTests.Contracts;
using Elsa.ServiceBus.IntegrationTests.Helpers;
using Elsa.ServiceBus.IntegrationTests.Scenarios.Workflows;
using Elsa.Workflows;
using Microsoft.Extensions.Options;
using Elsa.Workflows.Runtime.Options;
using Elsa.Workflows.Runtime.Contracts;
using NSubstitute;
namespace Elsa.ServiceBus.IntegrationTests.Scenarios.ServiceBus;
public class ServiceBusTest : IDisposable
{
private readonly IServiceProvider _services;
private readonly CapturingTextWriter _capturingTextWriter = new();
private readonly ServiceBusClient _serviceBusClient = Substitute.For<ServiceBusClient>();
private readonly IWorkerManager _worker;
private readonly BackgroundCommandSenderHostedService _hosted;
private readonly BackgroundEventPublisherHostedService _backgroundEventService;
private readonly ITestResetEventManager _resetEventManager = new TestResetEventManager();
private readonly ITestOutputHelper _testOutputHelper;
private readonly IServiceBusProcessorManager _sbProcessorManager;
public ServiceBusTest(ITestOutputHelper testOutputHelper)
{
_services = new TestApplicationBuilder(testOutputHelper)
.WithCapturingTextWriter(_capturingTextWriter)
.AddWorkflow<ReceiveOneMessageWorkflow>()
.AddWorkflow<ReceiveMessageWorkflow>()
.AddWorkflow<SendOneMessageWorkflow>()
.AddWorkflow<SendOneMessageWithCorrelationIdWorkflow>()
.ConfigureServices(services =>
{
services
.AddSingleton(_serviceBusClient)
.AddSingleton<IServiceBusProcessorManager, ServiceBusProcessorManager>()
.AddSingleton<IWorkerManager, WorkerManager>()
.AddSingleton(_resetEventManager)
.AddSingleton(sp =>
{
var options = sp.GetRequiredService<IOptions<MediatorOptions>>().Value;
return ActivatorUtilities.CreateInstance<BackgroundCommandSenderHostedService>(sp, options.CommandWorkerCount);
})
.AddSingleton(sp =>
{
var options = sp.GetRequiredService<IOptions<MediatorOptions>>().Value;
return ActivatorUtilities.CreateInstance<BackgroundEventPublisherHostedService>(sp, options.NotificationWorkerCount);
})
;
})
.Build();
_worker = _services.GetRequiredService<IWorkerManager>();
_hosted = _services.GetRequiredService<BackgroundCommandSenderHostedService>();
_backgroundEventService = _services.GetRequiredService<BackgroundEventPublisherHostedService>();
_sbProcessorManager = _services.GetRequiredService<IServiceBusProcessorManager>();
_testOutputHelper = testOutputHelper;
}
[Fact(DisplayName = "2 Receive - Sending 1 message - Should Block")]
public async Task Receive_1_Message_Should_Block_If_One_Receive()
{
_sbProcessorManager.Init("topicName", "subscriptionName");
_sbProcessorManager.Init("topicName1", "subscription1");
// Init waitEvent:
_resetEventManager.Init("receive1");
_resetEventManager.Init("receive2");
// Init BackGround
await InitRegistryAndBackGroundServiceWorkerAsync();
// Start Workflow
const string workflowDefinitionId = nameof(ReceiveMessageWorkflow);
var startWorkflowOptions = new StartWorkflowRuntimeOptions
{
VersionOptions = Common.Models.VersionOptions.Published
};
var workflowRuntime = _services.GetRequiredService<IWorkflowRuntime>();
var workflowState = await workflowRuntime.StartWorkflowAsync(workflowDefinitionId, startWorkflowOptions);
/*
* Workflow don't receive any message so it should be
* Running
* Suspended
*/
Assert.Equal(WorkflowStatus.Running, workflowState.Status);
Assert.Equal(WorkflowSubStatus.Suspended, workflowState.SubStatus);
// Start Worker to send Message on topicName/subscriptionName
await _worker.StartWorkerAsync("topicName", "subscriptionName");
await _sbProcessorManager
.Get("topicName", "subscriptionName")
.SendMessage<dynamic>(new { hello = "world" }, null!);
// Wait for receiving first message
var wait1 = _resetEventManager.Get("receive1").WaitOne(TimeSpan.FromSeconds(5));
_testOutputHelper.WriteLine($"wait1 : {wait1}");
// Wait for receiving second message
var wait2 = _resetEventManager.Get("receive2").WaitOne(TimeSpan.FromSeconds(5));
_testOutputHelper.WriteLine($"wait2 : {wait2}");
await Task.Delay(500); // Todo find how to remove delay
var lastWorkflowState = await workflowRuntime.ExportWorkflowStateAsync(workflowState.WorkflowInstanceId);
/*
* We don't send 2 messages so Workflow must be
* Running
* Suspended
*
* with a timeout on Wait for 2nd Message. (False ResetEvent)
*/
Assert.NotNull(lastWorkflowState);
Assert.Equal(WorkflowStatus.Running, lastWorkflowState.Status);
Assert.Equal(WorkflowSubStatus.Suspended, lastWorkflowState.SubStatus);
Assert.True(wait1);
Assert.False(wait2);
}
private async Task InitRegistryAndBackGroundServiceWorkerAsync()
{
// Init Registries to use StartWorkflow
await _services.PopulateRegistriesAsync();
// Start background services for CommandHandler
await _hosted.StartAsync(CancellationToken.None);
await _backgroundEventService.StartAsync(CancellationToken.None);
}
[Fact(DisplayName = "1 Receive - Sending 1 message - Should Finished")]
public async Task Receive_1_Message_Should_Finish_With_One_Receive()
{
_sbProcessorManager.Init("topicName", "subscriptionName");
// Init waitEvent:
_resetEventManager.Init("receive1");
// Init BackGround
await InitRegistryAndBackGroundServiceWorkerAsync();
// Start Workflow
const string workflowDefinitionId = nameof(ReceiveOneMessageWorkflow);
var startWorkflowOptions = new StartWorkflowRuntimeOptions { VersionOptions = Common.Models.VersionOptions.Published };
var workflowRuntime = _services.GetRequiredService<IWorkflowRuntime>();
var workflowState = await workflowRuntime.StartWorkflowAsync(workflowDefinitionId, startWorkflowOptions);
/*
* Workflow don't receive any message so it should be
* Running
* Suspended
*/
Assert.Equal(WorkflowStatus.Running, workflowState.Status);
Assert.Equal(WorkflowSubStatus.Suspended, workflowState.SubStatus);
//Start Worker to send Message on topicName/subscriptionName
await _worker.StartWorkerAsync("topicName", "subscriptionName");
await _sbProcessorManager
.Get("topicName", "subscriptionName")
.SendMessage<dynamic>(new { hello = "world" }, null!);
//Wait for receiving first message
var wait1 = _resetEventManager.Get("receive1").WaitOne(TimeSpan.FromSeconds(5));
_testOutputHelper.WriteLine($"wait1 : {wait1}");
await Task.Delay(500); //Todo find how to remove delay
var lastWorkflowState = await workflowRuntime.ExportWorkflowStateAsync(workflowState.WorkflowInstanceId);
/*
* We sent 1 message so Workflow must be
* Finished
* Finished
*
* with no timeout for the first message. (True ResetEvent)
*/
Assert.NotNull(lastWorkflowState);
Assert.Equal(WorkflowStatus.Finished, lastWorkflowState.Status);
Assert.Equal(WorkflowSubStatus.Finished, lastWorkflowState.SubStatus);
Assert.True(wait1);
}
[Fact(DisplayName = "1 Send - 1 Receive - Should Finished - without race condition")]
public async Task Send_1_Message_And_Receive_Response()
{
_sbProcessorManager.Init("topicName", "subscriptionName");
// Init waitEvent:
_resetEventManager.Init("receive1");
// Init Background
await InitRegistryAndBackGroundServiceWorkerAsync();
var senderMock = Substitute.For<ServiceBusSender>();
_serviceBusClient
.CreateSender(Arg.Any<string>())
.Returns(senderMock);
senderMock
.SendMessageAsync(Arg.Any<ServiceBusMessage>(), Arg.Any<CancellationToken>())
.ReturnsForAnyArgs(async (callback) =>
{
var sb = callback.Arg<ServiceBusMessage>();
var c = callback.Arg<CancellationToken>();
_testOutputHelper.WriteLine("Sending Message from activity");
await _worker.StartWorkerAsync("topicName", "subscriptionName", c);
await _sbProcessorManager
.Get("topicName", "subscriptionName")
.SendMessage<dynamic>(new { hello = "world" }, null!);
});
// Start Workflow
const string workflowDefinitionId = nameof(SendOneMessageWorkflow);
var startWorkflowOptions = new StartWorkflowRuntimeOptions { VersionOptions = Common.Models.VersionOptions.Published };
var workflowRuntime = _services.GetRequiredService<IWorkflowRuntime>();
var workflowState = await workflowRuntime.StartWorkflowAsync(workflowDefinitionId, startWorkflowOptions);
/*
* Workflow don't receive any message so it should be
* Running
* Suspended
*/
Assert.Equal(WorkflowStatus.Running, workflowState.Status);
Assert.Equal(WorkflowSubStatus.Suspended, workflowState.SubStatus);
//Wait for receiving first message
var wait1 = _resetEventManager.Get("receive1").WaitOne(TimeSpan.FromSeconds(5));
_testOutputHelper.WriteLine($"wait1 : {wait1}");
await Task.Delay(500); //Todo find how to remove delay
var lastWorkflowState = await workflowRuntime.ExportWorkflowStateAsync(workflowState.WorkflowInstanceId);
/*
* We sent 1 message so Workflow must be
* Finished
* Finished
*
* with no timeout for the first message. (True ResetEvent)
*/
Assert.NotNull(lastWorkflowState);
Assert.Equal(WorkflowStatus.Finished, lastWorkflowState.Status);
Assert.Equal(WorkflowSubStatus.Finished, lastWorkflowState.SubStatus);
Assert.True(wait1);
}
[Fact(DisplayName = "1 Send - 1 Receive - with correlationId - Should Finished - without race condition")]
public async Task Send_1_Message_And_Correlate_Receive_Response()
{
var correlationId = "EEE3D9CC-2279-4CE5-8F4F-FC6C65BF8814";
_sbProcessorManager.Init("topicName", "subscriptionName");
//Init waitEvent :
_resetEventManager.Init("receive1");
//Init BackGround
await InitRegistryAndBackGroundServiceWorkerAsync();
var senderMock = Substitute.For<ServiceBusSender>();
_serviceBusClient
.CreateSender(Arg.Any<string>())
.Returns(senderMock);
senderMock
.SendMessageAsync(Arg.Any<ServiceBusMessage>(), Arg.Any<CancellationToken>())
.ReturnsForAnyArgs(async (callback) =>
{
var sb = callback.Arg<ServiceBusMessage>();
var c = callback.Arg<CancellationToken>();
_testOutputHelper.WriteLine("Sending Message from activity");
await _worker.StartWorkerAsync("topicName", "subscriptionName");
await _sbProcessorManager
.Get("topicName", "subscriptionName")
.SendMessage<dynamic>(new { hello = "world" }, correlationId);
});
//Start Workflow
var workflowDefinitionId = nameof(SendOneMessageWithCorrelationIdWorkflow);
var startWorkflowOptions = new StartWorkflowRuntimeOptions { VersionOptions = Common.Models.VersionOptions.Published };
var workflowRuntime = _services.GetRequiredService<IWorkflowRuntime>();
var workflowState = await workflowRuntime.StartWorkflowAsync(workflowDefinitionId, startWorkflowOptions);
/*
* Workflow don't receive any message so it should be
* Running
* Suspended
*/
Assert.Equal(WorkflowStatus.Running, workflowState.Status);
Assert.Equal(WorkflowSubStatus.Suspended, workflowState.SubStatus);
// Wait for receiving first message.
var wait1 = _resetEventManager.Get("receive1").WaitOne(TimeSpan.FromSeconds(5));
_testOutputHelper.WriteLine($"wait1 : {wait1}");
await Task.Delay(500); // Todo find how to remove delay
var lastWorkflowState = await workflowRuntime.ExportWorkflowStateAsync(workflowState.WorkflowInstanceId);
/*
* We sent 1 message so Workflow must be
* Finished
* Finished
*
* with no timeout for the first message. (True ResetEvent)
*/
Assert.NotNull(lastWorkflowState);
Assert.Equal(WorkflowStatus.Finished, lastWorkflowState.Status);
Assert.Equal(WorkflowSubStatus.Finished, lastWorkflowState.SubStatus);
Assert.True(wait1);
}
[Fact(DisplayName = "1 Receive - Listening from other topic - Should not trigger")]
public async Task Receive_1_Message_Should_Not_Trigger()
{
_sbProcessorManager.Init("topicName1", "subscriptionName1");
// Init waitEvent:
_resetEventManager.Init("receive1");
_resetEventManager.Init("receive2");
// Init backGround
await InitRegistryAndBackGroundServiceWorkerAsync();
// Start Workflow
var workflowDefinitionId = nameof(ReceiveMessageWorkflow);
var startWorkflowOptions = new StartWorkflowRuntimeOptions { VersionOptions = Common.Models.VersionOptions.Published };
var workflowRuntime = _services.GetRequiredService<IWorkflowRuntime>();
var workflowState = await workflowRuntime.StartWorkflowAsync(workflowDefinitionId, startWorkflowOptions);
/*
* Workflow doesn't receive any message so it should be
* Running
* Suspended
*/
Assert.Equal(WorkflowStatus.Running, workflowState.Status);
Assert.Equal(WorkflowSubStatus.Suspended, workflowState.SubStatus);
//Start Worker to send Message on topicName/suscriptionName
await _worker.StartWorkerAsync("topicName1", "subscriptionName1");
await _sbProcessorManager
.Get("topicName1", "subscriptionName1")
.SendMessage<dynamic>(new { hello = "world" }, null!);
//Wait for receiving first message
var wait1 = _resetEventManager.Get("receive1").WaitOne(TimeSpan.FromSeconds(5));
_testOutputHelper.WriteLine($"wait1 : {wait1}");
var lastWorkflowState = await workflowRuntime.ExportWorkflowStateAsync(workflowState.WorkflowInstanceId);
/*
* We don't send 2 messages so Workflow must be
* Running
* Suspended
*
* with a timeout on Wait for 2nd Message. (False ResetEvent)
*/
Assert.NotNull(lastWorkflowState);
Assert.Equal(WorkflowStatus.Running, lastWorkflowState.Status);
Assert.Equal(WorkflowSubStatus.Suspended, lastWorkflowState.SubStatus);
Assert.False(wait1);
}
void IDisposable.Dispose()
{
_hosted.StopAsync(new CancellationToken()).GetAwaiter().GetResult();
}
}