Add project files.

This commit is contained in:
2025-08-18 17:11:43 +03:30
parent 807dc263be
commit e5eb10d604
3943 changed files with 223719 additions and 0 deletions
@@ -0,0 +1,17 @@
using Elsa.Workflows.ComponentTests.Abstractions;
using Elsa.Workflows.ComponentTests.Fixtures;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Workflows.ComponentTests.Scenarios.Activities.FlowJoins;
public class Tests(App app) : AppComponentTest(app)
{
// https://github.com/elsa-workflows/elsa-core/issues/5348
[Fact]
public async Task FlowchartWithSingleFlowJoin_ShouldExecuteSuccessfully()
{
var workflowRunner = Scope.ServiceProvider.GetRequiredService<IWorkflowRunner>();
var result = await workflowRunner.RunAsync<SingleJoinWorkflow>();
Assert.Equal(WorkflowSubStatus.Finished, result.WorkflowState.SubStatus);
}
}
@@ -0,0 +1,17 @@
using Elsa.Workflows.Activities.Flowchart.Activities;
namespace Elsa.Workflows.ComponentTests.Scenarios.Activities.FlowJoins;
public class SingleJoinWorkflow : WorkflowBase
{
protected override void Build(IWorkflowBuilder builder)
{
builder.Root = new Elsa.Workflows.Activities.Flowchart.Activities.Flowchart
{
Activities =
{
new FlowJoin()
}
};
}
}
@@ -0,0 +1,21 @@
using System.Net;
using Elsa.Api.Client.Resources.WorkflowDefinitions.Contracts;
using Elsa.Testing.Shared.Extensions;
using Elsa.Workflows.Api.Endpoints.WorkflowDefinitions.Execute;
using Elsa.Workflows.ComponentTests.Abstractions;
using Elsa.Workflows.ComponentTests.Fixtures;
namespace Elsa.Workflows.ComponentTests.Scenarios.BasicWorkflows;
public class HelloWorldTests(App app) : AppComponentTest(app)
{
[Fact]
public async Task HelloWorldWorkflow_ShouldReturnOk()
{
var client = WorkflowServer.CreateApiClient<IExecuteWorkflowApi>();
using var response = await client.ExecuteAsync("1590068018aa4f0a");
var model = await response.ReadAsJsonAsync<Response>(WorkflowServer.Services);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(WorkflowSubStatus.Finished, model.WorkflowState.SubStatus);
}
}
@@ -0,0 +1,42 @@
{
"id": "1590068018aa4f0a",
"definitionId": "1590068018aa4f0a",
"name": "Hello World",
"isLatest": true,
"isPublished": true,
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "969b0703a9379c3b",
"nodeId": "Workflow1:969b0703a9379c3b",
"activities": [
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "Hello World!"
}
},
"id": "b039045bb7443e57",
"nodeId": "Workflow1:969b0703a9379c3b:b039045bb7443e57",
"name": "WriteLine1",
"type": "Elsa.WriteLine",
"version": 1,
"metadata": {
"designer": {
"position": {
"x": -231.796875,
"y": 269
},
"size": {
"width": 139.296875,
"height": 50
}
}
}
}
],
"connections": []
}
}
@@ -0,0 +1,56 @@
using Elsa.Common.Models;
using Elsa.Testing.Shared.Services;
using Elsa.Workflows.ComponentTests.Abstractions;
using Elsa.Workflows.ComponentTests.Fixtures;
using Elsa.Workflows.ComponentTests.Scenarios.BulkDispatchWorkflows.Workflows;
using Elsa.Workflows.Models;
using Elsa.Workflows.Runtime;
using Elsa.Workflows.Runtime.Messages;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Workflows.ComponentTests.Scenarios.BulkDispatchWorkflows;
public class BulkDispatchWorkflowsTests : AppComponentTest
{
private readonly SignalManager _signalManager;
private readonly IWorkflowRuntime _workflowRuntime;
public BulkDispatchWorkflowsTests(App app) : base(app)
{
_workflowRuntime = Scope.ServiceProvider.GetRequiredService<IWorkflowRuntime>();
_signalManager = Scope.ServiceProvider.GetRequiredService<SignalManager>();
}
// /// <summary>
// /// Dispatches and waits for child workflows to complete.
// /// </summary>
// [Fact(Skip = "This test is flaky and needs to be fixed.")]
// public async Task DispatchAndWaitWorkflow_ShouldWaitForChildWorkflowToComplete()
// {
// var workflowClient = await _workflowRuntime.CreateClientAsync();
// await workflowClient.CreateInstanceAsync(new CreateWorkflowInstanceRequest
// {
// WorkflowDefinitionHandle = WorkflowDefinitionHandle.ByDefinitionId(GreetEmployeesWorkflow.DefinitionId, VersionOptions.Published)
// });
// await workflowClient.RunInstanceAsync(RunWorkflowInstanceRequest.Empty);
// await _signalManager.WaitAsync<string>("Completed");
// }
/// <summary>
/// Individual items are sent as input to child workflows.
/// </summary>
[Fact]
public async Task DispatchWorkflows_ChildWorkflowsShouldReceiveCurrentItem()
{
var workflowClient = await _workflowRuntime.CreateClientAsync();
var request = new CreateAndRunWorkflowInstanceRequest
{
WorkflowDefinitionHandle = WorkflowDefinitionHandle.ByDefinitionId(MixFruitsWorkflow.DefinitionId, VersionOptions.Published)
};
await workflowClient.CreateAndRunInstanceAsync(request);
await _signalManager.WaitAsync("Apple");
await _signalManager.WaitAsync("Banana");
await _signalManager.WaitAsync("Cherry");
}
}
@@ -0,0 +1,24 @@
using Elsa.Extensions;
using Elsa.Workflows.Activities;
using Hangfire.Annotations;
namespace Elsa.Workflows.ComponentTests.Scenarios.BulkDispatchWorkflows.Workflows;
[UsedImplicitly]
public class EmployeeGreetingWorkflow : WorkflowBase
{
public static readonly string DefinitionId = Guid.NewGuid().ToString();
protected override void Build(IWorkflowBuilder builder)
{
builder.WithDefinitionId(DefinitionId);
var employeeName = builder.WithInput<string>("Employee");
builder.Root = new Sequence
{
Activities =
{
new WriteLine(x => $"Hello {x.GetInput<string>(employeeName)}")
}
};
}
}
@@ -0,0 +1,26 @@
using Elsa.Extensions;
using Elsa.Workflows.Activities;
using Elsa.Workflows.ComponentTests.Activities;
using Hangfire.Annotations;
namespace Elsa.Workflows.ComponentTests.Scenarios.BulkDispatchWorkflows.Workflows;
[UsedImplicitly]
public class FruitWorkflow : WorkflowBase
{
public static readonly string DefinitionId = Guid.NewGuid().ToString();
protected override void Build(IWorkflowBuilder builder)
{
builder.WithDefinitionId(DefinitionId);
var item = builder.WithInput<string>("Item");
builder.Root = new Sequence
{
Activities =
{
new WriteLine(x => $"Mixing {x.GetInput<string>(item)}"),
new TriggerSignal(x => x.GetInput<string>(item))
}
};
}
}
@@ -0,0 +1,37 @@
using Elsa.Testing.Shared.Activities;
using Elsa.Workflows.Activities;
namespace Elsa.Workflows.ComponentTests.Scenarios.BulkDispatchWorkflows.Workflows;
public class GreetEmployeesWorkflow : WorkflowBase
{
public static readonly string DefinitionId = Guid.NewGuid().ToString();
protected override void Build(IWorkflowBuilder builder)
{
var employeeNames = new[]
{
"Alice", "Bob", "Charlie"
};
var inputEntries = employeeNames.Select(x => new Dictionary<string, object>
{
["Employee"] = x
});
builder.WithDefinitionId(DefinitionId);
builder.Root = new Sequence
{
Activities =
{
new Runtime.Activities.BulkDispatchWorkflows
{
WorkflowDefinitionId = new(EmployeeGreetingWorkflow.DefinitionId),
Items = new(inputEntries),
WaitForCompletion = new(true)
},
new TriggerSignal("Completed")
}
};
}
}
@@ -0,0 +1,30 @@
using Elsa.Workflows.Activities;
namespace Elsa.Workflows.ComponentTests.Scenarios.BulkDispatchWorkflows.Workflows;
public class MixFruitsWorkflow : WorkflowBase
{
public static readonly string DefinitionId = Guid.NewGuid().ToString();
protected override void Build(IWorkflowBuilder builder)
{
var fruits = new[]
{
"Apple", "Banana", "Cherry"
};
builder.WithDefinitionId(DefinitionId);
builder.Root = new Sequence
{
Activities =
{
new Runtime.Activities.BulkDispatchWorkflows
{
WorkflowDefinitionId = new(FruitWorkflow.DefinitionId),
Items = new(fruits),
WaitForCompletion = new(true)
}
}
};
}
}
@@ -0,0 +1,55 @@
using Elsa.Workflows.ComponentTests.Abstractions;
using Elsa.Workflows.ComponentTests.Fixtures;
using Elsa.Workflows.Management;
using Elsa.Workflows.Management.Filters;
using Microsoft.Extensions.DependencyInjection;
using Xunit.Abstractions;
namespace Elsa.Workflows.ComponentTests.Scenarios.CachingAndWorkflowDefinitionActivity;
// Tests the behavior of the WorkflowDefinitionActivity.
// See https://github.com/elsa-workflows/elsa-core/issues/5314
public class WorkflowDefinitionActivityTests : AppComponentTest
{
private readonly ITestOutputHelper _testOutputHelper;
private const string GrandChildDefinitionId = "29595e7b37a4836d";
private readonly IWorkflowDefinitionCacheManager _workflowDefinitionCacheManager;
private readonly IWorkflowInstanceStore _workflowInstanceStore;
private readonly HttpClient _httpWorkflowClient;
public WorkflowDefinitionActivityTests(App app, ITestOutputHelper testOutputHelper) : base(app)
{
_testOutputHelper = testOutputHelper;
_httpWorkflowClient = WorkflowServer.CreateHttpWorkflowClient();
_workflowDefinitionCacheManager = Scope.ServiceProvider.GetRequiredService<IWorkflowDefinitionCacheManager>();
_workflowInstanceStore = Scope.ServiceProvider.GetRequiredService<IWorkflowInstanceStore>();
}
[Fact]
public async Task SendHttpRequest_WhileEvictingCache_ShouldNotGenerateFaults()
{
var requestTasks = Enumerable.Range(0, 200).Select(SendRequestAsync).ToList();
await Task.WhenAll(requestTasks);
var filter = new WorkflowInstanceFilter
{
WorkflowSubStatus = WorkflowSubStatus.Faulted
};
var faultedWorkflows = (await _workflowInstanceStore.FindManyAsync(filter)).ToList();
var faultCount = faultedWorkflows.Count;
foreach (var faultedWorkflow in faultedWorkflows)
foreach (var incident in faultedWorkflow.WorkflowState.Incidents)
_testOutputHelper.WriteLine(incident.Message);
Assert.Equal(0, faultCount);
}
private async Task SendRequestAsync(int index = 0)
{
var requestTask = _httpWorkflowClient.PostAsync("parent", new StringContent("{}"));
var evictionTask = _workflowDefinitionCacheManager.EvictWorkflowDefinitionAsync(GrandChildDefinitionId);
await Task.WhenAll(requestTask, evictionTask);
}
}
@@ -0,0 +1,64 @@
{
"id": "26d186d68deb1250",
"definitionId": "a3390d1f4c2594a8",
"name": "Child",
"createdAt": "2024-04-30T08:22:44.543407+00:00",
"version": 1,
"toolVersion": "3.2.0.0",
"variables": [],
"inputs": [],
"outputs": [],
"outcomes": [],
"customProperties": {},
"isReadonly": false,
"isSystem": false,
"isLatest": true,
"isPublished": true,
"options": {
"usableAsActivity": true,
"autoUpdateConsumingWorkflows": true
},
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "e6c941b6f2292fcf",
"nodeId": "Workflow2:e6c941b6f2292fcf",
"metadata": {},
"customProperties": {
"source": "FlowchartJsonConverter.cs:45",
"notFoundConnections": [],
"canStartWorkflow": false,
"runAsynchronously": false
},
"activities": [
{
"workflowDefinitionId": "29595e7b37a4836d",
"workflowDefinitionVersionId": "f36dc264f14be3c6",
"latestAvailablePublishedVersion": 1,
"latestAvailablePublishedVersionId": "f36dc264f14be3c6",
"id": "377b23f4235ec26d",
"nodeId": "Workflow2:e6c941b6f2292fcf:377b23f4235ec26d",
"name": "GrandChild1",
"type": "GrandChild",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -193.95703125,
"y": 23
},
"size": {
"width": 115.1875,
"height": 50
}
}
}
}
],
"connections": []
}
}
@@ -0,0 +1,67 @@
{
"id": "f36dc264f14be3c6",
"definitionId": "29595e7b37a4836d",
"name": "Grand Child",
"createdAt": "2024-04-30T08:22:44.537237+00:00",
"version": 1,
"toolVersion": "3.2.0.0",
"variables": [],
"inputs": [],
"outputs": [],
"outcomes": [],
"customProperties": {},
"isReadonly": false,
"isSystem": false,
"isLatest": true,
"isPublished": true,
"options": {
"usableAsActivity": true,
"autoUpdateConsumingWorkflows": true
},
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "29044d8e2ba192c0",
"nodeId": "Workflow1:29044d8e2ba192c0",
"metadata": {},
"customProperties": {
"source": "FlowchartJsonConverter.cs:45",
"notFoundConnections": [],
"canStartWorkflow": false,
"runAsynchronously": false
},
"activities": [
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "Grand Child"
}
},
"id": "fb00878500fde1d3",
"nodeId": "Workflow1:29044d8e2ba192c0:fb00878500fde1d3",
"name": "WriteLine1",
"type": "Elsa.WriteLine",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -234.796875,
"y": -191
},
"size": {
"width": 139.296875,
"height": 50
}
}
}
}
],
"connections": []
}
}
@@ -0,0 +1,142 @@
{
"id": "9178b94f961b1cf",
"definitionId": "189be5173f90b1f6",
"name": "Parent",
"createdAt": "2024-04-30T08:42:17.838484+00:00",
"version": 6,
"toolVersion": "3.2.0.0",
"variables": [],
"inputs": [],
"outputs": [],
"outcomes": [],
"customProperties": {
"Elsa:WorkflowContextProviderTypes": []
},
"isReadonly": false,
"isSystem": false,
"isLatest": true,
"isPublished": true,
"options": {
"autoUpdateConsumingWorkflows": false
},
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "96f60810318a6a89",
"nodeId": "Workflow3:96f60810318a6a89",
"metadata": {},
"customProperties": {
"source": "FlowchartJsonConverter.cs:45",
"notFoundConnections": [],
"canStartWorkflow": false,
"runAsynchronously": false
},
"activities": [
{
"path": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "parent"
}
},
"supportedMethods": {
"typeName": "String[]",
"expression": {
"type": "Object",
"value": "[\u0022POST\u0022]"
}
},
"authorize": {
"typeName": "Boolean",
"expression": {
"type": "Literal",
"value": false
}
},
"policy": {
"typeName": "String",
"expression": {
"type": "Literal"
}
},
"requestTimeout": null,
"requestSizeLimit": null,
"fileSizeLimit": null,
"allowedFileExtensions": null,
"blockedFileExtensions": null,
"allowedMimeTypes": null,
"exposeRequestTooLargeOutcome": false,
"exposeFileTooLargeOutcome": false,
"exposeInvalidFileExtensionOutcome": false,
"exposeInvalidFileMimeTypeOutcome": false,
"parsedContent": null,
"files": null,
"routeData": null,
"queryStringData": null,
"headers": null,
"result": null,
"id": "7e20498ffc94dbc6",
"nodeId": "Workflow3:96f60810318a6a89:7e20498ffc94dbc6",
"name": "HttpEndpoint1",
"type": "Elsa.HttpEndpoint",
"version": 1,
"customProperties": {
"canStartWorkflow": true,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -520,
"y": -120
},
"size": {
"width": 176.390625,
"height": 50
}
}
}
},
{
"workflowDefinitionId": "a3390d1f4c2594a8",
"workflowDefinitionVersionId": "26d186d68deb1250",
"latestAvailablePublishedVersion": 1,
"latestAvailablePublishedVersionId": "26d186d68deb1250",
"id": "ed6a03040f651906",
"nodeId": "Workflow3:96f60810318a6a89:ed6a03040f651906",
"name": "Child1",
"type": "Child",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -236.5810546875,
"y": -120
},
"size": {
"width": 67.7734375,
"height": 50
}
}
}
}
],
"connections": [
{
"source": {
"activity": "7e20498ffc94dbc6",
"port": "Done"
},
"target": {
"activity": "ed6a03040f651906",
"port": "In"
}
}
]
}
}
@@ -0,0 +1,59 @@
using Elsa.Workflows.ComponentTests.Abstractions;
using Elsa.Workflows.ComponentTests.Fixtures;
using Elsa.Workflows.Management;
using Elsa.Workflows.Management.Models;
using Elsa.Workflows.Models;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Workflows.ComponentTests.Scenarios.ClusteredHosting;
public class ActivityRegistrySyncTests(App app) : AppComponentTest(app)
{
private readonly IServiceScope _pod1Scope = app.Cluster.Pod1.Services.CreateScope();
private readonly IServiceScope _pod2Scope = app.Cluster.Pod2.Services.CreateScope();
private readonly IServiceScope _pod3Scope = app.Cluster.Pod3.Services.CreateScope();
[Fact(Skip = "Not yet implemented")]
public async Task ImportWorkflowActivity_ShouldUpdateOtherPods()
{
var pod1ActivityRegistry = _pod1Scope.ServiceProvider.GetRequiredService<IActivityRegistry>();
var pod2ActivityRegistry = _pod2Scope.ServiceProvider.GetRequiredService<IActivityRegistry>();
var pod3ActivityRegistry = _pod3Scope.ServiceProvider.GetRequiredService<IActivityRegistry>();
var sub1Descriptor = pod1ActivityRegistry.Find("Sub");
Assert.Null(sub1Descriptor);
var importer = _pod1Scope.ServiceProvider.GetRequiredService<IWorkflowDefinitionImporter>();
var request = new SaveWorkflowDefinitionRequest
{
Model = new WorkflowDefinitionModel
{
Name = "Sub",
DefinitionId = "Sub",
Options = new WorkflowOptions
{
UsableAsActivity = true,
AutoUpdateConsumingWorkflows = true
}
},
Publish = true
};
await importer.ImportAsync(request);
sub1Descriptor = pod1ActivityRegistry.Find("Sub");
Assert.NotNull(sub1Descriptor);
sub1Descriptor = pod2ActivityRegistry.Find("Sub");
Assert.NotNull(sub1Descriptor);
sub1Descriptor = pod3ActivityRegistry.Find("Sub");
Assert.NotNull(sub1Descriptor);
}
protected override void OnDispose()
{
_pod1Scope.Dispose();
_pod2Scope.Dispose();
_pod3Scope.Dispose();
}
}
@@ -0,0 +1,36 @@
using Elsa.Common.Models;
using Elsa.Testing.Shared;
using Elsa.Testing.Shared.Services;
using Elsa.Workflows.ComponentTests.Abstractions;
using Elsa.Workflows.ComponentTests.Fixtures;
using Elsa.Workflows.ComponentTests.Scenarios.DispatchWorkflows.Workflows;
using Elsa.Workflows.Models;
using Elsa.Workflows.Runtime;
using Elsa.Workflows.Runtime.Messages;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Workflows.ComponentTests.Scenarios.DispatchWorkflows;
public class DispatchWorkflowsTests : AppComponentTest
{
private readonly SignalManager _signalManager;
private readonly IWorkflowRuntime _workflowRuntime;
public DispatchWorkflowsTests(App app) : base(app)
{
_workflowRuntime = Scope.ServiceProvider.GetRequiredService<IWorkflowRuntime>();
_signalManager = Scope.ServiceProvider.GetRequiredService<SignalManager>();
}
[Fact (Skip = "This test is flaky and needs to be fixed.")]
public async Task DispatchAndWaitWorkflow_ShouldWaitForChildWorkflowToComplete()
{
var workflowClient = await _workflowRuntime.CreateClientAsync();
await workflowClient.CreateInstanceAsync(new CreateWorkflowInstanceRequest
{
WorkflowDefinitionHandle = WorkflowDefinitionHandle.ByDefinitionId(DispatchAndWaitWorkflow.DefinitionId, VersionOptions.Published)
});
await workflowClient.RunInstanceAsync(RunWorkflowInstanceRequest.Empty);
await _signalManager.WaitAsync<string>("Completed");
}
}
@@ -0,0 +1,23 @@
using Elsa.Scheduling.Activities;
using Elsa.Workflows.Activities;
using Hangfire.Annotations;
namespace Elsa.Workflows.ComponentTests.Scenarios.DispatchWorkflows.Workflows;
[UsedImplicitly]
public class ChildWorkflow : WorkflowBase
{
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!")
}
};
}
}
@@ -0,0 +1,26 @@
using Elsa.Testing.Shared.Activities;
using Elsa.Workflows.Activities;
using Elsa.Workflows.Runtime.Activities;
namespace Elsa.Workflows.ComponentTests.Scenarios.DispatchWorkflows.Workflows;
public class DispatchAndWaitWorkflow : 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),
WaitForCompletion = new (true)
},
new TriggerSignal("Completed")
}
};
}
}
@@ -0,0 +1,31 @@
using Elsa.Common.Models;
using Elsa.Workflows.ComponentTests.Abstractions;
using Elsa.Workflows.ComponentTests.Fixtures;
using Elsa.Workflows.ComponentTests.Scenarios.ExecuteWorkflows.Workflows;
using Elsa.Workflows.Models;
using Elsa.Workflows.Runtime;
using Elsa.Workflows.Runtime.Messages;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Workflows.ComponentTests.Scenarios.ExecuteWorkflows;
public class ExecuteWorkflowsTests : AppComponentTest
{
private readonly IWorkflowRuntime _workflowRuntime;
public ExecuteWorkflowsTests(App app) : base(app)
{
_workflowRuntime = Scope.ServiceProvider.GetRequiredService<IWorkflowRuntime>();
}
[Fact]
public async Task ExecuteWorkflow_ShouldExecuteWorkflow()
{
var workflowClient = await _workflowRuntime.CreateClientAsync();
await workflowClient.CreateInstanceAsync(new CreateWorkflowInstanceRequest
{
WorkflowDefinitionHandle = WorkflowDefinitionHandle.ByDefinitionId(MainWorkflow.DefinitionId, VersionOptions.Published)
});
await workflowClient.RunInstanceAsync(RunWorkflowInstanceRequest.Empty);
}
}
@@ -0,0 +1,33 @@
using System.Text.Json;
using Elsa.Workflows.Activities;
using Elsa.Workflows.Runtime;
using Elsa.Workflows.Runtime.Activities;
namespace Elsa.Workflows.ComponentTests.Scenarios.ExecuteWorkflows.Workflows;
public class MainWorkflow : WorkflowBase
{
public static readonly string DefinitionId = Guid.NewGuid().ToString();
protected override void Build(IWorkflowBuilder builder)
{
builder.WithDefinitionId(DefinitionId);
var workflowResult = builder.WithVariable<ExecuteWorkflowResult>();
builder.Root = new Sequence
{
Activities =
{
new ExecuteWorkflow
{
WorkflowDefinitionId = new(SubroutineWorkflow.DefinitionId),
Input = new(new Dictionary<string, object>
{
["Value"] = 21
}),
Result = new(workflowResult)
},
new WriteLine(context => $"Subroutine output: {JsonSerializer.Serialize(workflowResult.Get(context))}")
}
};
}
}
@@ -0,0 +1,31 @@
using Elsa.Extensions;
using Elsa.Workflows.Activities;
using Elsa.Workflows.Management.Activities.SetOutput;
using Hangfire.Annotations;
namespace Elsa.Workflows.ComponentTests.Scenarios.ExecuteWorkflows.Workflows;
[UsedImplicitly]
public class SubroutineWorkflow : WorkflowBase
{
public static readonly string DefinitionId = Guid.NewGuid().ToString();
protected override void Build(IWorkflowBuilder builder)
{
builder.WithDefinitionId(DefinitionId);
var valueInput = builder.WithInput<double>("Value");
var output = builder.WithOutput<double>("Output");
builder.Root = new Sequence
{
Activities =
{
new WriteLine(context => $"Running subroutine on value {context.GetInput<double>(valueInput)}..."),
new SetOutput
{
OutputName = new(output.Name),
OutputValue = new(context => context.GetInput<double>(valueInput) * 2)
}
}
};
}
}
@@ -0,0 +1,15 @@
using Elsa.Workflows.ComponentTests.Abstractions;
using Elsa.Workflows.ComponentTests.Fixtures;
namespace Elsa.Workflows.ComponentTests.Scenarios.HttpWorkflows;
public class HttpHelloWorldTests(App app) : AppComponentTest(app)
{
[Fact]
public async Task HelloWorldWorkflow_ShouldRespondWithHelloWorld()
{
var client = WorkflowServer.CreateHttpWorkflowClient();
var response = await client.GetStringAsync("hello-world");
Assert.Equal("Hello World!", response);
}
}
@@ -0,0 +1,41 @@
using System.Net;
using Elsa.Workflows.ComponentTests.Abstractions;
using Elsa.Workflows.ComponentTests.Fixtures;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Workflows.ComponentTests.Scenarios.HttpWorkflows;
public class ResumeSpecificHttpWorkflowInstanceTests(App app) : AppComponentTest(app)
{
[Theory]
[InlineData("workflowInstanceId")]
[InlineData("correlationId")]
public async Task ResumingSpecificWorkflow_ShouldResumeSpecifiedWorkflow(string identifierKey)
{
var client = WorkflowServer.CreateHttpWorkflowClient();
// Start 3 instances.
var workflowInstanceId1 = await StartWorkflowAsync(client, identifierKey);
var workflowInstanceId2 = await StartWorkflowAsync(client, identifierKey);
var workflowInstanceId3 = await StartWorkflowAsync(client, identifierKey);
// Resume the 2nd instance.
var response = await ResumeWorkflowAsync(client, identifierKey, workflowInstanceId2);
// Response should be OK.
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
private async Task<string> StartWorkflowAsync(HttpClient client, string identifierKey)
{
var identityGenerator = Scope.ServiceProvider.GetRequiredService<IIdentityGenerator>();
var identifierValue = identityGenerator.GenerateId();
await client.GetStringAsync($"simple-http-api/start?{identifierKey}={identifierValue}");
return identifierValue;
}
private async Task<HttpResponseMessage> ResumeWorkflowAsync(HttpClient client, string identifierKey, string identifierValue)
{
return await client.GetAsync($"simple-http-api/resume?{identifierKey}={identifierValue}");
}
}
@@ -0,0 +1,15 @@
using Elsa.Workflows.ComponentTests.Abstractions;
using Elsa.Workflows.ComponentTests.Fixtures;
namespace Elsa.Workflows.ComponentTests.Scenarios.HttpWorkflows;
public class RouteDataTests(App app) : AppComponentTest(app)
{
[Fact]
public async Task RouteDataWorkflow_ShouldRespondWithRouteData()
{
var client = WorkflowServer.CreateHttpWorkflowClient();
var response = await client.GetStringAsync("orders/42");
Assert.Equal("42", response);
}
}
@@ -0,0 +1,32 @@
using Elsa.Http;
using Elsa.Workflows.Activities;
using Microsoft.AspNetCore.Http;
namespace Elsa.Workflows.ComponentTests.Scenarios.HttpWorkflows.Workflows;
public class RouteDataWorkflow : WorkflowBase
{
protected override void Build(IWorkflowBuilder builder)
{
var routeDataVariable = builder.WithVariable<IDictionary<string, object>>();
builder.Root = new Sequence
{
Activities =
[
new HttpEndpoint
{
Path = new("orders/{id}"),
SupportedMethods = new([HttpMethods.Get]),
CanStartWorkflow = true,
RouteData = new(routeDataVariable)
},
new WriteHttpResponse
{
Content = new(context => routeDataVariable.Get(context)!["id"].ToString()),
ContentType = new("text/plain")
}
]
};
}
}
@@ -0,0 +1,29 @@
using Elsa.Http;
using Elsa.Workflows.Activities;
using Microsoft.AspNetCore.Http;
namespace Elsa.Workflows.ComponentTests.Scenarios.HttpWorkflows.Workflows;
public class SimpleHttpApiWorkflow : WorkflowBase
{
protected override void Build(IWorkflowBuilder builder)
{
builder.Root = new Sequence
{
Activities =
[
new HttpEndpoint
{
Path = new("simple-http-api/start"),
SupportedMethods = new([HttpMethods.Get]),
CanStartWorkflow = true
},
new HttpEndpoint
{
Path = new("simple-http-api/resume"),
SupportedMethods = new([HttpMethods.Get])
}
]
};
}
}
@@ -0,0 +1,160 @@
{
"id": "a86a9f668817dcb6",
"definitionId": "f926beb0afc8505e",
"name": "HTTP Hello World",
"createdAt": "2024-04-20T17:14:34.118307+00:00",
"version": 2,
"toolVersion": "3.2.0.0",
"variables": [],
"inputs": [],
"outputs": [],
"outcomes": [],
"customProperties": {
"Elsa:WorkflowContextProviderTypes": []
},
"isReadonly": false,
"isSystem": false,
"isLatest": true,
"isPublished": true,
"options": {
"autoUpdateConsumingWorkflows": false
},
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "95a12c8ee4e8c5fc",
"nodeId": "Workflow1:95a12c8ee4e8c5fc",
"metadata": {},
"customProperties": {
"source": "FlowchartJsonConverter.cs:45",
"notFoundConnections": [],
"canStartWorkflow": false,
"runAsynchronously": false
},
"activities": [
{
"path": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "hello-world"
}
},
"supportedMethods": {
"typeName": "String[]",
"expression": {
"type": "Object",
"value": "[\u0022GET\u0022]"
}
},
"authorize": {
"typeName": "Boolean",
"expression": {
"type": "Literal",
"value": false
}
},
"policy": {
"typeName": "String",
"expression": {
"type": "Literal"
}
},
"requestTimeout": null,
"requestSizeLimit": null,
"fileSizeLimit": null,
"allowedFileExtensions": null,
"blockedFileExtensions": null,
"allowedMimeTypes": null,
"exposeRequestTooLargeOutcome": false,
"exposeFileTooLargeOutcome": false,
"exposeInvalidFileExtensionOutcome": false,
"exposeInvalidFileMimeTypeOutcome": false,
"parsedContent": null,
"files": null,
"routeData": null,
"queryStringData": null,
"headers": null,
"result": null,
"id": "90c3d3b2ea1f6ddc",
"nodeId": "Workflow1:95a12c8ee4e8c5fc:90c3d3b2ea1f6ddc",
"name": "HttpEndpoint1",
"type": "Elsa.HttpEndpoint",
"version": 1,
"customProperties": {
"canStartWorkflow": true,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -262.796875,
"y": 264
},
"size": {
"width": 176.390625,
"height": 50
}
}
}
},
{
"statusCode": {
"typeName": "System.Net.HttpStatusCode, System.Net.Primitives",
"expression": {
"type": "Literal",
"value": "OK"
}
},
"content": {
"typeName": "Object",
"expression": {
"type": "Literal",
"value": "Hello World!"
}
},
"contentType": null,
"responseHeaders": {
"typeName": "Elsa.Http.Models.HttpHeaders, Elsa.Http",
"expression": {
"type": "Literal",
"value": "{}"
}
},
"id": "3048105347912af5",
"nodeId": "Workflow1:95a12c8ee4e8c5fc:3048105347912af5",
"name": "WriteHttpResponse1",
"type": "Elsa.WriteHttpResponse",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": 35.203125,
"y": 264
},
"size": {
"width": 183.640625,
"height": 50
}
}
}
}
],
"connections": [
{
"source": {
"activity": "90c3d3b2ea1f6ddc",
"port": "Done"
},
"target": {
"activity": "3048105347912af5",
"port": "In"
}
}
]
}
}
@@ -0,0 +1,25 @@
using Elsa.Workflows.ComponentTests.Abstractions;
using Elsa.Workflows.ComponentTests.Fixtures;
using Elsa.Workflows.Management;
using Elsa.Workflows.Runtime;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Workflows.ComponentTests.Scenarios.InputOutput;
public class InputOutputTests(App app) : AppComponentTest(app)
{
[Fact(DisplayName = "Input should be received by the Provider and should in turn be able to send it back to the Consumer.")]
public async Task InputShouldBeOutput()
{
const string workflowDefinitionVersionId = "4e993ab1b3d4e4a1";
var workflowDefinitionService = Scope.ServiceProvider.GetRequiredService<IWorkflowDefinitionService>();
var workflowInvoker = Scope.ServiceProvider.GetRequiredService<IWorkflowInvoker>();
var variableManager = Scope.ServiceProvider.GetRequiredService<IWorkflowInstanceVariableManager>();
var workflowGraph = await workflowDefinitionService.FindWorkflowGraphAsync(workflowDefinitionVersionId);
var result = await workflowInvoker.InvokeAsync(workflowGraph!);
var variables = await variableManager.GetVariablesAsync(result.WorkflowState.Id);
var variableValue = variables.First().Value;
Assert.Equal("Hello World!", variableValue);
}
}
@@ -0,0 +1,98 @@
{
"$schema": "https://elsaworkflows.io/schemas/workflow-definition/v3.0.0/schema.json",
"id": "4e993ab1b3d4e4a1",
"definitionId": "58dbd7bffa85a761",
"name": "Consumer",
"createdAt": "2024-12-04T16:49:20.593932\u002B00:00",
"version": 1,
"toolVersion": "3.3.0.0",
"variables": [
{
"id": "67fcb9870af4c28e",
"name": "Variable1",
"typeName": "String",
"isArray": false,
"value": "",
"storageDriverTypeName": "Elsa.Workflows.WorkflowInstanceStorageDriver, Elsa.Workflows.Core"
}
],
"inputs": [],
"outputs": [],
"outcomes": [],
"customProperties": {
"logPersistenceConfig": {
"default": {
"evaluationMode": "Strategy",
"strategyType": "Elsa.Workflows.LogPersistence.Strategies.Inherit, Elsa.Workflows.Core",
"expression": null
}
}
},
"isReadonly": false,
"isSystem": false,
"isLatest": true,
"isPublished": true,
"options": {
"autoUpdateConsumingWorkflows": false
},
"root": {
"id": "938f2c91bb759a6a",
"nodeId": "Workflow2:938f2c91bb759a6a",
"name": "Flowchart1",
"type": "Elsa.Flowchart",
"version": 1,
"customProperties": {
"notFoundConnections": [],
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {},
"activities": [
{
"workflowDefinitionId": "780fbf96f1bb1e7e",
"workflowDefinitionVersionId": "640867552fc865d",
"latestAvailablePublishedVersion": 3,
"latestAvailablePublishedVersionId": "640867552fc865d",
"id": "8c1127e1193cd6e8",
"nodeId": "Workflow2:938f2c91bb759a6a:8c1127e1193cd6e8",
"name": "Output1",
"type": "Provider",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -160,
"y": -280
},
"size": {
"width": 89.6953125,
"height": 50
}
}
},
"input1": {
"typeName": "Object",
"expression": {
"type": "Literal",
"value": "Hello World!"
},
"memoryReference": {
"id": "8c1127e1193cd6e8:input-input-1"
}
},
"output1": {
"typeName": "Object",
"memoryReference": {
"id": "67fcb9870af4c28e"
}
}
}
],
"variables": [],
"connections": []
}
}
@@ -0,0 +1,104 @@
{
"$schema": "https://elsaworkflows.io/schemas/workflow-definition/v3.0.0/schema.json",
"id": "5ba5a7e36d60549b",
"definitionId": "780fbf96f1bb1e7e",
"name": "Provider",
"createdAt": "2024-12-04T16:22:39.064075\u002B00:00",
"version": 1,
"toolVersion": "3.3.0.0",
"variables": [],
"inputs": [
{
"uiHint": "singleline",
"storageDriverType": "Elsa.Workflows.WorkflowInstanceStorageDriver, Elsa.Workflows.Core",
"type": "Object",
"name": "Input1",
"displayName": "Input 1",
"description": "",
"category": "Primitives",
"isArray": false
}
],
"outputs": [
{
"type": "Object",
"name": "Output1",
"displayName": "Output 1",
"description": "",
"category": "Primitives",
"isArray": false
}
],
"outcomes": [],
"customProperties": {
"logPersistenceConfig": {
"default": {
"evaluationMode": "Strategy",
"strategyType": "Elsa.Workflows.LogPersistence.Strategies.Inherit, Elsa.Workflows.Core",
"expression": null
}
}
},
"isReadonly": false,
"isSystem": false,
"isLatest": true,
"isPublished": false,
"options": {
"usableAsActivity": true,
"autoUpdateConsumingWorkflows": true
},
"root": {
"id": "85a5e1d9e487bfa4",
"nodeId": "Workflow1:85a5e1d9e487bfa4",
"name": "Flowchart1",
"type": "Elsa.Flowchart",
"version": 1,
"customProperties": {
"notFoundConnections": [],
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {},
"activities": [
{
"outputName": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "Output1"
}
},
"outputValue": {
"typeName": "Object",
"expression": {
"type": "JavaScript",
"value": "return getInput1()"
}
},
"id": "985eb3bfb8d4dd3f",
"nodeId": "Workflow1:85a5e1d9e487bfa4:985eb3bfb8d4dd3f",
"name": "SetOutput1",
"type": "Elsa.SetOutput",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -380,
"y": -220
},
"size": {
"width": 107.3203125,
"height": 50
}
}
}
}
],
"variables": [],
"connections": []
}
}
@@ -0,0 +1,16 @@
using Elsa.Extensions;
using Elsa.JavaScript.Activities;
namespace Elsa.Workflows.ComponentTests.Scenarios.JavaScriptVariables;
public class JavaScriptVariablesWorkflow1 : WorkflowBase
{
public static readonly string DefinitionId = Guid.NewGuid().ToString();
protected override void Build(IWorkflowBuilder builder)
{
builder.WithDefinitionId(DefinitionId);
builder.WithVariable("MagicNumber", 3).WithWorkflowStorage();
builder.Root = new RunJavaScript("setMagicNumber(42)", null, null);
}
}
@@ -0,0 +1,16 @@
using Elsa.Extensions;
using Elsa.JavaScript.Activities;
namespace Elsa.Workflows.ComponentTests.Scenarios.JavaScriptVariables;
public class JavaScriptVariablesWorkflow2 : WorkflowBase
{
public static readonly string DefinitionId = Guid.NewGuid().ToString();
protected override void Build(IWorkflowBuilder builder)
{
builder.WithDefinitionId(DefinitionId);
builder.WithVariable("MagicNumber", 3).WithWorkflowStorage();
builder.Root = new RunJavaScript("variables.MagicNumber = 42", null, null);
}
}
@@ -0,0 +1,16 @@
using Elsa.Extensions;
using Elsa.JavaScript.Activities;
namespace Elsa.Workflows.ComponentTests.Scenarios.JavaScriptVariables;
public class JavaScriptVariablesWorkflow3 : WorkflowBase
{
public static readonly string DefinitionId = Guid.NewGuid().ToString();
protected override void Build(IWorkflowBuilder builder)
{
builder.WithDefinitionId(DefinitionId);
builder.WithVariable("MagicNumber", 3).WithWorkflowStorage();
builder.Root = new RunJavaScript("setVariable('MagicNumber', 42)", null, null);
}
}
@@ -0,0 +1,51 @@
using Elsa.Expressions.Helpers;
using Elsa.Extensions;
using Elsa.Workflows.ComponentTests.Abstractions;
using Elsa.Workflows.ComponentTests.Fixtures;
using Elsa.Workflows.Management;
using Elsa.Workflows.Models;
using Elsa.Workflows.Runtime;
using Elsa.Workflows.Runtime.Messages;
using Elsa.Workflows.State;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Workflows.ComponentTests.Scenarios.JavaScriptVariables;
public class JavaScriptVariablesWorkflowTests(App app) : AppComponentTest(app)
{
[Theory(DisplayName = "SetVariable JS function sets a variable and does not get overridden by variables API")]
[MemberData(nameof(GetWorkflowDefinitions))]
public async Task SetVariableRetainsValue(string workflowDefinitionId)
{
var workflowRuntime = Scope.ServiceProvider.GetRequiredService<IWorkflowRuntime>();
var workflowInstanceStore = Scope.ServiceProvider.GetRequiredService<IWorkflowInstanceStore>();
var workflowClient = await workflowRuntime.CreateClientAsync();
var runAndCreateRequest = new CreateAndRunWorkflowInstanceRequest
{
WorkflowDefinitionHandle = WorkflowDefinitionHandle.ByDefinitionId(workflowDefinitionId)
};
var runResponse = await workflowClient.CreateAndRunInstanceAsync(runAndCreateRequest);
var workflowInstanceId = runResponse.WorkflowInstanceId;
var workflowInstance = await workflowInstanceStore.FindAsync(workflowInstanceId);
var workflowState = workflowInstance!.WorkflowState;
var rootWorkflowActivityExecutionContext = workflowState.ActivityExecutionContexts.Single(x => x.ParentContextId == null);
var variables = GetVariablesDictionary(rootWorkflowActivityExecutionContext);
var magicNumber = variables["magicNumberVariable"].ConvertTo<int>();
Assert.Equal(42, magicNumber);
}
public static IEnumerable<object[]> GetWorkflowDefinitions()
{
return
[
[JavaScriptVariablesWorkflow1.DefinitionId],
[JavaScriptVariablesWorkflow2.DefinitionId],
[JavaScriptVariablesWorkflow3.DefinitionId]
];
}
private VariablesDictionary GetVariablesDictionary(ActivityExecutionContextState context)
{
return context.Properties.GetOrAdd(WorkflowInstanceStorageDriver.VariablesDictionaryStateKey, () => new VariablesDictionary());
}
}
@@ -0,0 +1,109 @@
using Elsa.Api.Client.Resources.WorkflowDefinitions.Contracts;
using Elsa.Api.Client.Resources.WorkflowDefinitions.Responses;
using Elsa.Api.Client.Resources.WorkflowInstances.Models;
using Elsa.Common.Entities;
using Elsa.Testing.Shared.Extensions;
using Elsa.Workflows.Activities;
using Elsa.Workflows.ComponentTests.Abstractions;
using Elsa.Workflows.ComponentTests.Fixtures;
using Elsa.Workflows.Helpers;
using Elsa.Workflows.Runtime;
using Elsa.Workflows.Runtime.Entities;
using Elsa.Workflows.Runtime.Filters;
using Elsa.Workflows.Runtime.OrderDefinitions;
using Microsoft.Extensions.DependencyInjection;
using Open.Linq.AsyncExtensions;
namespace Elsa.Workflows.ComponentTests.Scenarios.LogPersistenceModes;
public class InputOutputLoggingTests(App app) : AppComponentTest(app)
{
[Theory]
[InlineData("input-output-logging-1", true, false, true, false)]
[InlineData("input-output-logging-2", true, false, true, false)]
public async Task Workflow_ShouldHonorSettings_WhenExecuting(string workflowDefinitionId, params bool[] shouldBeIncludedArray)
{
var workflowState = await ExecuteWorkflowAsync(workflowDefinitionId);
var workflowInstanceId = workflowState.Id;
var writeLineActivityTypeName = ActivityTypeNameHelper.GenerateTypeName<WriteLine>();
var activityExecutionRecords = await GetActivityExecutionRecordsAsync(workflowInstanceId, x => x.ActivityType == writeLineActivityTypeName).ToList();
for (var i = 0; i < shouldBeIncludedArray.Length; i++)
{
var activityExecutionRecord = activityExecutionRecords[i];
var shouldBeIncluded = shouldBeIncludedArray[i];
var isIncluded = activityExecutionRecord.ActivityState?.ContainsKey(nameof(WriteLine.Text)) == true;
Assert.Equal(shouldBeIncluded, isIncluded);
}
}
[Fact]
public async Task WorkflowAsActivity_ShouldHonorSettings_WhenExecuting()
{
await ExecuteWorkflowAsync("input-output-logging-3");
var activityExecutionRecord = await GetRecordByActivityNameAsync("InputOutputLogging3-Child1");
var output1IsIncluded = activityExecutionRecord.Outputs!.ContainsKey("Output1");
var output2IsIncluded = activityExecutionRecord.Outputs!.ContainsKey("Output2");
Assert.True(output1IsIncluded);
Assert.True(output2IsIncluded);
}
[Fact(Skip = "Although this functionality works in practice, the component test fails from time to time for no clear reason (yet).")]
public async Task WorkflowAsActivityInternal_ShouldHonorSettings_WhenExecuting()
{
await ExecuteWorkflowAsync("input-output-logging-3");
var setOutput1Record = await GetRecordByActivityNameAsync("SetOutput1");
var setOutput2Record = await GetRecordByActivityNameAsync("SetOutput2");
var output1IsIncluded = setOutput1Record.ActivityState?.ContainsKey("OutputName") == true;
var output2IsIncluded = setOutput2Record.ActivityState?.ContainsKey("OutputName") == true;
Assert.False(output1IsIncluded);
Assert.True(output2IsIncluded);
}
private async Task<WorkflowState> ExecuteWorkflowAsync(string workflowDefinitionId)
{
var client = WorkflowServer.CreateApiClient<IExecuteWorkflowApi>();
using var response = await client.ExecuteAsync(workflowDefinitionId);
var model = await response.ReadAsJsonAsync<ExecuteWorkflowDefinitionResponse>(WorkflowServer.Services);
return model.WorkflowState;
}
private Task<IEnumerable<ActivityExecutionRecord>> GetActivityExecutionRecordsAsync(string workflowInstanceId, Func<ActivityExecutionRecord, bool> predicate)
{
var activityExecutionRecordFilter = new ActivityExecutionRecordFilter
{
WorkflowInstanceId = workflowInstanceId,
};
return GetActivityExecutionRecordsAsync(activityExecutionRecordFilter, predicate);
}
private Task<ActivityExecutionRecord> GetRecordByActivityNameAsync(string name)
{
var filter = new ActivityExecutionRecordFilter
{
Name = name
};
return GetActivityExecutionRecordAsync(filter);
}
private async Task<IEnumerable<ActivityExecutionRecord>> GetActivityExecutionRecordsAsync(ActivityExecutionRecordFilter filter, Func<ActivityExecutionRecord, bool>? predicate = null)
{
var activityExecutionStore = Scope.ServiceProvider.GetRequiredService<IActivityExecutionStore>();
var orderBy = new ActivityExecutionRecordOrder<DateTimeOffset>(x => x.StartedAt, OrderDirection.Ascending);
var activityExecutionRecords = await activityExecutionStore.FindManyAsync(filter, orderBy);
return predicate == null ? activityExecutionRecords : activityExecutionRecords.Where(predicate);
}
private async Task<ActivityExecutionRecord> GetActivityExecutionRecordAsync(ActivityExecutionRecordFilter filter)
{
var activityExecutionStore = Scope.ServiceProvider.GetRequiredService<IActivityExecutionStore>();
var result = await activityExecutionStore.FindAsync(filter);
if(result == null)
throw new InvalidOperationException($"Activity execution record not found.");
return result;
}
}
@@ -0,0 +1,210 @@
{
"id": "94f11e583299383b",
"definitionId": "input-output-logging-1",
"name": "Input Output Logging 1",
"createdAt": "2024-04-21T11:34:17.423214+00:00",
"version": 1,
"toolVersion": "3.2.0.0",
"variables": [],
"inputs": [],
"outputs": [],
"outcomes": [],
"customProperties": {
"Elsa:WorkflowContextProviderTypes": []
},
"isReadonly": false,
"isSystem": false,
"isLatest": true,
"isPublished": true,
"options": {
"autoUpdateConsumingWorkflows": false
},
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "482993d55270a5e7",
"nodeId": "Workflow1:482993d55270a5e7",
"metadata": {},
"customProperties": {
"source": "FlowchartJsonConverter.cs:45",
"notFoundConnections": [],
"canStartWorkflow": false,
"runAsynchronously": false
},
"activities": [
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "This input should be stored"
}
},
"id": "ea21b3863867ebb3",
"nodeId": "Workflow1:482993d55270a5e7:ea21b3863867ebb3",
"name": "WriteLine1",
"type": "Elsa.WriteLine",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -440,
"y": -140
},
"size": {
"width": 139.296875,
"height": 50
}
}
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "This input should not be stored"
}
},
"id": "4726389b3d516c40",
"nodeId": "Workflow1:482993d55270a5e7:4726389b3d516c40",
"name": "WriteLine2",
"type": "Elsa.WriteLine",
"version": 1,
"customProperties": {
"logPersistenceMode": {
"default": "Exclude",
"inputs": {
"text": "Inherit"
},
"outputs": {}
},
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -220,
"y": -140
},
"size": {
"width": 139.296875,
"height": 50
}
}
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "This input should be stored"
}
},
"id": "e13b09839d4244b9",
"nodeId": "Workflow1:482993d55270a5e7:e13b09839d4244b9",
"name": "WriteLine3",
"type": "Elsa.WriteLine",
"version": 1,
"customProperties": {
"logPersistenceMode": {
"default": "Exclude",
"inputs": {
"text": "Include"
},
"outputs": {}
},
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -16,
"y": -140
},
"size": {
"width": 139.296875,
"height": 50
}
}
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "This input should not be stored"
}
},
"id": "1ba2922db9052ce9",
"nodeId": "Workflow1:482993d55270a5e7:1ba2922db9052ce9",
"name": "WriteLine4",
"type": "Elsa.WriteLine",
"version": 1,
"customProperties": {
"logPersistenceMode": {
"default": "Include",
"inputs": {
"text": "Exclude"
},
"outputs": {}
},
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": 188,
"y": -140
},
"size": {
"width": 139.296875,
"height": 50
}
}
}
}
],
"connections": [
{
"source": {
"activity": "ea21b3863867ebb3",
"port": "Done"
},
"target": {
"activity": "4726389b3d516c40",
"port": "In"
}
},
{
"source": {
"activity": "4726389b3d516c40",
"port": "Done"
},
"target": {
"activity": "e13b09839d4244b9",
"port": "In"
}
},
{
"source": {
"activity": "e13b09839d4244b9",
"port": "Done"
},
"target": {
"activity": "1ba2922db9052ce9",
"port": "In"
}
}
]
}
}
@@ -0,0 +1,213 @@
{
"id": "a7499c4c7888fafb",
"definitionId": "input-output-logging-2",
"name": "Input Output Logging 2",
"createdAt": "2024-04-21T11:39:57.182662+00:00",
"version": 2,
"toolVersion": "3.2.0.0",
"variables": [],
"inputs": [],
"outputs": [],
"outcomes": [],
"customProperties": {
"Elsa:WorkflowContextProviderTypes": [],
"logPersistenceMode": {
"default": "Exclude"
}
},
"isReadonly": false,
"isSystem": false,
"isLatest": true,
"isPublished": true,
"options": {
"autoUpdateConsumingWorkflows": false
},
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "4dce3806d9c7af92",
"nodeId": "Workflow1:4dce3806d9c7af92",
"metadata": {},
"customProperties": {
"source": "FlowchartJsonConverter.cs:45",
"notFoundConnections": [],
"canStartWorkflow": false,
"runAsynchronously": false
},
"activities": [
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "This should be logged"
}
},
"id": "30d32d634c1ddbfa",
"nodeId": "Workflow1:4dce3806d9c7af92:30d32d634c1ddbfa",
"name": "WriteLine1",
"type": "Elsa.WriteLine",
"version": 1,
"customProperties": {
"logPersistenceMode": {
"default": "Include",
"inputs": {
"text": "Inherit"
},
"outputs": {}
},
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -380,
"y": -120
},
"size": {
"width": 139.296875,
"height": 50
}
}
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "This should not be logged"
}
},
"id": "50fcb804a7ace442",
"nodeId": "Workflow1:4dce3806d9c7af92:50fcb804a7ace442",
"name": "WriteLine2",
"type": "Elsa.WriteLine",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -156,
"y": -120
},
"size": {
"width": 139.296875,
"height": 50
}
}
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "This should be logged"
}
},
"id": "3f00f1de3ec2862b",
"nodeId": "Workflow1:4dce3806d9c7af92:3f00f1de3ec2862b",
"name": "WriteLine3",
"type": "Elsa.WriteLine",
"version": 1,
"customProperties": {
"logPersistenceMode": {
"default": "Inherit",
"inputs": {
"text": "Include"
},
"outputs": {}
},
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": 60,
"y": -120
},
"size": {
"width": 139.296875,
"height": 50
}
}
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "This should not be logged"
}
},
"id": "3dcff307781b3707",
"nodeId": "Workflow1:4dce3806d9c7af92:3dcff307781b3707",
"name": "WriteLine4",
"type": "Elsa.WriteLine",
"version": 1,
"customProperties": {
"logPersistenceMode": {
"default": "Inherit",
"inputs": {
"text": "Exclude"
},
"outputs": {}
},
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": 264,
"y": -120
},
"size": {
"width": 139.296875,
"height": 50
}
}
}
}
],
"connections": [
{
"source": {
"activity": "3f00f1de3ec2862b",
"port": "Done"
},
"target": {
"activity": "3dcff307781b3707",
"port": "In"
}
},
{
"source": {
"activity": "30d32d634c1ddbfa",
"port": "Done"
},
"target": {
"activity": "50fcb804a7ace442",
"port": "In"
}
},
{
"source": {
"activity": "50fcb804a7ace442",
"port": "Done"
},
"target": {
"activity": "3f00f1de3ec2862b",
"port": "In"
}
}
]
}
}
@@ -0,0 +1,158 @@
{
"id": "4cf7c10c7a2901d6",
"definitionId": "input-output-logging-3-child",
"name": "Input Output Logging 3 - Child",
"createdAt": "2024-04-22T13:11:07.146222+00:00",
"version": 4,
"toolVersion": "3.2.0.0",
"variables": [],
"inputs": [],
"outputs": [
{
"type": "Object",
"name": "Output1",
"displayName": "Output 1",
"category": "Primitives",
"isArray": false
},
{
"type": "Object",
"name": "Output2",
"displayName": "Output 2",
"category": "Primitives",
"isArray": false
}
],
"outcomes": [],
"customProperties": {
"logPersistenceMode": {
"default": "Exclude"
},
"Elsa:WorkflowContextProviderTypes": []
},
"isReadonly": false,
"isSystem": false,
"isLatest": true,
"isPublished": true,
"options": {
"usableAsActivity": true,
"autoUpdateConsumingWorkflows": true
},
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "5f72efc9043753fa",
"nodeId": "Workflow1:5f72efc9043753fa",
"metadata": {},
"customProperties": {
"source": "FlowchartJsonConverter.cs:45",
"notFoundConnections": [],
"canStartWorkflow": false,
"runAsynchronously": false
},
"activities": [
{
"outputName": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "Output1"
}
},
"outputValue": {
"typeName": "Object",
"expression": {
"type": "Literal",
"value": "Output value 1"
}
},
"id": "402076f12c1220a9",
"nodeId": "Workflow1:5f72efc9043753fa:402076f12c1220a9",
"name": "SetOutput1",
"type": "Elsa.SetOutput",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false,
"logPersistenceMode": {
"default": "Inherit",
"inputs": {
"outputName": "Inherit",
"outputValue": "Inherit"
},
"outputs": {}
}
},
"metadata": {
"designer": {
"position": {
"x": -221.4757080078125,
"y": 264.861083984375
},
"size": {
"width": 107.734375,
"height": 50.2256965637207
}
}
}
},
{
"outputName": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "Output2"
}
},
"outputValue": {
"typeName": "Object",
"expression": {
"type": "Literal",
"value": "Output value 2"
}
},
"id": "db1d1c02cbfac590",
"nodeId": "Workflow1:5f72efc9043753fa:db1d1c02cbfac590",
"name": "SetOutput2",
"type": "Elsa.SetOutput",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false,
"logPersistenceMode": {
"default": "Inherit",
"inputs": {
"outputName": "Include",
"outputValue": "Include"
},
"outputs": {}
}
},
"metadata": {
"designer": {
"position": {
"x": 58.5242919921875,
"y": 264.861083984375
},
"size": {
"width": 107.734375,
"height": 50.2256965637207
}
}
}
}
],
"connections": [
{
"source": {
"activity": "402076f12c1220a9",
"port": "Done"
},
"target": {
"activity": "db1d1c02cbfac590",
"port": "In"
}
}
]
}
}
@@ -0,0 +1,65 @@
{
"id": "6da18530316aee2e",
"definitionId": "input-output-logging-3",
"name": "Input Output Logging 3",
"createdAt": "2024-04-21T20:08:17.495551+00:00",
"version": 1,
"toolVersion": "3.2.0.0",
"variables": [],
"inputs": [],
"outputs": [],
"outcomes": [],
"customProperties": {
"Elsa:WorkflowContextProviderTypes": []
},
"isReadonly": false,
"isSystem": false,
"isLatest": true,
"isPublished": true,
"options": {
"autoUpdateConsumingWorkflows": false
},
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "9aec3fea5a39cccd",
"nodeId": "Workflow2:9aec3fea5a39cccd",
"metadata": {},
"customProperties": {
"source": "FlowchartJsonConverter.cs:45",
"notFoundConnections": [],
"canStartWorkflow": false,
"runAsynchronously": false
},
"activities": [
{
"workflowDefinitionId": "input-output-logging-3-child",
"workflowDefinitionVersionId": "4cf7c10c7a2901d6",
"latestAvailablePublishedVersion": 4,
"latestAvailablePublishedVersionId": "4cf7c10c7a2901d6",
"id": "6bf4016d6074831f",
"nodeId": "Workflow2:9aec3fea5a39cccd:6bf4016d6074831f",
"name": "InputOutputLogging3-Child1",
"type": "InputOutputLogging3-Child",
"version": 4,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -360,
"y": -320
},
"size": {
"width": 246.375,
"height": 50
}
}
}
}
],
"connections": []
}
}
@@ -0,0 +1,24 @@
using Elsa.Common.Models;
using Elsa.Workflows.ComponentTests.Abstractions;
using Elsa.Workflows.ComponentTests.Fixtures;
using Elsa.Workflows.Management;
using Elsa.Workflows.Management.Filters;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Workflows.ComponentTests.Scenarios.Multitenancy;
public class MultitenancyTests(App app) : AppComponentTest(app)
{
[Fact(Skip = "Multitenancy disabled. This test doesn't work because not all workflows are assigned the Tenant1 tenant.")]
public async Task LoadingWorkflows_ShouldReturnWorkflows_FromCurrentTenant()
{
var store = Scope.ServiceProvider.GetRequiredService<IWorkflowDefinitionStore>();
var filter = new WorkflowDefinitionFilter
{
IsSystem = false,
VersionOptions = VersionOptions.Latest
};
var workflows = await store.FindManyAsync(filter);
Assert.All(workflows, workflow => Assert.Equal("Tenant1", workflow.TenantId));
}
}
@@ -0,0 +1,16 @@
using Elsa.Workflows.Activities;
using Hangfire.Annotations;
namespace Elsa.Workflows.ComponentTests.Scenarios.Multitenancy.Workflows;
[UsedImplicitly]
public class WorkflowForTenant1 : WorkflowBase
{
public static readonly string DefinitionId = Guid.NewGuid().ToString();
protected override void Build(IWorkflowBuilder builder)
{
builder.WithDefinitionId(DefinitionId);
builder.WithTenantId("Tenant1");
builder.Root = new WriteLine("Tenant1");
}
}
@@ -0,0 +1,16 @@
using Elsa.Workflows.Activities;
using Hangfire.Annotations;
namespace Elsa.Workflows.ComponentTests.Scenarios.Multitenancy.Workflows;
[UsedImplicitly]
public class WorkflowForTenant2 : WorkflowBase
{
public static readonly string DefinitionId = Guid.NewGuid().ToString();
protected override void Build(IWorkflowBuilder builder)
{
builder.WithDefinitionId(DefinitionId);
builder.WithTenantId("Tenant2");
builder.Root = new WriteLine("Tenant2");
}
}
@@ -0,0 +1,13 @@
using Elsa.Extensions;
namespace Elsa.Workflows.ComponentTests.Scenarios.Variables.Activities;
public class CountdownStep : Activity
{
protected override void Execute(ActivityExecutionContext context)
{
var counter = context.GetVariable<int>("Counter");
context.SetVariable("Counter", counter - 1);
context?.CreateBookmark();
}
}
@@ -0,0 +1,71 @@
using Elsa.Common.Models;
using Elsa.Expressions.Helpers;
using Elsa.Extensions;
using Elsa.Workflows.ComponentTests.Abstractions;
using Elsa.Workflows.ComponentTests.Fixtures;
using Elsa.Workflows.ComponentTests.Scenarios.Variables.Workflows;
using Elsa.Workflows.Management;
using Elsa.Workflows.Models;
using Elsa.Workflows.Runtime;
using Elsa.Workflows.Runtime.Entities;
using Elsa.Workflows.Runtime.Filters;
using Elsa.Workflows.Runtime.Messages;
using Elsa.Workflows.State;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Workflows.ComponentTests.Scenarios.Variables;
public class CountdownWorkflowTests(App app) : AppComponentTest(app)
{
[Fact(DisplayName = "Variable is persisted across workflow runs")]
public async Task VariableIsPersistedAcrossWorkflowRuns()
{
var workflowRuntime = Scope.ServiceProvider.GetRequiredService<IWorkflowRuntime>();
var workflowClient = await workflowRuntime.CreateClientAsync();
var workflowInstanceStore = Scope.ServiceProvider.GetRequiredService<IWorkflowInstanceStore>();
var bookmarkStore = Scope.ServiceProvider.GetRequiredService<IBookmarkStore>();
var runAndCreateRequest = new CreateAndRunWorkflowInstanceRequest
{
WorkflowDefinitionHandle = WorkflowDefinitionHandle.ByDefinitionId(CountdownWorkflow.DefinitionId, VersionOptions.Latest),
};
var runResponse = await workflowClient.CreateAndRunInstanceAsync(runAndCreateRequest);
var workflowInstanceId = runResponse.WorkflowInstanceId;
var createdBookmarks = await bookmarkStore.FindManyAsync(new()
{
WorkflowInstanceId = workflowInstanceId
});
var bookmarks = new Stack<StoredBookmark>(createdBookmarks);
var expectedCounter = 3;
while (bookmarks.Any())
{
var workflowInstance = await workflowInstanceStore.FindAsync(workflowInstanceId);
var workflowState = workflowInstance!.WorkflowState;
var rootWorkflowActivityExecutionContext = workflowState.ActivityExecutionContexts.Single(x => x.ParentContextId == null);
var variables = GetVariablesDictionary(rootWorkflowActivityExecutionContext);
var actualCounter = variables["counterVariable"].ConvertTo<int>();
Assert.Equal(--expectedCounter, actualCounter);
var bookmark = bookmarks.Pop();
var runRequest = new RunWorkflowInstanceRequest
{
BookmarkId = bookmark.Id,
};
await workflowClient.RunInstanceAsync(runRequest);
createdBookmarks = await bookmarkStore.FindManyAsync(new()
{
WorkflowInstanceId = workflowInstanceId
});
foreach (var newBookmark in createdBookmarks)
bookmarks.Push(newBookmark);
}
}
private VariablesDictionary GetVariablesDictionary(ActivityExecutionContextState context)
{
return context.Properties.GetOrAdd(WorkflowInstanceStorageDriver.VariablesDictionaryStateKey, () => new VariablesDictionary());
}
}
@@ -0,0 +1,34 @@
using Elsa.Extensions;
using Elsa.Workflows.Activities;
using Elsa.Workflows.ComponentTests.Scenarios.Variables.Activities;
namespace Elsa.Workflows.ComponentTests.Scenarios.Variables.Workflows;
public class CountdownWorkflow : WorkflowBase
{
public static readonly string DefinitionId = Guid.NewGuid().ToString();
protected override void Build(IWorkflowBuilder builder)
{
builder.WithDefinitionId(DefinitionId);
var counter = builder.WithVariable("Counter", 3).WithWorkflowStorage();
builder.Root = new Sequence
{
Activities =
{
new While(context => counter.Get(context) > 0)
{
Body = new Sequence
{
Activities =
{
new WriteLine(context => $"Counter: {counter.Get(context)}"),
new CountdownStep()
}
}
}
}
};
}
}
@@ -0,0 +1,100 @@
using Elsa.Http;
using Elsa.Testing.Shared;
using Elsa.Testing.Shared.Services;
using Elsa.Workflows.ComponentTests.Abstractions;
using Elsa.Workflows.ComponentTests.Fixtures;
using Elsa.Workflows.Management;
using Elsa.Workflows.Management.Filters;
using Elsa.Workflows.Runtime.Filters;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Workflows.ComponentTests.Scenarios.WorkflowActivities;
public class AutoUpdateTests : AppComponentTest
{
private const string ParentDefinitionVersionId = "4b584e249fdca951";
private const string ParentDefinitionId = "878770f04439a55d";
private const string ChildDefinitionId = "f353742a9ef6af4";
private static readonly object HttpChangeTokenSignal = new();
private static readonly object TriggerChangeTokenSignal = new();
private static readonly object GraphChangeTokenSignal = new();
private readonly IMemoryCache _cache;
private readonly TriggerChangeTokenSignalEvents _changeTokenEvents;
private readonly IWorkflowDefinitionCacheManager _definitionCacheManager;
private readonly IHasher _hasher;
private readonly IHttpWorkflowsCacheManager _httpCacheManager;
private readonly IWorkflowDefinitionPublisher _publisher;
private readonly SignalManager _signalManager;
private readonly IWorkflowDefinitionCacheManager _workflowCacheManager;
private string? _graphChangeToken;
private string? _httpChangeToken;
private string? _triggerChangeToken;
public AutoUpdateTests(App app) : base(app)
{
_cache = Scope.ServiceProvider.GetRequiredService<IMemoryCache>();
_hasher = Scope.ServiceProvider.GetRequiredService<IHasher>();
_definitionCacheManager = Scope.ServiceProvider.GetRequiredService<IWorkflowDefinitionCacheManager>();
_publisher = Scope.ServiceProvider.GetRequiredService<IWorkflowDefinitionPublisher>();
_httpCacheManager = Scope.ServiceProvider.GetRequiredService<IHttpWorkflowsCacheManager>();
_workflowCacheManager = Scope.ServiceProvider.GetRequiredService<IWorkflowDefinitionCacheManager>();
_signalManager = Scope.ServiceProvider.GetRequiredService<SignalManager>();
_changeTokenEvents = Scope.ServiceProvider.GetRequiredService<TriggerChangeTokenSignalEvents>();
_changeTokenEvents.ChangeTokenSignalTriggered += OnChangeTokenSignalTriggered;
}
[Fact(DisplayName = "Updating a workflow with `auto update consuming workflows` should invalidate consuming workflows from cache")]
public async Task UpdateWorkflowWithAutoUpdate()
{
// Run workflow to make sure the all required items for running the workflow are in the cache.
var client = WorkflowServer.CreateHttpWorkflowClient();
await client.GetStringAsync("test-cache-invalidation");
// Make sure the items are in the cache.
var hash = _httpCacheManager.ComputeBookmarkHash("/test-cache-invalidation", "get");
Assert.True(_cache.TryGetValue($"http-workflow:{hash}", out _));
var filter = new TriggerFilter
{
Hash = hash
};
var hashedFilter = _hasher.Hash(filter);
Assert.True(_cache.TryGetValue($"IEnumerable`1:{hashedFilter}", out _));
var parentWorkflowDefinitionFilter = new WorkflowDefinitionFilter
{
Id = ParentDefinitionVersionId
};
var parentVersionCacheKey = _definitionCacheManager.CreateWorkflowFilterCacheKey(parentWorkflowDefinitionFilter);
Assert.True(_cache.TryGetValue(parentVersionCacheKey, out _));
// Set change tokens.
_httpChangeToken = _workflowCacheManager.CreateWorkflowDefinitionChangeTokenKey(ParentDefinitionId);
_triggerChangeToken = _httpCacheManager.GetTriggerChangeTokenKey(hash);
_graphChangeToken = _workflowCacheManager.CreateWorkflowDefinitionChangeTokenKey(ParentDefinitionId);
// (Act) Save the draft version of the child workflow and update the references.
await _publisher.PublishAsync(ChildDefinitionId);
// Wait until the notifications for updating the cache have been send and check the cache.
await _signalManager.WaitAsync<TriggerChangeTokenSignalEventArgs>(HttpChangeTokenSignal);
await _signalManager.WaitAsync<TriggerChangeTokenSignalEventArgs>(TriggerChangeTokenSignal);
await _signalManager.WaitAsync<TriggerChangeTokenSignalEventArgs>(GraphChangeTokenSignal);
Assert.False(_cache.TryGetValue($"http-workflow:{hash}", out _));
Assert.False(_cache.TryGetValue($"IEnumerable`1:{hashedFilter}", out _));
Assert.False(_cache.TryGetValue(parentVersionCacheKey, out _));
}
private void OnChangeTokenSignalTriggered(object? sender, TriggerChangeTokenSignalEventArgs args)
{
if (args.Key == _httpChangeToken) _signalManager.Trigger(HttpChangeTokenSignal, args);
if (args.Key == _triggerChangeToken) _signalManager.Trigger(TriggerChangeTokenSignal, args);
if (args.Key == _graphChangeToken) _signalManager.Trigger(GraphChangeTokenSignal, args);
}
}
@@ -0,0 +1,84 @@
using Elsa.Testing.Shared;
using Elsa.Testing.Shared.Services;
using Elsa.Workflows.ComponentTests.Abstractions;
using Elsa.Workflows.ComponentTests.Fixtures;
using Elsa.Workflows.ComponentTests.Scenarios.WorkflowActivities.Workflows;
using Elsa.Workflows.Management;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Workflows.ComponentTests.Scenarios.WorkflowActivities;
public class DeleteWorkflowTests : AppComponentTest
{
private static readonly object WorkflowDeletedSignal = new();
private readonly IServiceScope _scope1;
// private readonly IServiceScope _scope2;
// private readonly IServiceScope _scope3;
private readonly SignalManager _signalManager;
private readonly WorkflowDefinitionEvents _workflowDefinitionEvents;
public DeleteWorkflowTests(App app) : base(app)
{
_scope1 = app.Cluster.Pod1.Services.CreateScope();
// Disabled these scope creations since this prevents events from firing in other tests.
// _scope2 = app.Cluster.Pod2.Services.CreateScope();
// _scope3 = app.Cluster.Pod3.Services.CreateScope();
_signalManager = Scope.ServiceProvider.GetRequiredService<SignalManager>();
_workflowDefinitionEvents = Scope.ServiceProvider.GetRequiredService<WorkflowDefinitionEvents>();
_workflowDefinitionEvents.WorkflowDefinitionDeleted += OnWorkflowDefinitionDeleted;
}
[Fact]
public async Task DeleteWorkflow()
{
EnsureWorkflowInRegistry(_scope1, Workflows.DeleteWorkflow.Type);
var workflowDefinitionManager = _scope1.ServiceProvider.GetRequiredService<IWorkflowDefinitionManager>();
await workflowDefinitionManager.DeleteByDefinitionIdAsync(Workflows.DeleteWorkflow.DefinitionId);
WorkflowTypeDeletedFromRegistry(_scope1, Workflows.DeleteWorkflow.Type);
}
[Fact(Skip = "Clustered tests are interfering with other event driven tests")]
public async Task DeleteWorkflow_Clustered()
{
EnsureWorkflowInRegistry(_scope1, DeleteWorkflowClustered.Type);
// EnsureWorkflowInRegistry(_scope2, DeleteWorkflowClustered.Type);
// EnsureWorkflowInRegistry(_scope3, DeleteWorkflowClustered.Type);
var workflowDefinitionManager = _scope1.ServiceProvider.GetRequiredService<IWorkflowDefinitionManager>();
await workflowDefinitionManager.DeleteByDefinitionIdAsync(DeleteWorkflowClustered.DefinitionId);
WorkflowTypeDeletedFromRegistry(_scope1, DeleteWorkflowClustered.Type);
await _signalManager.WaitAsync<WorkflowDefinitionDeletedEventArgs>(WorkflowDeletedSignal);
// WorkflowTypeDeletedFromRegistry(_scope2, DeleteWorkflowClustered.Type);
// WorkflowTypeDeletedFromRegistry(_scope3, DeleteWorkflowClustered.Type);
}
private static void EnsureWorkflowInRegistry(IServiceScope scope, string type)
{
var activityRegistry = scope.ServiceProvider.GetRequiredService<IActivityRegistry>();
var descriptor = activityRegistry.Find(type);
Assert.NotNull(descriptor);
}
private static void WorkflowTypeDeletedFromRegistry(IServiceScope scope, string type)
{
var activityRegistry = scope.ServiceProvider.GetRequiredService<IActivityRegistry>();
var descriptor = activityRegistry.Find(type);
Assert.Null(descriptor);
}
private void OnWorkflowDefinitionDeleted(object? sender, WorkflowDefinitionDeletedEventArgs args)
{
if (args.DefinitionId == Workflows.DeleteWorkflow.DefinitionId)
_signalManager.Trigger(WorkflowDeletedSignal, args);
}
protected override void OnDispose()
{
_workflowDefinitionEvents.WorkflowDefinitionDeleted -= OnWorkflowDefinitionDeleted;
}
}
@@ -0,0 +1,55 @@
using Elsa.Workflows.ComponentTests.Abstractions;
using Elsa.Workflows.ComponentTests.Fixtures;
using Elsa.Workflows.Management;
using Elsa.Workflows.Management.Activities.WorkflowDefinitionActivity;
using Elsa.Workflows.Management.Models;
using Elsa.Workflows.Models;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Workflows.ComponentTests.Scenarios.WorkflowActivities;
public class SaveWorkflowTests(App app) : AppComponentTest(app)
{
[Theory(DisplayName = "Saving workflows updates ActivityRegistry")]
[InlineData("Save1", true, true, true, true)]
[InlineData("Save2", true, false, false, false)]
[InlineData("Save3", false, true, false, false)]
[InlineData("Save4", false, false, false, false)]
public async Task ActivityRegistry(string name, bool usableAsActivity, bool publish, bool expectedInRegistry, bool isBrowsable)
{
var activityRegistry = Scope.ServiceProvider.GetRequiredService<IActivityRegistry>();
var descriptor = activityRegistry.Find(name);
if (descriptor is not null)
activityRegistry.Remove(typeof(WorkflowDefinitionActivityProvider), descriptor);
var importer = Scope.ServiceProvider.GetRequiredService<IWorkflowDefinitionImporter>();
var request = new SaveWorkflowDefinitionRequest
{
Model = new WorkflowDefinitionModel
{
Name = name,
DefinitionId = name,
Options = new WorkflowOptions
{
UsableAsActivity = usableAsActivity,
AutoUpdateConsumingWorkflows = true
}
},
Publish = publish
};
await importer.ImportAsync(request);
descriptor = activityRegistry.Find(name);
if (expectedInRegistry)
{
Assert.NotNull(descriptor);
Assert.Equal(isBrowsable, descriptor.IsBrowsable);
}
else
{
Assert.Null(descriptor);
}
}
}
@@ -0,0 +1,67 @@
{
"id": "23d18a46dc3000fd",
"definitionId": "f353742a9ef6af4",
"name": "CacheInvalidation_Child",
"createdAt": "2024-05-28T08:10:31.5429337+00:00",
"version": 1,
"toolVersion": "3.2.0.0",
"variables": [],
"inputs": [],
"outputs": [],
"outcomes": [],
"customProperties": {},
"isReadonly": false,
"isSystem": false,
"isLatest": true,
"isPublished": true,
"options": {
"usableAsActivity": true,
"autoUpdateConsumingWorkflows": true
},
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "5d146ccb42668b6b",
"nodeId": "Workflow1:5d146ccb42668b6b",
"metadata": {},
"customProperties": {
"source": "FlowchartJsonConverter.cs:45",
"notFoundConnections": [],
"canStartWorkflow": false,
"runAsynchronously": false
},
"activities": [
{
"correlationId": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "Child version 1"
}
},
"id": "d719079757f5d1bf",
"nodeId": "Workflow1:5d146ccb42668b6b:d719079757f5d1bf",
"name": "Correlate1",
"type": "Elsa.Correlate",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -127.40211486816406,
"y": 526.2000122070312
},
"size": {
"width": 132.7375030517578,
"height": 49.60000228881836
}
}
}
}
],
"connections": []
}
}
@@ -0,0 +1,67 @@
{
"id": "3da9c310a071ac7b",
"definitionId": "f353742a9ef6af4",
"name": "CacheInvalidation_Child",
"createdAt": "2024-05-28T11:00:47.8186457+00:00",
"version": 2,
"toolVersion": "3.2.0.0",
"variables": [],
"inputs": [],
"outputs": [],
"outcomes": [],
"customProperties": {},
"isReadonly": false,
"isSystem": false,
"isLatest": true,
"isPublished": false,
"options": {
"usableAsActivity": true,
"autoUpdateConsumingWorkflows": true
},
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "5d146ccb42668b6b",
"nodeId": "Workflow1:5d146ccb42668b6b",
"metadata": {},
"customProperties": {
"source": "FlowchartJsonConverter.cs:45",
"notFoundConnections": [],
"canStartWorkflow": false,
"runAsynchronously": false
},
"activities": [
{
"correlationId": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "Child version 2"
}
},
"id": "d719079757f5d1bf",
"nodeId": "Workflow1:5d146ccb42668b6b:d719079757f5d1bf",
"name": "Correlate1",
"type": "Elsa.Correlate",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -127.40211486816406,
"y": 526.2000122070312
},
"size": {
"width": 132.7375030517578,
"height": 49.60000228881836
}
}
}
}
],
"connections": []
}
}
@@ -0,0 +1,188 @@
{
"id": "4b584e249fdca951",
"definitionId": "878770f04439a55d",
"name": "CacheInvalidation_Parent",
"createdAt": "2024-05-28T08:11:06.1123531+00:00",
"version": 1,
"toolVersion": "3.2.0.0",
"variables": [
{
"id": "47ab4de953db266",
"name": "CorrId",
"typeName": "String",
"isArray": false,
"storageDriverTypeName": "Elsa.Workflows.Services.WorkflowStorageDriver, Elsa.Workflows.Core"
}
],
"inputs": [],
"outputs": [],
"outcomes": [],
"customProperties": {},
"isReadonly": false,
"isSystem": false,
"isLatest": true,
"isPublished": true,
"options": {
"autoUpdateConsumingWorkflows": false
},
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "76f03074bbc8cff3",
"nodeId": "Workflow2:76f03074bbc8cff3",
"metadata": {},
"customProperties": {
"source": "FlowchartJsonConverter.cs:45",
"notFoundConnections": [],
"canStartWorkflow": false,
"runAsynchronously": false
},
"activities": [
{
"workflowDefinitionId": "f353742a9ef6af4",
"workflowDefinitionVersionId": "23d18a46dc3000fd",
"latestAvailablePublishedVersion": 1,
"latestAvailablePublishedVersionId": "23d18a46dc3000fd",
"id": "b8fe7116016141f",
"nodeId": "Workflow2:76f03074bbc8cff3:b8fe7116016141f",
"name": "CacheInvalidationChild1",
"type": "CacheInvalidationChild",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -40,
"y": 320
},
"size": {
"width": 203.9499969482422,
"height": 49.60000228881836
}
}
}
},
{
"value": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "Parent version 1"
}
},
"id": "ad9ee49c807b852e",
"nodeId": "Workflow2:76f03074bbc8cff3:ad9ee49c807b852e",
"name": "SetName1",
"type": "Elsa.SetName",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -303.3261413574219,
"y": 320
},
"size": {
"width": 137.6354217529297,
"height": 49.60000228881836
}
}
}
},
{
"path": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "test-cache-invalidation"
}
},
"supportedMethods": {
"typeName": "String[]",
"expression": {
"type": "Object",
"value": "[\u0022GET\u0022]"
}
},
"authorize": {
"typeName": "Boolean",
"expression": {
"type": "Literal",
"value": false
}
},
"policy": {
"typeName": "String",
"expression": {
"type": "Literal"
}
},
"requestTimeout": null,
"requestSizeLimit": null,
"fileSizeLimit": null,
"allowedFileExtensions": null,
"blockedFileExtensions": null,
"allowedMimeTypes": null,
"exposeRequestTooLargeOutcome": false,
"exposeFileTooLargeOutcome": false,
"exposeInvalidFileExtensionOutcome": false,
"exposeInvalidFileMimeTypeOutcome": false,
"parsedContent": null,
"files": null,
"routeData": null,
"queryStringData": null,
"headers": null,
"result": null,
"id": "109b9c25e55375ba",
"nodeId": "Workflow2:76f03074bbc8cff3:109b9c25e55375ba",
"name": "HttpEndpoint1",
"type": "Elsa.HttpEndpoint",
"version": 1,
"customProperties": {
"canStartWorkflow": true,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -622.4391784667969,
"y": 320
},
"size": {
"width": 176.0625,
"height": 49.60000228881836
}
}
}
}
],
"connections": [
{
"source": {
"activity": "ad9ee49c807b852e",
"port": "Done"
},
"target": {
"activity": "b8fe7116016141f",
"port": "In"
}
},
{
"source": {
"activity": "109b9c25e55375ba",
"port": "Done"
},
"target": {
"activity": "ad9ee49c807b852e",
"port": "In"
}
}
]
}
}
@@ -0,0 +1,25 @@
using Elsa.Scheduling.Activities;
using Elsa.Workflows.Activities;
namespace Elsa.Workflows.ComponentTests.Scenarios.WorkflowActivities.Workflows;
public class DeleteWorkflow : WorkflowBase
{
public static readonly string DefinitionId = Guid.NewGuid().ToString();
public static readonly string Type = nameof(DeleteWorkflow);
protected override void Build(IWorkflowBuilder builder)
{
builder.Name = Type;
builder.WithDefinitionId(DefinitionId);
builder.WorkflowOptions.UsableAsActivity = true;
builder.Root = new Sequence
{
Activities =
{
new Delay(TimeSpan.FromMilliseconds(250)),
new WriteLine("This workflow will be deleted!")
}
};
}
}
@@ -0,0 +1,25 @@
using Elsa.Scheduling.Activities;
using Elsa.Workflows.Activities;
namespace Elsa.Workflows.ComponentTests.Scenarios.WorkflowActivities.Workflows;
public class DeleteWorkflowClustered : WorkflowBase
{
public static readonly string DefinitionId = Guid.NewGuid().ToString();
public static readonly string Type = nameof(DeleteWorkflowClustered);
protected override void Build(IWorkflowBuilder builder)
{
builder.Name = Type;
builder.WithDefinitionId(DefinitionId);
builder.WorkflowOptions.UsableAsActivity = true;
builder.Root = new Sequence
{
Activities =
{
new Delay(TimeSpan.FromMilliseconds(250)),
new WriteLine("This workflow will be deleted!")
}
};
}
}
@@ -0,0 +1,23 @@
using System.Net;
using Elsa.Api.Client.Resources.WorkflowDefinitions.Contracts;
using Elsa.Testing.Shared.Extensions;
using Elsa.Workflows.Api.Endpoints.WorkflowDefinitions.Execute;
using Elsa.Workflows.ComponentTests.Abstractions;
using Elsa.Workflows.ComponentTests.Fixtures;
namespace Elsa.Workflows.ComponentTests.Scenarios.WorkflowCompletion;
public class WorkflowCompletionTests(App app) : AppComponentTest(app)
{
[Theory]
[InlineData("2630068018ac1f0a")]
[InlineData("5590069018aa4f0e")]
public async Task Workflow_ShouldComplete(string workflowDefinitionId)
{
var client = WorkflowServer.CreateApiClient<IExecuteWorkflowApi>();
using var response = await client.ExecuteAsync(workflowDefinitionId);
var model = await response.ReadAsJsonAsync<Response>(WorkflowServer.Services);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(WorkflowSubStatus.Finished, model.WorkflowState.SubStatus);
}
}
@@ -0,0 +1,189 @@
{
"id": "5590069018aa4f0e",
"definitionId": "5590069018aa4f0e",
"name": "Fork",
"createdAt": "2024-04-20T20:52:32.29327+00:00",
"version": 2,
"toolVersion": "3.2.0.0",
"variables": [],
"inputs": [],
"outputs": [],
"outcomes": [],
"customProperties": {
"Elsa:WorkflowContextProviderTypes": []
},
"isReadonly": false,
"isSystem": false,
"isLatest": true,
"isPublished": true,
"options": {
"autoUpdateConsumingWorkflows": false
},
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "c74056c7f1f4a3",
"nodeId": "Workflow1:c74056c7f1f4a3",
"metadata": {},
"customProperties": {
"source": "FlowchartJsonConverter.cs:45",
"notFoundConnections": [],
"canStartWorkflow": false,
"runAsynchronously": false
},
"activities": [
{
"branches": {
"typeName": "String[]",
"expression": {
"type": "Object",
"value": "[\u0022One\u0022,\u0022Two\u0022,\u0022Three\u0022]"
}
},
"id": "c0e09e3ac3cfdba7",
"nodeId": "Workflow1:c74056c7f1f4a3:c0e09e3ac3cfdba7",
"name": "FlowFork1",
"type": "Elsa.FlowFork",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -340,
"y": -197
},
"size": {
"width": 145.5625,
"height": 50
}
}
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "One"
}
},
"id": "e6f0cddb1cdbdd7c",
"nodeId": "Workflow1:c74056c7f1f4a3:e6f0cddb1cdbdd7c",
"name": "WriteLine1",
"type": "Elsa.WriteLine",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -80,
"y": -300
},
"size": {
"width": 139.296875,
"height": 50
}
}
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "Two"
}
},
"id": "3ece093dfc20c752",
"nodeId": "Workflow1:c74056c7f1f4a3:3ece093dfc20c752",
"name": "WriteLine2",
"type": "Elsa.WriteLine",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -80,
"y": -197
},
"size": {
"width": 139.296875,
"height": 50
}
}
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "Three"
}
},
"id": "ca9fcd1d6069df9",
"nodeId": "Workflow1:c74056c7f1f4a3:ca9fcd1d6069df9",
"name": "WriteLine3",
"type": "Elsa.WriteLine",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -80,
"y": -102
},
"size": {
"width": 139.296875,
"height": 50
}
}
}
}
],
"connections": [
{
"source": {
"activity": "c0e09e3ac3cfdba7",
"port": "One"
},
"target": {
"activity": "e6f0cddb1cdbdd7c",
"port": "In"
}
},
{
"source": {
"activity": "c0e09e3ac3cfdba7",
"port": "Two"
},
"target": {
"activity": "3ece093dfc20c752",
"port": "In"
}
},
{
"source": {
"activity": "c0e09e3ac3cfdba7",
"port": "Three"
},
"target": {
"activity": "ca9fcd1d6069df9",
"port": "In"
}
}
]
}
}
@@ -0,0 +1,42 @@
{
"id": "2630068018ac1f0a",
"definitionId": "2630068018ac1f0a",
"name": "Hello World",
"isLatest": true,
"isPublished": true,
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "969b0703a9379c3b",
"nodeId": "Workflow1:969b0703a9379c3b",
"activities": [
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "Hello World!"
}
},
"id": "b039045bb7443e57",
"nodeId": "Workflow1:969b0703a9379c3b:b039045bb7443e57",
"name": "WriteLine1",
"type": "Elsa.WriteLine",
"version": 1,
"metadata": {
"designer": {
"position": {
"x": -231.796875,
"y": 269
},
"size": {
"width": 139.296875,
"height": 50
}
}
}
}
],
"connections": []
}
}
@@ -0,0 +1,36 @@
using System.Net;
using Elsa.Testing.Shared.Services;
using Elsa.Workflows.ComponentTests.Abstractions;
using Elsa.Workflows.ComponentTests.Fixtures;
using Elsa.Workflows.Runtime;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Workflows.ComponentTests.Scenarios.WorkflowDefinitionRefresh;
public class DynamicEndpointTests : AppComponentTest
{
private readonly IWorkflowDefinitionsRefresher _workflowDefinitionsRefresher;
public DynamicEndpointTests(App app) : base(app)
{
StaticValueHolder.Value = "first-value";
_workflowDefinitionsRefresher = Scope.ServiceProvider.GetRequiredService<IWorkflowDefinitionsRefresher>();
}
[Fact]
public async Task ChangingEndpointValueThenRefresh_WorkflowShouldRespondToTheNewValue()
{
var client = WorkflowServer.CreateHttpWorkflowClient();
var firstResponse = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "first-value"));
StaticValueHolder.Value = "second-value";
var _ = await _workflowDefinitionsRefresher.RefreshWorkflowDefinitionsAsync(
new Runtime.Requests.RefreshWorkflowDefinitionsRequest() { DefinitionIds = ["f69f061159adc3ae"] }, CancellationToken.None);
var secondResponse = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "second-value"));
Assert.Equal(HttpStatusCode.OK, firstResponse.StatusCode);
Assert.Equal(HttpStatusCode.OK, secondResponse.StatusCode);
}
}
@@ -0,0 +1,104 @@
{
"id": "69cb6deaa8e20599",
"definitionId": "f69f061159adc3ae",
"name": "Workflow 1",
"createdAt": "2024-07-04T13:41:33.1905134+00:00",
"version": 1,
"toolVersion": "3.3.0.0",
"variables": [],
"inputs": [],
"outputs": [],
"outcomes": [],
"customProperties": {
"Elsa:WorkflowContextProviderTypes": []
},
"isReadonly": false,
"isSystem": false,
"isLatest": true,
"isPublished": true,
"options": {
"autoUpdateConsumingWorkflows": false
},
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "1bac532ee993dfdc",
"nodeId": "Workflow1:1bac532ee993dfdc",
"metadata": {},
"customProperties": {
"source": "FlowchartJsonConverter.cs:45",
"notFoundConnections": [],
"canStartWorkflow": false,
"runAsynchronously": false
},
"activities": [
{
"path": {
"typeName": "String",
"expression": {
"type": "JavaScript",
"value": "return getStaticValue() ?? \u0022first-value\u0022;"
}
},
"supportedMethods": {
"typeName": "String[]",
"expression": {
"type": "Literal",
"value": "[\u0022GET\u0022]"
}
},
"authorize": {
"typeName": "Boolean",
"expression": {
"type": "Literal",
"value": false
}
},
"policy": {
"typeName": "String",
"expression": {
"type": "Literal"
}
},
"requestTimeout": null,
"requestSizeLimit": null,
"fileSizeLimit": null,
"allowedFileExtensions": null,
"blockedFileExtensions": null,
"allowedMimeTypes": null,
"exposeRequestTooLargeOutcome": false,
"exposeFileTooLargeOutcome": false,
"exposeInvalidFileExtensionOutcome": false,
"exposeInvalidFileMimeTypeOutcome": false,
"parsedContent": null,
"files": null,
"routeData": null,
"queryStringData": null,
"headers": null,
"result": null,
"id": "25d3a6465094a796",
"nodeId": "Workflow1:1bac532ee993dfdc:25d3a6465094a796",
"name": "HttpEndpoint1",
"type": "Elsa.HttpEndpoint",
"version": 1,
"customProperties": {
"canStartWorkflow": true,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -260,
"y": 460
},
"size": {
"width": 176.390625,
"height": 50
}
}
}
}
],
"connections": []
}
}
@@ -0,0 +1,119 @@
using System.Net;
using Elsa.Common.Models;
using Elsa.Workflows.Activities;
using Elsa.Workflows.ComponentTests.Abstractions;
using Elsa.Workflows.ComponentTests.Fixtures;
using Elsa.Workflows.ComponentTests.Materializers;
using Elsa.Workflows.ComponentTests.WorkflowProviders;
using Elsa.Workflows.Management;
using Elsa.Workflows.Runtime;
using Humanizer;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Workflows.ComponentTests.Scenarios.WorkflowDefinitionReload;
public class ReloadWorkflowTests : AppComponentTest
{
private readonly IActivityRegistry _activityRegistry;
private readonly TestWorkflowProvider _testWorkflowProvider;
private readonly IWorkflowBuilderFactory _workflowBuilderFactory;
private readonly IWorkflowDefinitionManager _workflowDefinitionManager;
private readonly IWorkflowDefinitionService _workflowDefinitionService;
private readonly IWorkflowDefinitionsReloader _workflowDefinitionsReloader;
public ReloadWorkflowTests(App app) : base(app)
{
_workflowDefinitionManager = Scope.ServiceProvider.GetRequiredService<IWorkflowDefinitionManager>();
_workflowDefinitionsReloader = Scope.ServiceProvider.GetRequiredService<IWorkflowDefinitionsReloader>();
_workflowBuilderFactory = Scope.ServiceProvider.GetRequiredService<IWorkflowBuilderFactory>();
_workflowDefinitionService = Scope.ServiceProvider.GetRequiredService<IWorkflowDefinitionService>();
_activityRegistry = Scope.ServiceProvider.GetRequiredService<IActivityRegistry>();
var workflowsProviders = Scope.ServiceProvider.GetRequiredService<IEnumerable<IWorkflowsProvider>>();
_testWorkflowProvider = (TestWorkflowProvider)workflowsProviders.First(x => x is TestWorkflowProvider);
}
[Fact]
public async Task Reloading_AfterRemovingTheWorkflow_ShouldMakeWorkflowReachableAgain()
{
var client = WorkflowServer.CreateHttpWorkflowClient();
await _workflowDefinitionManager.DeleteByDefinitionIdAsync("f68b09bc-2013-4617-b82f-d76b6819a624", CancellationToken.None);
var firstResponse = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "reload-test"));
await _workflowDefinitionsReloader.ReloadWorkflowDefinitionsAsync();
var secondResponse = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "reload-test"));
Assert.Equal(HttpStatusCode.NotFound, firstResponse.StatusCode);
Assert.Equal(HttpStatusCode.OK, secondResponse.StatusCode);
}
[Fact]
public async Task Reloading_AfterUpdatingSourceProvider_ShouldRefreshCaches()
{
var definitionId = Guid.NewGuid().ToString();
var definitionVersionId1 = Guid.NewGuid().ToString();
var workflowV1 = await BuildWorkflowAsync(definitionId, definitionVersionId1, 1);
// Set up the initial workflow version.
_testWorkflowProvider.MaterializedWorkflows = [workflowV1];
await _workflowDefinitionsReloader.ReloadWorkflowDefinitionsAsync();
var definitionV1 = await _workflowDefinitionService.FindWorkflowGraphAsync(definitionId, VersionOptions.Latest);
Assert.Equal(definitionVersionId1, definitionV1!.Workflow.Identity.Id);
// Simulate the workflow provider to have a new version available.
var definitionVersionId2 = Guid.NewGuid().ToString();
var workflowV2 = await BuildWorkflowAsync(definitionId, definitionVersionId2, 2);
_testWorkflowProvider.MaterializedWorkflows = [workflowV1, workflowV2];
// Reload the workflow definitions.
await _workflowDefinitionsReloader.ReloadWorkflowDefinitionsAsync();
// Assert that the workflow definition service finds the updated workflow version.
var definitionV2 = await _workflowDefinitionService.FindWorkflowGraphAsync(definitionId, VersionOptions.Latest);
Assert.Equal(definitionVersionId2, definitionV2!.Workflow.Identity.Id);
// Cleanup: Delete the workflow definition and its versions.
await _workflowDefinitionManager.DeleteByDefinitionIdAsync(definitionId, CancellationToken.None);
}
[Fact]
public async Task Reloading_AfterUpdatingSourceProvider_ShouldRefreshActivityRegistry()
{
var definitionId = Guid.NewGuid().ToString();
var definitionVersionId1 = Guid.NewGuid().ToString();
var workflowV1 = await BuildWorkflowAsync(definitionId, definitionVersionId1, 1);
// Set up the initial workflow version.
_testWorkflowProvider.MaterializedWorkflows = [workflowV1];
await _workflowDefinitionsReloader.ReloadWorkflowDefinitionsAsync();
var activityTypeName = workflowV1.Workflow.Name.Pascalize();
var activityV1 = _activityRegistry.Find(activityTypeName);
Assert.Equal(1, activityV1!.Version);
// Simulate the workflow provider to have a new version available.
var definitionVersionId2 = Guid.NewGuid().ToString();
var workflowV2 = await BuildWorkflowAsync(definitionId, definitionVersionId2, 2);
_testWorkflowProvider.MaterializedWorkflows = [workflowV1, workflowV2];
// Reload the workflow definitions.
await _workflowDefinitionsReloader.ReloadWorkflowDefinitionsAsync();
// Assert that the activity registry contains a new activity descriptor representing the new workflow version.
var activityV2 = _activityRegistry.Find(activityTypeName)!;
Assert.Equal(2, activityV2.Version);
// Cleanup: Delete the workflow definition and its versions.
await _workflowDefinitionManager.DeleteByDefinitionIdAsync(definitionId, CancellationToken.None);
}
private async Task<MaterializedWorkflow> BuildWorkflowAsync(string definitionId, string definitionVersionId, int version)
{
var builder = _workflowBuilderFactory.CreateBuilder();
builder.DefinitionId = definitionId;
builder.Id = definitionVersionId;
builder.Version = version;
builder.Name = definitionId;
builder.Root = new WriteLine($"Version {version}");
builder.WorkflowOptions.UsableAsActivity = true;
var workflow = await builder.BuildWorkflowAsync();
workflow.Name = definitionId;
return new MaterializedWorkflow(workflow, _testWorkflowProvider.Name, TestWorkflowMaterializer.MaterializerName);
}
}
@@ -0,0 +1,104 @@
{
"id": "c2e19917-d03e-41ed-9d19-ef5c1ddc9871",
"definitionId": "f68b09bc-2013-4617-b82f-d76b6819a624",
"name": "Workflow 1",
"createdAt": "2024-07-04T13:41:33.1905134+00:00",
"version": 1,
"toolVersion": "3.3.0.0",
"variables": [],
"inputs": [],
"outputs": [],
"outcomes": [],
"customProperties": {
"Elsa:WorkflowContextProviderTypes": []
},
"isReadonly": false,
"isSystem": false,
"isLatest": true,
"isPublished": true,
"options": {
"autoUpdateConsumingWorkflows": false
},
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "f8019bae-c506-4467-812b-e403e7aad8ff",
"nodeId": "Workflow1:f8019bae-c506-4467-812b-e403e7aad8ff",
"metadata": {},
"customProperties": {
"source": "FlowchartJsonConverter.cs:45",
"notFoundConnections": [],
"canStartWorkflow": false,
"runAsynchronously": false
},
"activities": [
{
"path": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "reload-test"
}
},
"supportedMethods": {
"typeName": "String[]",
"expression": {
"type": "Literal",
"value": "[\u0022GET\u0022]"
}
},
"authorize": {
"typeName": "Boolean",
"expression": {
"type": "Literal",
"value": false
}
},
"policy": {
"typeName": "String",
"expression": {
"type": "Literal"
}
},
"requestTimeout": null,
"requestSizeLimit": null,
"fileSizeLimit": null,
"allowedFileExtensions": null,
"blockedFileExtensions": null,
"allowedMimeTypes": null,
"exposeRequestTooLargeOutcome": false,
"exposeFileTooLargeOutcome": false,
"exposeInvalidFileExtensionOutcome": false,
"exposeInvalidFileMimeTypeOutcome": false,
"parsedContent": null,
"files": null,
"routeData": null,
"queryStringData": null,
"headers": null,
"result": null,
"id": "cc196a5e-04f7-4794-b25c-1a0987632d8a",
"nodeId": "Workflow1:f8019bae-c506-4467-812b-e403e7aad8ff:cc196a5e-04f7-4794-b25c-1a0987632d8a",
"name": "HttpEndpoint1",
"type": "Elsa.HttpEndpoint",
"version": 1,
"customProperties": {
"canStartWorkflow": true,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -260,
"y": 460
},
"size": {
"width": 176.390625,
"height": 50
}
}
}
}
],
"connections": []
}
}
@@ -0,0 +1,32 @@
using Elsa.Api.Client.Resources.WorkflowDefinitions.Contracts;
using Elsa.Workflows.ComponentTests.Abstractions;
using Elsa.Workflows.ComponentTests.Fixtures;
namespace Elsa.Workflows.ComponentTests.Scenarios.WorkflowJsonStructures;
public class WorkflowJsonStructureTests(App app) : AppComponentTest(app)
{
private const string MainDefinitionId = "a417fadbbb7c417e";
private const string Sub1NodeId = "Workflow2:4d5af7585eece1d7:f19ae76011a020f3";
[Fact]
public async Task Workflow_ContainingWorkflowActivity_ShouldNotIncludeChildrenOfWorkflowActivity()
{
var client = WorkflowServer.CreateApiClient<IWorkflowDefinitionsApi>();
var workflowDefinition = (await client.GetByIdAsync(MainDefinitionId))!;
var root = workflowDefinition.Root;
var subActivity = root["activities"]![0]!;
Assert.Null(subActivity["root"]);
}
[Fact]
public async Task Requesting_Subgraph_Returns_ExpectedSubgraph()
{
var client = WorkflowServer.CreateApiClient<IWorkflowDefinitionsApi>();
var subgraph = (await client.GetSubgraphAsync(MainDefinitionId, Sub1NodeId))!;
Assert.Equal(Sub1NodeId, subgraph.Activity["nodeId"]!.ToString());
var writeLine = subgraph.Activity["root"]!["root"]!["activities"]![0]!;
Assert.Equal("Elsa.WriteLine", writeLine["type"]!.ToString());
}
}
@@ -0,0 +1,103 @@
{
"id": "a417fadbbb7c417e",
"definitionId": "ebe87d0f1d1924ea",
"name": "Main",
"createdAt": "2024-06-20T15:08:33.469865+00:00",
"version": 1,
"toolVersion": "3.3.0.0",
"variables": [],
"inputs": [],
"outputs": [],
"outcomes": [],
"customProperties": {
"Elsa:WorkflowContextProviderTypes": []
},
"isReadonly": false,
"isSystem": false,
"isLatest": true,
"isPublished": true,
"options": {
"autoUpdateConsumingWorkflows": false
},
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "4d5af7585eece1d7",
"nodeId": "Workflow2:4d5af7585eece1d7",
"metadata": {},
"customProperties": {
"source": "FlowchartJsonConverter.cs:45",
"notFoundConnections": [],
"canStartWorkflow": false,
"runAsynchronously": false
},
"activities": [
{
"workflowDefinitionId": "497be45e7d647e8f",
"workflowDefinitionVersionId": "e91d58c7a7ca1861",
"latestAvailablePublishedVersion": 1,
"latestAvailablePublishedVersionId": "e91d58c7a7ca1861",
"id": "f19ae76011a020f3",
"nodeId": "Workflow2:4d5af7585eece1d7:f19ae76011a020f3",
"name": "Sub1",
"type": "Sub",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -235.796875,
"y": -74
},
"size": {
"width": 58.7421875,
"height": 50
}
}
}
},
{
"workflowDefinitionId": "497be45e7d647e8f",
"workflowDefinitionVersionId": "e91d58c7a7ca1861",
"latestAvailablePublishedVersion": 1,
"latestAvailablePublishedVersionId": "e91d58c7a7ca1861",
"id": "ad4d54104a443b5b",
"nodeId": "Workflow2:4d5af7585eece1d7:ad4d54104a443b5b",
"name": "Sub2",
"type": "Sub",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -101.796875,
"y": -74
},
"size": {
"width": 58.7421875,
"height": 50
}
}
}
}
],
"connections": [
{
"source": {
"activity": "f19ae76011a020f3",
"port": "Done"
},
"target": {
"activity": "ad4d54104a443b5b",
"port": "In"
}
}
]
}
}
@@ -0,0 +1,69 @@
{
"id": "e91d58c7a7ca1861",
"definitionId": "497be45e7d647e8f",
"name": "Sub",
"createdAt": "2024-06-20T15:08:33.474045+00:00",
"version": 1,
"toolVersion": "3.3.0.0",
"variables": [],
"inputs": [],
"outputs": [],
"outcomes": [],
"customProperties": {
"Elsa:WorkflowContextProviderTypes": []
},
"isReadonly": false,
"isSystem": false,
"isLatest": true,
"isPublished": true,
"options": {
"usableAsActivity": true,
"autoUpdateConsumingWorkflows": true
},
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "dc487ec63d2e8520",
"nodeId": "Workflow1:dc487ec63d2e8520",
"metadata": {},
"customProperties": {
"source": "FlowchartJsonConverter.cs:45",
"notFoundConnections": [],
"canStartWorkflow": false,
"runAsynchronously": false
},
"activities": [
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "Test"
}
},
"id": "f0f7dfe68eda4744",
"nodeId": "Workflow1:dc487ec63d2e8520:f0f7dfe68eda4744",
"name": "WriteLine1",
"type": "Elsa.WriteLine",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -55.796875,
"y": -171
},
"size": {
"width": 139.296875,
"height": 50
}
}
}
}
],
"connections": []
}
}