Add DispatchWorkflow tests with new workflow definitions (#7035)
* Add DispatchWorkflow tests with new workflow definitions - Introduced multiple workflow definitions with varied scenarios including input handling, correlation IDs, and fault handling. - Enhanced `DispatchWorkflowsTests` with comprehensive test cases to validate `DispatchWorkflow` behavior under different configurations. - Updated existing workflows and tests for improved structure, readability, and accuracy. - Refactored and renamed related workflows for consistency across test suites. * Update test/component/Elsa.Workflows.ComponentTests/Scenarios/Activities/DispatchWorkflows/DispatchWorkflowsTests.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Refactor DispatchWorkflowsTests for readability and maintainability - Replaced hardcoded constants with named variables for improved clarity. - Enhanced assertions using utility methods like `Assert.Single` for cleaner code. - Updated WriteLine activity tests to handle null values reliably. - Introduced timeout handling for child workflow execution. * Update GUID length validation in JintJavaScriptFunctionBehaviorTests - Adjusted `shortGuid` length assertion to accommodate a range of 19-22 characters instead of 20-22. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
parent
52e287f7ad
commit
b32a067d03
|
|
@ -78,7 +78,7 @@ public class BulkDispatchWorkflowsTests : AppComponentTest
|
|||
[Fact(DisplayName = "BulkDispatchWorkflows should execute ChildFaulted ports")]
|
||||
public async Task BulkDispatchWithChildPorts_ShouldExecuteChildFaultedPortForFaultedWorkflows()
|
||||
{
|
||||
var result = await RunWorkflowAsync(BulkDispatchWithChildPortsWorkflow.DefinitionId);
|
||||
var result = await RunWorkflowAsync(BulkDispatchWithBulkChildPortsWorkflow.DefinitionId);
|
||||
AssertWorkflowFinished(result);
|
||||
|
||||
var faultedCount = await GetWorkflowVariableAsync<int>(result, "FaultedCount");
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ using Elsa.Workflows.Activities;
|
|||
|
||||
namespace Elsa.Workflows.ComponentTests.Scenarios.Activities.BulkDispatchWorkflows.Workflows;
|
||||
|
||||
public class BulkDispatchWithChildPortsWorkflow : WorkflowBase
|
||||
public class BulkDispatchWithBulkChildPortsWorkflow : WorkflowBase
|
||||
{
|
||||
public static readonly string DefinitionId = Guid.NewGuid().ToString();
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ public class BulkDispatchWithChildPortsWorkflow : WorkflowBase
|
|||
|
||||
builder.Root = new Runtime.Activities.BulkDispatchWorkflows
|
||||
{
|
||||
WorkflowDefinitionId = new(FaultingChildWorkflow.DefinitionId),
|
||||
WorkflowDefinitionId = new(FaultingBulkChildWorkflow.DefinitionId),
|
||||
Items = new(new object[] { 1, 2, 3 }),
|
||||
WaitForCompletion = new(true),
|
||||
ChildCompleted = new Sequence
|
||||
|
|
@ -5,7 +5,7 @@ using JetBrains.Annotations;
|
|||
namespace Elsa.Workflows.ComponentTests.Scenarios.Activities.BulkDispatchWorkflows.Workflows;
|
||||
|
||||
[UsedImplicitly]
|
||||
public class FaultingChildWorkflow : WorkflowBase
|
||||
public class FaultingBulkChildWorkflow : WorkflowBase
|
||||
{
|
||||
public static readonly string DefinitionId = Guid.NewGuid().ToString();
|
||||
|
||||
|
|
@ -1,36 +1,140 @@
|
|||
using Elsa.Common.Models;
|
||||
using Elsa.Common.Models;
|
||||
using Elsa.Testing.Shared;
|
||||
using Elsa.Testing.Shared.Models;
|
||||
using Elsa.Testing.Shared.Services;
|
||||
using Elsa.Workflows.Activities;
|
||||
using Elsa.Workflows.ComponentTests.Abstractions;
|
||||
using Elsa.Workflows.ComponentTests.Fixtures;
|
||||
using Elsa.Workflows.ComponentTests.Scenarios.Activities.DispatchWorkflows.Workflows;
|
||||
using Elsa.Workflows.Management;
|
||||
using Elsa.Workflows.Models;
|
||||
using Elsa.Workflows.Runtime;
|
||||
using Elsa.Workflows.Runtime.Messages;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Elsa.Workflows.ComponentTests.Scenarios.DispatchWorkflows;
|
||||
namespace Elsa.Workflows.ComponentTests.Scenarios.Activities.DispatchWorkflows;
|
||||
|
||||
public class DispatchWorkflowsTests : AppComponentTest
|
||||
{
|
||||
private readonly SignalManager _signalManager;
|
||||
private readonly IWorkflowRuntime _workflowRuntime;
|
||||
private readonly AsyncWorkflowRunner _workflowRunner;
|
||||
private const int ExpectedWriteLineCount = 2; // One from parent, one from child
|
||||
private const int ChildWorkflowTimeoutSeconds = 30;
|
||||
|
||||
public DispatchWorkflowsTests(App app) : base(app)
|
||||
{
|
||||
_workflowRuntime = Scope.ServiceProvider.GetRequiredService<IWorkflowRuntime>();
|
||||
_signalManager = Scope.ServiceProvider.GetRequiredService<SignalManager>();
|
||||
_workflowRunner = Scope.ServiceProvider.GetRequiredService<AsyncWorkflowRunner>();
|
||||
}
|
||||
|
||||
[Fact (Skip = "This test is flaky and needs to be fixed.")]
|
||||
[Fact(DisplayName = "DispatchWorkflow should wait for child workflow to complete")]
|
||||
public async Task DispatchAndWaitWorkflow_ShouldWaitForChildWorkflowToComplete()
|
||||
{
|
||||
var workflowClient = await _workflowRuntime.CreateClientAsync();
|
||||
await workflowClient.CreateInstanceAsync(new()
|
||||
{
|
||||
WorkflowDefinitionHandle = WorkflowDefinitionHandle.ByDefinitionId(DispatchAndWaitWorkflow.DefinitionId, VersionOptions.Published)
|
||||
});
|
||||
await workflowClient.RunInstanceAsync(RunWorkflowInstanceRequest.Empty);
|
||||
await _signalManager.WaitAsync("Completed");
|
||||
var result = await RunWorkflowAsync(DispatchAndWaitWorkflow.DefinitionId);
|
||||
|
||||
AssertWorkflowFinished(result);
|
||||
var writeLineExecutionRecords = result.ActivityExecutionRecords.Where(x => x.ActivityType == "Elsa.WriteLine").ToList();
|
||||
Assert.Equal(ExpectedWriteLineCount, writeLineExecutionRecords.Count);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "DispatchWorkflow should dispatch and not wait when WaitForCompletion is false")]
|
||||
public async Task DispatchFireAndForget_ShouldNotWaitForChildWorkflow()
|
||||
{
|
||||
// Run the main workflow and wait for child workflow to complete
|
||||
var (result, completedChildWorkflows) = await RunWorkflowAndWaitForChildWorkflowAsync(
|
||||
DispatchFireAndForgetWorkflow.DefinitionId,
|
||||
SlowChildWorkflow.DefinitionId);
|
||||
|
||||
AssertWorkflowFinished(result);
|
||||
var mainWorkflowCompletedAt = result.WorkflowExecutionContext.UpdatedAt;
|
||||
|
||||
// Assert that the child workflow completed after the main workflow
|
||||
var childContext = Assert.Single(completedChildWorkflows);
|
||||
Assert.True(childContext.UpdatedAt > mainWorkflowCompletedAt,
|
||||
$"Child workflow should complete after main workflow. Main: {mainWorkflowCompletedAt}, Child: {childContext.UpdatedAt}");
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "DispatchWorkflow should send input to child workflow")]
|
||||
public async Task DispatchWithInput_ShouldSendInputToChildWorkflow()
|
||||
{
|
||||
var result = await RunWorkflowAsync(DispatchWithInputWorkflow.DefinitionId);
|
||||
|
||||
AssertWorkflowFinished(result);
|
||||
var writeLineExecutionRecords = result.ActivityExecutionRecords.Where(x => x.ActivityType == "Elsa.WriteLine").ToList();
|
||||
Assert.Equal(ExpectedWriteLineCount, writeLineExecutionRecords.Count);
|
||||
|
||||
var writtenTexts = writeLineExecutionRecords
|
||||
.Select(x => x.ActivityState?[nameof(WriteLine.Text)] as string ?? string.Empty)
|
||||
.ToList();
|
||||
|
||||
Assert.Contains("Received: Hello from parent!", writtenTexts);
|
||||
Assert.Contains("Parent completed", writtenTexts);
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "DispatchWorkflow should use CorrelationId")]
|
||||
public async Task DispatchWithCorrelationId_ShouldUseCorrelationId()
|
||||
{
|
||||
// Run the main workflow and wait for child workflow to complete
|
||||
var (result, completedChildWorkflows) = await RunWorkflowAndWaitForChildWorkflowAsync(
|
||||
DispatchWithCorrelationIdWorkflow.DefinitionId,
|
||||
ChildWorkflow.DefinitionId);
|
||||
|
||||
AssertWorkflowFinished(result);
|
||||
|
||||
// Assert that the child workflow has the expected correlation ID
|
||||
var childContext = Assert.Single(completedChildWorkflows);
|
||||
Assert.Equal("test-correlation-id-123", childContext.CorrelationId);
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "DispatchWorkflow should throw when workflow definition not found")]
|
||||
public async Task DispatchWithInvalidWorkflowDefinitionId_ShouldThrow()
|
||||
{
|
||||
var result = await RunWorkflowAsync(DispatchInvalidDefinitionWorkflow.DefinitionId);
|
||||
Assert.Equal(WorkflowSubStatus.Faulted, result.WorkflowExecutionContext.SubStatus);
|
||||
}
|
||||
|
||||
private Task<TestWorkflowExecutionResult> RunWorkflowAsync(string workflowDefinitionId)
|
||||
{
|
||||
return _workflowRunner.RunAndAwaitWorkflowCompletionAsync(WorkflowDefinitionHandle.ByDefinitionId(workflowDefinitionId, VersionOptions.Published));
|
||||
}
|
||||
|
||||
private static void AssertWorkflowFinished(TestWorkflowExecutionResult result)
|
||||
{
|
||||
Assert.Equal(WorkflowSubStatus.Finished, result.WorkflowExecutionContext.SubStatus);
|
||||
}
|
||||
|
||||
private async Task<(TestWorkflowExecutionResult Result, List<WorkflowExecutionContext> CompletedChildWorkflows)> RunWorkflowAndWaitForChildWorkflowAsync(
|
||||
string parentWorkflowDefinitionId,
|
||||
string childWorkflowDefinitionId)
|
||||
{
|
||||
var workflowEvents = Scope.ServiceProvider.GetRequiredService<WorkflowEvents>();
|
||||
var completedChildWorkflows = new List<WorkflowExecutionContext>();
|
||||
var childWorkflowCompletionTcs = new TaskCompletionSource();
|
||||
|
||||
// Subscribe to child workflow completion events
|
||||
void OnWorkflowStateCommitted(object? sender, WorkflowStateCommittedEventArgs e)
|
||||
{
|
||||
if (e.WorkflowExecutionContext.Workflow.Identity.DefinitionId != childWorkflowDefinitionId ||
|
||||
e.WorkflowExecutionContext.Status != WorkflowStatus.Finished)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
completedChildWorkflows.Add(e.WorkflowExecutionContext);
|
||||
childWorkflowCompletionTcs.TrySetResult();
|
||||
}
|
||||
|
||||
workflowEvents.WorkflowStateCommitted += OnWorkflowStateCommitted;
|
||||
|
||||
try
|
||||
{
|
||||
// Run the main workflow
|
||||
var result = await RunWorkflowAsync(parentWorkflowDefinitionId);
|
||||
|
||||
// Wait for the child workflow to complete
|
||||
await childWorkflowCompletionTcs.Task.WaitAsync(TimeSpan.FromSeconds(ChildWorkflowTimeoutSeconds));
|
||||
|
||||
return (result, completedChildWorkflows);
|
||||
}
|
||||
finally
|
||||
{
|
||||
workflowEvents.WorkflowStateCommitted -= OnWorkflowStateCommitted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
using Elsa.Scheduling.Activities;
|
||||
using Elsa.Workflows.Activities;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
|
|
@ -7,17 +6,11 @@ namespace Elsa.Workflows.ComponentTests.Scenarios.Activities.DispatchWorkflows.W
|
|||
[UsedImplicitly]
|
||||
public class ChildWorkflow : WorkflowBase
|
||||
{
|
||||
public static readonly string DefinitionId = Guid.NewGuid().ToString();
|
||||
public static readonly string DefinitionId = Guid.NewGuid().ToString();
|
||||
|
||||
protected override void Build(IWorkflowBuilder builder)
|
||||
{
|
||||
builder.WithDefinitionId(DefinitionId);
|
||||
builder.Root = new Sequence
|
||||
{
|
||||
Activities =
|
||||
{
|
||||
new Delay(TimeSpan.FromMilliseconds(250)),
|
||||
new WriteLine("Hello from Child!")
|
||||
}
|
||||
};
|
||||
builder.Root = new WriteLine("Child workflow executed");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
using Elsa.Extensions;
|
||||
using Elsa.Workflows.Activities;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Elsa.Workflows.ComponentTests.Scenarios.Activities.DispatchWorkflows.Workflows;
|
||||
|
||||
[UsedImplicitly]
|
||||
public class ChildWorkflowWithInput : WorkflowBase
|
||||
{
|
||||
public static readonly string DefinitionId = Guid.NewGuid().ToString();
|
||||
|
||||
protected override void Build(IWorkflowBuilder builder)
|
||||
{
|
||||
builder.WithDefinitionId(DefinitionId);
|
||||
var message = builder.WithInput<string>("Message");
|
||||
|
||||
builder.Root = new WriteLine(context => $"Received: {context.GetInput<string>(message)}");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
using Elsa.Testing.Shared.Activities;
|
||||
using Elsa.Workflows.Activities;
|
||||
using Elsa.Workflows.Runtime.Activities;
|
||||
|
||||
|
|
@ -7,6 +6,7 @@ namespace Elsa.Workflows.ComponentTests.Scenarios.Activities.DispatchWorkflows.W
|
|||
public class DispatchAndWaitWorkflow : WorkflowBase
|
||||
{
|
||||
public static readonly string DefinitionId = Guid.NewGuid().ToString();
|
||||
|
||||
protected override void Build(IWorkflowBuilder builder)
|
||||
{
|
||||
builder.WithDefinitionId(DefinitionId);
|
||||
|
|
@ -17,9 +17,9 @@ public class DispatchAndWaitWorkflow : WorkflowBase
|
|||
new DispatchWorkflow
|
||||
{
|
||||
WorkflowDefinitionId = new(ChildWorkflow.DefinitionId),
|
||||
WaitForCompletion = new (true)
|
||||
WaitForCompletion = new(true)
|
||||
},
|
||||
new TriggerSignal("Completed")
|
||||
new WriteLine("Parent completed")
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
using Elsa.Workflows.Runtime.Activities;
|
||||
|
||||
namespace Elsa.Workflows.ComponentTests.Scenarios.Activities.DispatchWorkflows.Workflows;
|
||||
|
||||
public class DispatchFireAndForgetWorkflow : WorkflowBase
|
||||
{
|
||||
public static readonly string DefinitionId = Guid.NewGuid().ToString();
|
||||
|
||||
protected override void Build(IWorkflowBuilder builder)
|
||||
{
|
||||
builder.WithDefinitionId(DefinitionId);
|
||||
builder.Root = new DispatchWorkflow
|
||||
{
|
||||
WorkflowDefinitionId = new(SlowChildWorkflow.DefinitionId),
|
||||
WaitForCompletion = new(false)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
using Elsa.Workflows.Activities;
|
||||
using Elsa.Workflows.IncidentStrategies;
|
||||
using Elsa.Workflows.Runtime.Activities;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Elsa.Workflows.ComponentTests.Scenarios.Activities.DispatchWorkflows.Workflows;
|
||||
|
||||
[UsedImplicitly]
|
||||
public class DispatchInvalidDefinitionWorkflow : WorkflowBase
|
||||
{
|
||||
public static readonly string DefinitionId = Guid.NewGuid().ToString();
|
||||
public static readonly string InvalidChildWorkflowId = "NonExistentWorkflow";
|
||||
|
||||
protected override void Build(IWorkflowBuilder builder)
|
||||
{
|
||||
builder.WithDefinitionId(DefinitionId);
|
||||
builder.WorkflowOptions.IncidentStrategyType = typeof(FaultStrategy);
|
||||
|
||||
builder.Root = new Sequence
|
||||
{
|
||||
Activities =
|
||||
{
|
||||
new DispatchWorkflow
|
||||
{
|
||||
WorkflowDefinitionId = new(InvalidChildWorkflowId),
|
||||
WaitForCompletion = new(true)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
using Elsa.Workflows.Activities;
|
||||
using Elsa.Workflows.Runtime.Activities;
|
||||
|
||||
namespace Elsa.Workflows.ComponentTests.Scenarios.Activities.DispatchWorkflows.Workflows;
|
||||
|
||||
public class DispatchWithCorrelationIdWorkflow : WorkflowBase
|
||||
{
|
||||
public static readonly string DefinitionId = Guid.NewGuid().ToString();
|
||||
|
||||
protected override void Build(IWorkflowBuilder builder)
|
||||
{
|
||||
builder.WithDefinitionId(DefinitionId);
|
||||
builder.Root = new Sequence
|
||||
{
|
||||
Activities =
|
||||
{
|
||||
new DispatchWorkflow
|
||||
{
|
||||
WorkflowDefinitionId = new(ChildWorkflow.DefinitionId),
|
||||
CorrelationId = new("test-correlation-id-123"),
|
||||
WaitForCompletion = new(true)
|
||||
},
|
||||
new WriteLine("Parent completed")
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
using Elsa.Workflows.Activities;
|
||||
using Elsa.Workflows.Runtime.Activities;
|
||||
|
||||
namespace Elsa.Workflows.ComponentTests.Scenarios.Activities.DispatchWorkflows.Workflows;
|
||||
|
||||
public class DispatchWithInputWorkflow : WorkflowBase
|
||||
{
|
||||
public static readonly string DefinitionId = Guid.NewGuid().ToString();
|
||||
|
||||
protected override void Build(IWorkflowBuilder builder)
|
||||
{
|
||||
builder.WithDefinitionId(DefinitionId);
|
||||
builder.Root = new Sequence
|
||||
{
|
||||
Activities =
|
||||
{
|
||||
new DispatchWorkflow
|
||||
{
|
||||
WorkflowDefinitionId = new(ChildWorkflowWithInput.DefinitionId),
|
||||
Input = new(new Dictionary<string, object>
|
||||
{
|
||||
["Message"] = "Hello from parent!"
|
||||
}),
|
||||
WaitForCompletion = new(true)
|
||||
},
|
||||
new WriteLine("Parent completed")
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
using Elsa.Workflows.Activities;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Elsa.Workflows.ComponentTests.Scenarios.Activities.DispatchWorkflows.Workflows;
|
||||
|
||||
[UsedImplicitly]
|
||||
public class FaultingChildWorkflow : WorkflowBase
|
||||
{
|
||||
public static readonly string DefinitionId = Guid.NewGuid().ToString();
|
||||
|
||||
protected override void Build(IWorkflowBuilder builder)
|
||||
{
|
||||
builder.WithDefinitionId(DefinitionId);
|
||||
builder.Root = new Fault
|
||||
{
|
||||
Message = new("Child workflow failed intentionally")
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
using Elsa.Scheduling.Activities;
|
||||
using Elsa.Workflows.Activities;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Elsa.Workflows.ComponentTests.Scenarios.Activities.DispatchWorkflows.Workflows;
|
||||
|
||||
[UsedImplicitly]
|
||||
public class SlowChildWorkflow : WorkflowBase
|
||||
{
|
||||
public static readonly string DefinitionId = Guid.NewGuid().ToString();
|
||||
|
||||
protected override void Build(IWorkflowBuilder builder)
|
||||
{
|
||||
builder.WithDefinitionId(DefinitionId);
|
||||
builder.Root = new Sequence
|
||||
{
|
||||
Activities =
|
||||
{
|
||||
Delay.FromMilliseconds(10),
|
||||
new WriteLine("Slow child workflow executed")
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -88,7 +88,7 @@ public class JintJavaScriptFunctionBehaviorTests(ITestOutputHelper testOutputHel
|
|||
|
||||
var shortGuid = dict["shortGuid"]?.ToString();
|
||||
Assert.NotNull(shortGuid);
|
||||
Assert.InRange(shortGuid.Length, 20, 22); // Base64 GUID without padding (can be 20-22 chars)
|
||||
Assert.InRange(shortGuid.Length, 19, 22); // Base64 GUID without padding (can be 19-22 chars)
|
||||
}
|
||||
|
||||
[Theory(DisplayName = "Encoding functions should round-trip correctly")]
|
||||
|
|
|
|||
Loading…
Reference in a new issue