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,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\common\Elsa.Testing.Shared.Integration\Elsa.Testing.Shared.Integration.csproj" />
<ProjectReference Include="..\..\..\src\common\Elsa.Testing.Shared\Elsa.Testing.Shared.csproj" />
<ProjectReference Include="..\..\..\src\modules\Elsa.Workflows.Core\Elsa.Workflows.Core.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,27 @@
using Elsa.Testing.Shared;
using Elsa.Workflows.Activities;
using Elsa.Workflows.Models;
using Xunit.Abstractions;
namespace Elsa.Activities.IntegrationTests;
public class SetNameTests
{
private readonly CapturingTextWriter _capturingTextWriter = new();
private readonly IServiceProvider _serviceProvider;
public SetNameTests(ITestOutputHelper testOutputHelper)
{
_serviceProvider = new TestApplicationBuilder(testOutputHelper).WithCapturingTextWriter(_capturingTextWriter).Build();
}
[Fact(DisplayName = "SetName sets the workflow instance name.")]
public async Task Test1()
{
const string expectedName = "Foo";
var setName = new SetName(new Input<string>(expectedName));
var result = await _serviceProvider.RunActivityAsync(setName);
var actualName = result.WorkflowState.Name;
Assert.Equal(expectedName, actualName);
}
}
@@ -0,0 +1 @@
global using Xunit;
@@ -0,0 +1,25 @@
using Elsa.Testing.Shared;
using Elsa.Workflows.Activities;
using Xunit.Abstractions;
namespace Elsa.Activities.IntegrationTests;
public class WriteLineTests
{
private readonly CapturingTextWriter _capturingTextWriter = new();
private readonly IServiceProvider _serviceProvider;
public WriteLineTests(ITestOutputHelper testOutputHelper)
{
_serviceProvider = new TestApplicationBuilder(testOutputHelper).WithCapturingTextWriter(_capturingTextWriter).Build();
}
[Fact(DisplayName = "WriteLine prints the expected line to the console.")]
public async Task Test1()
{
const string expectedLine = "Hello world!";
var writeLine = new WriteLine(expectedLine);
await _serviceProvider.RunActivityAsync(writeLine);
Assert.Equal(expectedLine, _capturingTextWriter.Lines.Single());
}
}
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\..\..\src\common\Elsa.Testing.Shared.Integration\Elsa.Testing.Shared.Integration.csproj" />
<ProjectReference Include="..\..\..\src\modules\Elsa.Alterations\Elsa.Alterations.csproj" />
<ProjectReference Include="..\..\..\src\modules\Elsa.Workflows.Runtime\Elsa.Workflows.Runtime.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Workflows\" />
</ItemGroup>
<ItemGroup>
<None Update="Workflows\version-1.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Workflows\version-2.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
@@ -0,0 +1,73 @@
using Elsa.Alterations.AlterationTypes;
using Elsa.Alterations.Core.Contracts;
using Elsa.Alterations.Extensions;
using Elsa.Common.Models;
using Elsa.Extensions;
using Elsa.Testing.Shared;
using Elsa.Workflows.Management;
using Microsoft.Extensions.DependencyInjection;
using Xunit.Abstractions;
namespace Elsa.Alterations.IntegrationTests;
/// <summary>
/// Contains tests for migration.
/// </summary>
public class MigrationTests
{
private readonly CapturingTextWriter _capturingTextWriter = new();
private readonly IServiceProvider _services;
private readonly IAlterationRunner _alterationRunner;
private readonly IWorkflowInstanceStore _workflowInstanceStore;
/// <summary>
/// Initializes a new instance of the <see cref="MigrationTests"/> class.
/// </summary>
public MigrationTests(ITestOutputHelper testOutputHelper)
{
_services = new TestApplicationBuilder(testOutputHelper)
.WithCapturingTextWriter(_capturingTextWriter)
.ConfigureElsa(elsa => elsa.UseAlterations())
.Build();
_alterationRunner = _services.GetRequiredService<IAlterationRunner>();
_workflowInstanceStore = _services.GetRequiredService<IWorkflowInstanceStore>();
}
/// <summary>
/// This method tests the migration from version 1 to version 2.
/// </summary>
[Fact(DisplayName = "Migrating from version 1 to 2 succeeds")]
public async Task Test1()
{
// Populate registries.
await _services.PopulateRegistriesAsync();
// Import workflows.
await _services.ImportWorkflowDefinitionAsync("Workflows/version-1.json");
await _services.ImportWorkflowDefinitionAsync("Workflows/version-2.json");
// Run version 1 workflow.
var workflowState = await _services.RunWorkflowUntilEndAsync("my-workflow", versionOptions: VersionOptions.SpecificVersion(1));
// Migrate to version 2.
var alterations = new IAlteration[]
{
new Migrate
{
TargetVersion = 2
}
};
var instanceIds = new[]
{
workflowState.Id
};
var migrationResult = await _alterationRunner.RunAsync(instanceIds, alterations);
// Assert success.
Assert.True(migrationResult.All(x => x.IsSuccessful));
// Assert that the workflow instance is now at version 2.
var workflowState2 = await _workflowInstanceStore.FindAsync(workflowState.Id);
Assert.Equal(2, workflowState2!.Version);
}
}
@@ -0,0 +1 @@
global using Xunit;
@@ -0,0 +1,41 @@
{
"id": "b760f121e7dc5dae",
"definitionId": "my-workflow",
"name": "Version 1",
"version": 1,
"toolVersion": "3.0.0.0",
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "52ae1d65f7896a9e",
"nodeId": "Workflow1:52ae1d65f7896a9e",
"activities": [
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "Version 1"
}
},
"id": "3a1ab61a351fe232",
"nodeId": "Workflow1:52ae1d65f7896a9e:3a1ab61a351fe232",
"name": "WriteLine1",
"type": "Elsa.WriteLine",
"metadata": {
"designer": {
"position": {
"x": -121.796875,
"y": 272
},
"size": {
"width": 139.296875,
"height": 50
}
}
}
}
],
"connections": []
}
}
@@ -0,0 +1,41 @@
{
"id": "4aca2e3aa79475d3",
"definitionId": "my-workflow",
"name": "Version 2",
"version": 2,
"toolVersion": "3.0.0.0",
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "52ae1d65f7896a9e",
"nodeId": "Workflow1:52ae1d65f7896a9e",
"activities": [
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "Version 2"
}
},
"id": "3a1ab61a351fe232",
"nodeId": "Workflow1:52ae1d65f7896a9e:3a1ab61a351fe232",
"name": "WriteLine1",
"type": "Elsa.WriteLine",
"metadata": {
"designer": {
"position": {
"x": -121.796875,
"y": 272
},
"size": {
"width": 139.296875,
"height": 50
}
}
}
}
],
"connections": []
}
}
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\common\Elsa.Testing.Shared.Integration\Elsa.Testing.Shared.Integration.csproj" />
<ProjectReference Include="..\..\..\src\common\Elsa.Testing.Shared\Elsa.Testing.Shared.csproj" />
<ProjectReference Include="..\..\..\src\modules\Elsa.JavaScript\Elsa.JavaScript.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,61 @@
using System.Dynamic;
using System.Numerics;
using System.Text.Json;
using Elsa.Common.Converters;
using Elsa.Expressions.Models;
using Elsa.JavaScript.Contracts;
using Elsa.Testing.Shared;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
using Xunit.Abstractions;
namespace Elsa.JavaScript.IntegrationTests;
public class JsonConverterTest(ITestOutputHelper testOutputHelper)
{
private readonly IServiceProvider _serviceProvider = new TestApplicationBuilder(testOutputHelper).Build();
[Fact(DisplayName = "JavaScript BigInt mapping to BigInteger serialization")]
public async Task Test1()
{
var javaScriptEvaluator = _serviceProvider.GetRequiredService<IJavaScriptEvaluator>();
var script = @"return {
'BigNumber': BigInt('7239948466988781569')
}";
var expressionExecutionContext = new ExpressionExecutionContext(_serviceProvider, new MemoryRegister());
var result = (await javaScriptEvaluator.EvaluateAsync(script, typeof(ExpandoObject), expressionExecutionContext))!;
var options = new JsonSerializerOptions()
{
Converters = { new BigIntegerJsonConverter() }
};
var serializedText = JsonSerializer.Serialize(result, options);
Assert.Equal("{\"BigNumber\":7239948466988781569}", serializedText);
}
[Fact(DisplayName = "JavaScript BigInt mapping to BigInteger serialization")]
public async Task Test2()
{
var javaScriptEvaluator = _serviceProvider.GetRequiredService<IJavaScriptEvaluator>();
var script = @"return {
'BigNumber': BigInt('7239948466988781569')
}";
var expressionExecutionContext = new ExpressionExecutionContext(_serviceProvider, new MemoryRegister());
var result = (await javaScriptEvaluator.EvaluateAsync(script, typeof(ExpandoObject), expressionExecutionContext))!;
var serializedText = JsonSerializer.Serialize(result);
Assert.Equal("{\"BigNumber\":{\"IsPowerOfTwo\":false,\"IsZero\":false,\"IsOne\":false,\"IsEven\":false,\"Sign\":1}}", serializedText);
}
[Fact(DisplayName = "BigIntegerJsonConverter Deserialize")]
public async Task Test3()
{
var options = new JsonSerializerOptions()
{
Converters = { new BigIntegerJsonConverter() }
};
BigInteger bigInteger = JsonSerializer.Deserialize<BigInteger>("7239948466988781569", options);
Assert.Equal(7239948466988781569, bigInteger);
}
}
@@ -0,0 +1,136 @@
using System.Text.Json;
using Elsa.Expressions.Models;
using Elsa.JavaScript.Contracts;
using Elsa.Testing.Shared;
using Elsa.Workflows.Memory;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
using Xunit.Abstractions;
namespace Elsa.JavaScript.IntegrationTests;
public class JsonElementConverterTests(ITestOutputHelper testOutputHelper)
{
private readonly IServiceProvider _serviceProvider = new TestApplicationBuilder(testOutputHelper).Build();
[Fact(DisplayName = "JsonElement JsonObject can be passed to JavaScript")]
public async Task TestJsonObjectPassedAsJsonElement()
{
var javaScriptEvaluator = _serviceProvider.GetRequiredService<IJavaScriptEvaluator>();
var expressionExecutionContext = new ExpressionExecutionContext(_serviceProvider, new());
var jsonVariable = new Variable<object>("JsonVariable", "");
var jsonString = "{\"name\": \"John\", \"age\": 30}";
var jsonElement = JsonSerializer.Deserialize<JsonElement>(jsonString);
jsonVariable.Set(expressionExecutionContext, jsonElement);
var script = "getVariable('JsonVariable').age";
var result = await javaScriptEvaluator.EvaluateAsync(script, typeof(int), expressionExecutionContext);
Assert.Equal(30, result);
}
[Fact(DisplayName = "JsonElement JsonArray can be passed to JavaScript")]
public async Task TestJsonArrayPassedAsJsonElement()
{
var javaScriptEvaluator = _serviceProvider.GetRequiredService<IJavaScriptEvaluator>();
var expressionExecutionContext = new ExpressionExecutionContext(_serviceProvider, new());
var jsonVariable = new Variable<object>("JsonVariable", "");
var jsonString = "[1, 2, 3, 4, 5, 6]";
var jsonElement = JsonSerializer.Deserialize<JsonElement>(jsonString);
jsonVariable.Set(expressionExecutionContext, jsonElement);
var script = "getVariable('JsonVariable')[3]";
var result = await javaScriptEvaluator.EvaluateAsync(script, typeof(int), expressionExecutionContext);
Assert.Equal(4, result);
}
[Fact(DisplayName = "JsonElement string can be passed to JavaScript")]
public async Task TestStringPassedAsJsonElement()
{
var javaScriptEvaluator = _serviceProvider.GetRequiredService<IJavaScriptEvaluator>();
var expressionExecutionContext = new ExpressionExecutionContext(_serviceProvider, new());
var jsonVariable = new Variable<object>("JsonVariable", "");
var jsonString = "\"I'm just a string\"";
var jsonElement = JsonSerializer.Deserialize<JsonElement>(jsonString);
jsonVariable.Set(expressionExecutionContext, jsonElement);
var script = "getVariable('JsonVariable')";
var result = await javaScriptEvaluator.EvaluateAsync(script, typeof(string), expressionExecutionContext);
Assert.Equal("I'm just a string", result);
}
[Fact(DisplayName = "JsonElement boolean can be passed to JavaScript")]
public async Task TestBooleanPassedAsJsonElement()
{
var javaScriptEvaluator = _serviceProvider.GetRequiredService<IJavaScriptEvaluator>();
var expressionExecutionContext = new ExpressionExecutionContext(_serviceProvider, new());
var jsonVariable = new Variable<object>("JsonVariable", "");
var jsonString = "false";
var jsonElement = JsonSerializer.Deserialize<JsonElement>(jsonString);
jsonVariable.Set(expressionExecutionContext, jsonElement);
var script = "getVariable('JsonVariable')";
var result = await javaScriptEvaluator.EvaluateAsync(script, typeof(bool), expressionExecutionContext);
Assert.Equal(false, result);
}
[Fact(DisplayName = "JsonElement containing nested Json objects and arrays be passed to JavaScript")]
public async Task TestNestedJsonPassedAsJsonElement()
{
var javaScriptEvaluator = _serviceProvider.GetRequiredService<IJavaScriptEvaluator>();
var expressionExecutionContext = new ExpressionExecutionContext(_serviceProvider, new());
var jsonVariable = new Variable<object>("JsonVariable", "");
var jsonString = @"
{
""name"": ""John Doe"",
""age"": 35,
""address"": {
""street"": ""123 Main St"",
""city"": ""Springfield"",
""zip"": ""12345"",
""coordinates"": { ""lat"": 40.7128, ""lng"": -74.006 }
},
""skills"": [
{ ""name"": ""Programming"", ""level"": ""Advanced"" },
{ ""name"": ""Writing"", ""level"": ""Intermediate"" }
],
""projects"": [
{
""title"": ""Project A"",
""status"": ""Completed"",
""team"": [""Alice"", ""Bob""]
},
{
""title"": ""Project B"",
""status"": ""In Progress"",
""team"": [""Charlie"", ""David"", ""Eve""]
}
],
""isEmployed"": true,
""contact"": { ""email"": ""john.doe@example.com"", ""phone"": ""555-1234"" }
}";
var jsonElement = JsonSerializer.Deserialize<JsonElement>(jsonString);
jsonVariable.Set(expressionExecutionContext, jsonElement);
var script = "getVariable('JsonVariable').projects[1].team[0]";
var result = await javaScriptEvaluator.EvaluateAsync(script, typeof(string), expressionExecutionContext);
Assert.Equal("Charlie", result);
var script2 = "getVariable('JsonVariable').isEmployed";
var result2 = await javaScriptEvaluator.EvaluateAsync(script2, typeof(bool), expressionExecutionContext);
Assert.Equal(true, result2);
var script3 = "getVariable('JsonVariable').skills[0].level";
var result3 = await javaScriptEvaluator.EvaluateAsync(script3, typeof(string), expressionExecutionContext);
Assert.Equal("Advanced", result3);
var script4 = "getVariable('JsonVariable').address.coordinates.lat";
var result4 = await javaScriptEvaluator.EvaluateAsync(script4, typeof(double), expressionExecutionContext);
Assert.Equal(40.7128, result4);
var script5 = "getVariable('JsonVariable').age";
var result5 = await javaScriptEvaluator.EvaluateAsync(script5, typeof(int), expressionExecutionContext);
Assert.Equal(35, result5);
}
}
@@ -0,0 +1,45 @@
using System.Text.Json;
using Elsa.Expressions.Models;
using Elsa.JavaScript.Contracts;
using Elsa.Testing.Shared;
using Elsa.Workflows.Memory;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
using Xunit.Abstractions;
namespace Elsa.JavaScript.IntegrationTests;
public class ToJsonTests(ITestOutputHelper testOutputHelper)
{
private readonly IServiceProvider _serviceProvider = new TestApplicationBuilder(testOutputHelper).Build();
[Fact(DisplayName = "Serialize large unicode string using JavaScript's toJson function")]
public async Task Test1()
{
var javaScriptEvaluator = _serviceProvider.GetRequiredService<IJavaScriptEvaluator>();
var unicodeString = UnicodeRangeGenerator.GenerateUnicodeString();
var script = $"toJson({{ text: '{unicodeString}' }})";
var expressionExecutionContext = new ExpressionExecutionContext(_serviceProvider, new MemoryRegister());
var result = (string)(await javaScriptEvaluator.EvaluateAsync(script, typeof(string), expressionExecutionContext))!;
var serializedText = JsonDocument.Parse(result).RootElement.GetProperty("text").GetString();
Assert.Equal(unicodeString, serializedText);
}
[Fact(DisplayName = "Serialize large unicode string using JavaScript's toJson function from a workflow variable")]
public async Task Test2()
{
var javaScriptEvaluator = _serviceProvider.GetRequiredService<IJavaScriptEvaluator>();
var expressionExecutionContext = new ExpressionExecutionContext(_serviceProvider, new MemoryRegister());
var unicodeString = UnicodeRangeGenerator.GenerateUnicodeString();
var payloadVariable = new Variable<object>("Payload", null!);
var payload = new
{
Text = unicodeString
};
payloadVariable.Set(expressionExecutionContext, payload);
var script = "toJson(getPayload())";
var result = (string)(await javaScriptEvaluator.EvaluateAsync(script, typeof(string), expressionExecutionContext))!;
var serializedText = JsonDocument.Parse(result).RootElement.GetProperty("Text").GetString();
Assert.Equal(unicodeString, serializedText);
}
}
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\common\Elsa.Testing.Shared.Integration\Elsa.Testing.Shared.Integration.csproj" />
<ProjectReference Include="..\..\..\src\common\Elsa.Testing.Shared\Elsa.Testing.Shared.csproj" />
<ProjectReference Include="..\..\..\src\modules\Elsa.Http\Elsa.Http.csproj" />
<ProjectReference Include="..\..\..\src\modules\Elsa.Resilience\Elsa.Resilience.csproj" />
<ProjectReference Include="..\..\..\src\modules\Elsa.Workflows.Core\Elsa.Workflows.Core.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,81 @@
using System.Net;
using Elsa.Extensions;
using Elsa.Http;
using Elsa.Resilience.Extensions;
using Elsa.Resilience.Models;
using Elsa.Testing.Shared;
using Elsa.Workflows;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Xunit.Abstractions;
namespace Elsa.Resilience.IntegrationTests;
public class FlowSendHttpRequestResilienceTests
{
private readonly IServiceProvider _services;
private readonly TestRetryAttemptRecorder _recorder = new();
public FlowSendHttpRequestResilienceTests(ITestOutputHelper output)
{
var handler = new SequentialStatusHandler([HttpStatusCode.TooManyRequests, HttpStatusCode.ServiceUnavailable, HttpStatusCode.OK]);
var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string?>
{
["Resilience:Strategies:0:$type"] = "HttpResilienceStrategy",
["Resilience:Strategies:0:Id"] = "test",
["Resilience:Strategies:0:DisplayName"] = "Test",
["Resilience:Strategies:0:MaxRetryAttempts"] = "5",
["Resilience:Strategies:0:Delay"] = "00:00:00.001"
})
.Build();
_services = new TestApplicationBuilder(output)
.ConfigureServices(s =>
{
s.AddSingleton<IConfiguration>(configuration);
})
.ConfigureElsa(module =>
{
module.UseHttp(http =>
{
http.HttpClientBuilder = builder => builder.ConfigurePrimaryHttpMessageHandler(() => handler);
});
module.UseResilience(resilience =>
{
resilience.WithRetryAttemptRecorder(_ => _recorder);
});
})
.Build();
}
[Fact(DisplayName = "FlowSendHttpRequest retries using selected resilience strategy")]
public async Task InvokesResilienceStrategy()
{
var activity = new FlowSendHttpRequest
{
Url = new(new Uri("http://localhost/test")),
Method = new("GET"),
ExpectedStatusCodes = new([200]),
CustomProperties =
{
["resilienceStrategy"] = new ResilienceStrategyConfig
{
Mode = ResilienceStrategyConfigMode.Identifier,
StrategyId = "test"
}
}
};
var result = await _services.RunActivityAsync(activity);
var statusCode = result.GetActivityOutput<int>(activity, nameof(SendHttpRequestBase.StatusCode));
Assert.Equal(200, statusCode);
Assert.Equal(2, _recorder.Attempts.Count);
Assert.Collection(_recorder.Attempts,
first => Assert.Equal(0, first.AttemptNumber),
second => Assert.Equal(1, second.AttemptNumber));
}
}
@@ -0,0 +1,19 @@
using System.Net;
using System.Net.Http.Headers;
namespace Elsa.Resilience.IntegrationTests;
public class SequentialStatusHandler(IEnumerable<HttpStatusCode> codes) : HttpMessageHandler
{
private readonly Queue<HttpStatusCode> _codes = new(codes);
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var code = _codes.Count > 0 ? _codes.Dequeue() : _codes.Last();
var response = new HttpResponseMessage(code)
{
Content = new StringContent(string.Empty)
};
response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/plain");
return Task.FromResult(response);
}
}
@@ -0,0 +1,16 @@
using Elsa.Resilience.Entities;
using Elsa.Resilience.Models;
namespace Elsa.Resilience.IntegrationTests;
public class TestRetryAttemptRecorder : IRetryAttemptRecorder
{
public IList<RetryAttemptRecord> Attempts { get; } = new List<RetryAttemptRecord>();
public Task RecordAsync(RecordRetryAttemptsContext context)
{
foreach (var record in context.Attempts)
Attempts.Add(record);
return Task.CompletedTask;
}
}
@@ -0,0 +1 @@
global using Xunit;
@@ -0,0 +1,64 @@
using Elsa.Testing.Shared;
using Elsa.Workflows.IntegrationTests.Activities.Workflows;
using Microsoft.Extensions.DependencyInjection;
using Xunit.Abstractions;
namespace Elsa.Workflows.IntegrationTests.Activities;
public class BreakTests
{
private readonly IWorkflowRunner _workflowRunner;
private readonly CapturingTextWriter _capturingTextWriter = new();
private readonly IServiceProvider _services;
public BreakTests(ITestOutputHelper testOutputHelper)
{
_services = new TestApplicationBuilder(testOutputHelper).WithCapturingTextWriter(_capturingTextWriter).Build();
_workflowRunner = _services.GetRequiredService<IWorkflowRunner>();
}
[Fact(DisplayName = "Break exits out of ForEach")]
public async Task Test1()
{
await _services.PopulateRegistriesAsync();
await _workflowRunner.RunAsync<BreakForEachWorkflow>();
var lines = _capturingTextWriter.Lines.ToList();
Assert.Equal(new[] { "Start", "C#", "End" }, lines);
}
[Fact(DisplayName = "Break exits out of immediate ForEach only")]
public async Task Test2()
{
await _services.PopulateRegistriesAsync();
await _workflowRunner.RunAsync<NestedForEachWithBreakWorkflow>();
var lines = _capturingTextWriter.Lines.ToList();
Assert.Equal(new[] { "C#", "Classes", "Rust", "Classes", "Go", "Classes" }, lines);
}
[Fact(DisplayName = "Break exits out of For")]
public async Task Test3()
{
await _services.PopulateRegistriesAsync();
await _workflowRunner.RunAsync<BreakForWorkflow>();
var lines = _capturingTextWriter.Lines.ToList();
Assert.Equal(new[] { "Start", "0", "1", "End" }, lines);
}
[Fact(DisplayName = "Break exits out of While")]
public async Task Test4()
{
await _services.PopulateRegistriesAsync();
await _workflowRunner.RunAsync<BreakWhileWorkflow>();
var lines = _capturingTextWriter.Lines.ToList();
Assert.Equal(new[] { "Start", "1", "2", "End" }, lines);
}
[Fact(DisplayName = "Break removes the right bookmarks")]
public async Task Test5()
{
await _services.PopulateRegistriesAsync();
await _workflowRunner.RunAsync<BreakWhileWorkflow>();
var lines = _capturingTextWriter.Lines.ToList();
Assert.Equal(new[] { "Start", "1", "2", "End" }, lines);
}
}
@@ -0,0 +1,38 @@
using Elsa.Workflows.Activities;
using Elsa.Workflows.Memory;
namespace Elsa.Workflows.IntegrationTests.Activities.Workflows;
class BreakForEachWorkflow : WorkflowBase
{
protected override void Build(IWorkflowBuilder workflow)
{
var items = new[] { "C#", "Rust", "Go" };
var currentItem = new Variable<string>("CurrentItem", "");
workflow.Root = new Sequence
{
Activities =
{
new WriteLine("Start"),
new ForEach<string>
{
Items = new(items),
CurrentValue = new (currentItem),
Body = new Sequence
{
Activities =
{
new If(context => currentItem.Get(context) == "Rust")
{
Then = new Break()
},
new WriteLine(currentItem)
}
}
},
new WriteLine("End"),
}
};
}
}
@@ -0,0 +1,36 @@
using Elsa.Workflows.Activities;
using Elsa.Workflows.Memory;
namespace Elsa.Workflows.IntegrationTests.Activities.Workflows;
class BreakForWorkflow : WorkflowBase
{
protected override void Build(IWorkflowBuilder workflow)
{
var currentValue = new Variable<int?>("CurrentValue", null);
workflow.Root = new Sequence
{
Activities =
{
new WriteLine("Start"),
new For(0, 3, 1)
{
CurrentValue = new (currentValue),
Body = new Sequence
{
Activities =
{
new If(context => currentValue.Get(context) == 2)
{
Then = new Break()
},
new WriteLine(context => currentValue.Get(context).ToString()),
}
}
},
new WriteLine("End"),
}
};
}
}
@@ -0,0 +1,51 @@
using Elsa.Workflows.Activities;
using Elsa.Workflows.Memory;
using Elsa.Workflows.Runtime.Activities;
namespace Elsa.Workflows.IntegrationTests.Activities.Workflows;
public class BreakWhileForkWorkflow : WorkflowBase
{
protected override void Build(IWorkflowBuilder workflow)
{
var currentValue = new Variable<int?>("CurrentValue", 0);
workflow.Root = new Sequence
{
Variables = { currentValue },
Activities =
{
While.True(new Fork
{
Branches =
{
new Sequence
{
Activities =
{
new SetVariable
{
Variable = currentValue,
Value = new (context => currentValue.Get(context) + 1)
},
new If(context => currentValue.Get(context) == 3)
{
Then = new Break()
}
}
},
new Sequence
{
Activities =
{
new WriteLine("Waiting for event..."),
new Event("Some event") { Id = "SomeEvent" },
new WriteLine("Resuming"),
}
}
}
}),
}
};
}
}
@@ -0,0 +1,38 @@
using Elsa.Workflows.Activities;
using Elsa.Workflows.Memory;
namespace Elsa.Workflows.IntegrationTests.Activities.Workflows;
public class BreakWhileWorkflow : WorkflowBase
{
protected override void Build(IWorkflowBuilder workflow)
{
var currentValue = new Variable<int?>("CurrentValue", 0);
workflow.Root = new Sequence
{
Variables = { currentValue },
Activities =
{
new WriteLine("Start"),
While.True(new Sequence
{
Activities =
{
new SetVariable
{
Variable = currentValue,
Value = new (context => currentValue.Get(context) + 1)
},
new If(context => currentValue.Get(context) == 3)
{
Then = new Break()
},
new WriteLine(context => currentValue.Get(context).ToString()),
}
}),
new WriteLine("End"),
}
};
}
}
@@ -0,0 +1,34 @@
using Elsa.Workflows.Activities;
using Elsa.Workflows.Runtime.Activities;
namespace Elsa.Workflows.IntegrationTests.Activities;
public class FinishForkedWorkflow : WorkflowBase
{
protected override void Build(IWorkflowBuilder workflow)
{
workflow.Root = new Fork
{
JoinMode = ForkJoinMode.WaitAll,
Branches =
{
new Sequence
{
Activities =
{
new WriteLine("Branch 1"),
new Event("Event 1")
}
},
new Sequence
{
Activities =
{
new WriteLine("Branch 2"),
new Finish()
}
}
}
};
}
}
@@ -0,0 +1,19 @@
using Elsa.Workflows.Activities;
namespace Elsa.Workflows.IntegrationTests.Activities;
public class FinishSequentialWorkflow : WorkflowBase
{
protected override void Build(IWorkflowBuilder workflow)
{
workflow.Root = new Sequence
{
Activities =
{
new WriteLine("Line 1"),
new Finish(),
new WriteLine("Line 2")
}
};
}
}
@@ -0,0 +1,45 @@
using Elsa.Testing.Shared;
using Microsoft.Extensions.DependencyInjection;
using Xunit.Abstractions;
namespace Elsa.Workflows.IntegrationTests.Activities;
public class FinishTests
{
private readonly IWorkflowRunner _workflowRunner;
private readonly CapturingTextWriter _capturingTextWriter = new();
private readonly IServiceProvider _services;
public FinishTests(ITestOutputHelper testOutputHelper)
{
_services = new TestApplicationBuilder(testOutputHelper).WithCapturingTextWriter(_capturingTextWriter).Build();
_workflowRunner = _services.GetRequiredService<IWorkflowRunner>();
}
[Fact(DisplayName = "Subsequent activities are not executed")]
public async Task Test1()
{
await _services.PopulateRegistriesAsync();
await _workflowRunner.RunAsync<FinishSequentialWorkflow>();
var lines = _capturingTextWriter.Lines.ToList();
Assert.Equal(new[] { "Line 1" }, lines);
}
[Fact(DisplayName = "Workflow status is set to Finished")]
public async Task Test2()
{
await _services.PopulateRegistriesAsync();
var result = await _workflowRunner.RunAsync<FinishSequentialWorkflow>();
var workflowState = result.WorkflowState;
Assert.Equal(WorkflowStatus.Finished, workflowState.Status);
Assert.Equal(WorkflowSubStatus.Finished, workflowState.SubStatus);
}
[Fact(DisplayName = "All bookmarks are removed")]
public async Task Test3()
{
await _services.PopulateRegistriesAsync();
var result = await _workflowRunner.RunAsync<FinishSequentialWorkflow>();
Assert.Empty(result.WorkflowState.Bookmarks);
}
}
@@ -0,0 +1,28 @@
using Elsa.Testing.Shared;
using Microsoft.Extensions.DependencyInjection;
using Xunit.Abstractions;
namespace Elsa.Workflows.IntegrationTests.Activities;
public class ForEachTests
{
private readonly IWorkflowRunner _workflowRunner;
private readonly CapturingTextWriter _capturingTextWriter = new();
private readonly IServiceProvider _services;
public ForEachTests(ITestOutputHelper testOutputHelper)
{
_services = new TestApplicationBuilder(testOutputHelper).WithCapturingTextWriter(_capturingTextWriter).Build();
_workflowRunner = _services.GetRequiredService<IWorkflowRunner>();
}
[Fact(DisplayName = "ForEach outputs each iteration")]
public async Task Test1()
{
var items = new[] { "C#", "Rust", "Go"};
await _services.PopulateRegistriesAsync();
await _workflowRunner.RunAsync(new ForEachWorkflow(items));
var lines = _capturingTextWriter.Lines.ToList();
Assert.Equal(items, lines);
}
}
@@ -0,0 +1,33 @@
using Elsa.Workflows.Activities;
using Elsa.Workflows.Memory;
namespace Elsa.Workflows.IntegrationTests.Activities;
class ForEachWorkflow : WorkflowBase
{
private readonly ICollection<string> _items;
public ForEachWorkflow(ICollection<string> items)
{
_items = items;
}
protected override void Build(IWorkflowBuilder workflow)
{
var currentItem = new Variable<string>("CurrentItem", "");
workflow.Root = new Sequence
{
Variables = { currentItem },
Activities =
{
new ForEach<string>
{
Items = new(_items),
CurrentValue = new(currentItem),
Body = new WriteLine(currentItem)
},
}
};
}
}
@@ -0,0 +1,42 @@
using Elsa.Workflows.Activities;
using Elsa.Workflows.Memory;
namespace Elsa.Workflows.IntegrationTests.Activities;
class NestedForEachWithBreakWorkflow : WorkflowBase
{
protected override void Build(IWorkflowBuilder workflow)
{
var outerItems = new[] { "C#", "Rust", "Go" };
var innerItems = new[] { "Classes", "Functions", "Modules" };
var currentOuterItem = new Variable<string>("CurrentOuterItem", "");
var currentInnerItem = new Variable<string>("CurrentInnerItem", "");
workflow.Root = new ForEach<string>(outerItems)
{
CurrentValue = new (currentOuterItem),
Body = new Sequence
{
Activities =
{
new WriteLine(currentOuterItem),
new ForEach<string>(innerItems)
{
CurrentValue = new (currentInnerItem),
Body = new Sequence
{
Activities =
{
new If(context => currentInnerItem.Get(context) == "Functions")
{
Then = new Break()
},
new WriteLine(currentInnerItem)
}
}
}
}
}
};
}
}
@@ -0,0 +1,19 @@
using Elsa.Workflows.Activities;
namespace Elsa.Workflows.IntegrationTests.Activities;
public class BasicForkWorkflow : WorkflowBase
{
protected override void Build(IWorkflowBuilder workflow)
{
workflow.Root = new Fork
{
Branches =
{
new WriteLine("Branch 1"),
new WriteLine("Branch 2"),
new WriteLine("Branch 3")
}
};
}
}
@@ -0,0 +1,54 @@
using Elsa.Extensions;
using Elsa.Testing.Shared;
using Elsa.Workflows.Options;
using Microsoft.Extensions.DependencyInjection;
using Xunit.Abstractions;
namespace Elsa.Workflows.IntegrationTests.Activities;
public class ForkTests
{
private readonly IWorkflowRunner _workflowRunner;
private readonly CapturingTextWriter _capturingTextWriter = new();
private readonly IWorkflowBuilderFactory _workflowBuilderFactory;
private IServiceProvider _services;
public ForkTests(ITestOutputHelper testOutputHelper)
{
_services = new TestApplicationBuilder(testOutputHelper).WithCapturingTextWriter(_capturingTextWriter).Build();
_workflowBuilderFactory = _services.GetRequiredService<IWorkflowBuilderFactory>();
_workflowRunner = _services.GetRequiredService<IWorkflowRunner>();
}
[Fact(DisplayName = "Each branch executes")]
public async Task Test1()
{
await _services.PopulateRegistriesAsync();
var workflow = await _workflowBuilderFactory.CreateBuilder().BuildWorkflowAsync<BasicForkWorkflow>();
await _workflowRunner.RunAsync(workflow);
var lines = _capturingTextWriter.Lines.ToList();
Assert.Equal(new[] { "Branch 1", "Branch 2", "Branch 3" }, lines);
}
[Fact(DisplayName = "Wait AnyAsync causes workflow to continue")]
public async Task Test2()
{
await _services.PopulateRegistriesAsync();
var workflow = await _workflowBuilderFactory.CreateBuilder().BuildWorkflowAsync<JoinAnyForkWorkflow>();
// First run.
var result = await _workflowRunner.RunAsync(workflow);
// Collect one of the bookmarks to resume the workflow.
var bookmark = result.WorkflowState.Bookmarks.FirstOrDefault(x => x.ActivityId == "Event2");
Assert.NotNull(bookmark);
// Resume the workflow.
var runOptions = new RunWorkflowOptions { BookmarkId = bookmark.Id };
await _workflowRunner.RunAsync(workflow, result.WorkflowState, runOptions);
// Verify output.
var lines = _capturingTextWriter.Lines.ToList();
Assert.Equal(new[] { "Start", "Branch 2", "End" }, lines);
}
}
@@ -0,0 +1,59 @@
using Elsa.Workflows.Activities;
using Elsa.Workflows.Runtime.Activities;
namespace Elsa.Workflows.IntegrationTests.Activities;
public class JoinAnyForkWorkflow : WorkflowBase
{
protected override void Build(IWorkflowBuilder workflow)
{
workflow.Root = new Sequence
{
Activities =
{
new WriteLine("Start"),
new Fork
{
Branches =
{
new Sequence
{
Activities =
{
new Event("Event 1")
{
Id = "Event1"
},
new WriteLine("Branch 1")
}
},
new Sequence
{
Activities =
{
new Event("Event 2")
{
Id = "Event2"
},
new WriteLine("Branch 2")
}
},
new Sequence
{
Activities =
{
new Event("Event 3")
{
Id = "Event3"
},
new WriteLine("Branch 3")
}
},
},
JoinMode = ForkJoinMode.WaitAny
},
new WriteLine("End")
}
};
}
}
@@ -0,0 +1,59 @@
using Elsa.Testing.Shared;
using Elsa.Workflows.Activities;
using Microsoft.Extensions.DependencyInjection;
using Xunit.Abstractions;
namespace Elsa.Workflows.IntegrationTests.Activities;
public class IfTests
{
private readonly IWorkflowRunner _workflowRunner;
private readonly CapturingTextWriter _capturingTextWriter = new();
private readonly IServiceProvider _services;
public IfTests(ITestOutputHelper testOutputHelper)
{
_services = new TestApplicationBuilder(testOutputHelper).WithCapturingTextWriter(_capturingTextWriter).Build();
_workflowRunner = _services.GetRequiredService<IWorkflowRunner>();
}
[Theory(DisplayName = "The correct branch executes when condition is true")]
[InlineData(true)]
[InlineData(false)]
public async Task Test1(bool conditionResult)
{
var result = default(bool?);
var activity = new If(() => conditionResult)
{
Then = new Inline(() => result = true),
Else = new Inline(() => result = false)
};
await _services.RunActivityAsync(activity);
Assert.Equal(conditionResult, result);
}
[Theory(DisplayName = "The If activity completes only after either one of its branches completed")]
[InlineData(true)]
[InlineData(false)]
public async Task Test2(bool conditionResult)
{
var activity = new If(() => conditionResult)
{
Then = new Inline(),
Else = new Inline()
};
var result = await _services.RunActivityAsync(activity);
Assert.Equal(WorkflowStatus.Finished, result.WorkflowState.Status);
}
[Fact(DisplayName = "The If activity produces a result when one of its branches completes")]
public async Task Test3()
{
var activity = new If(() => true);
var result = await _services.RunActivityAsync(activity);
var activityResult = result.GetActivityOutput<bool>(activity);
Assert.True(activityResult);
}
}
@@ -0,0 +1,27 @@
using Elsa.Workflows.Activities;
namespace Elsa.Workflows.IntegrationTests.Activities;
class NestedSequentialWorkflow : WorkflowBase
{
protected override void Build(IWorkflowBuilder workflow)
{
workflow.Root = new Sequence
{
Activities =
{
new WriteLine("Start"),
new Sequence
{
Activities =
{
new WriteLine("Line 1"),
new WriteLine("Line 2"),
new WriteLine("Line 3")
}
},
new WriteLine("End"),
}
};
}
}
@@ -0,0 +1,38 @@
using Elsa.Testing.Shared;
using Microsoft.Extensions.DependencyInjection;
using Xunit.Abstractions;
namespace Elsa.Workflows.IntegrationTests.Activities;
public class SequenceTests
{
private readonly IWorkflowRunner _workflowRunner;
private readonly CapturingTextWriter _capturingTextWriter = new();
private readonly IServiceProvider _services;
public SequenceTests(ITestOutputHelper testOutputHelper)
{
_services = new TestApplicationBuilder(testOutputHelper).WithCapturingTextWriter(_capturingTextWriter).Build();
_workflowRunner = _services.GetRequiredService<IWorkflowRunner>();
}
[Fact(DisplayName = "Sequence completes after its child activities complete")]
public async Task Test1()
{
await _services.PopulateRegistriesAsync();
await _workflowRunner.RunAsync(new SequentialWorkflow());
var lines = _capturingTextWriter.Lines.ToList();
Assert.Equal(new[] { "Line 1", "Line 2", "Line 3" }, lines);
}
[Fact(DisplayName = "Sequence completes after its child sequence activity complete")]
public async Task Test2()
{
await _services.PopulateRegistriesAsync();
await _workflowRunner.RunAsync(new NestedSequentialWorkflow());
var lines = _capturingTextWriter.Lines.ToList();
Assert.Equal(new[] { "Start", "Line 1", "Line 2", "Line 3", "End" }, lines);
}
}
@@ -0,0 +1,19 @@
using Elsa.Workflows.Activities;
namespace Elsa.Workflows.IntegrationTests.Activities;
class SequentialWorkflow : WorkflowBase
{
protected override void Build(IWorkflowBuilder workflow)
{
workflow.Root = new Sequence
{
Activities =
{
new WriteLine("Line 1"),
new WriteLine("Line 2"),
new WriteLine("Line 3")
}
};
}
}
@@ -0,0 +1,30 @@
using Elsa.Testing.Shared;
using Elsa.Workflows.Activities;
using Microsoft.Extensions.DependencyInjection;
using Xunit.Abstractions;
namespace Elsa.Workflows.IntegrationTests.Activities;
public class WriteLineTests
{
private readonly IWorkflowRunner _workflowRunner;
private readonly CapturingTextWriter _capturingTextWriter = new();
private readonly IServiceProvider _services;
public WriteLineTests(ITestOutputHelper testOutputHelper)
{
_services = new TestApplicationBuilder(testOutputHelper).WithCapturingTextWriter(_capturingTextWriter).Build();
_workflowRunner = _services.GetRequiredService<IWorkflowRunner>();
}
[Fact(DisplayName = "Run a simple workflow")]
public async Task Test1()
{
var expectedOutput = "Hello World!";
var workflow = Workflow.FromActivity(new WriteLine(expectedOutput));
await _services.PopulateRegistriesAsync();
await _workflowRunner.RunAsync(workflow);
var line = _capturingTextWriter.Lines.FirstOrDefault();
Assert.Equal(expectedOutput, line);
}
}
@@ -0,0 +1,29 @@
using Elsa.Testing.Shared;
using Elsa.Workflows.Activities;
using Microsoft.Extensions.DependencyInjection;
using Xunit.Abstractions;
namespace Elsa.Workflows.IntegrationTests.Core;
public class ScheduleActivityExecutionContextTests(ITestOutputHelper testOutputHelper)
{
private readonly IServiceProvider _serviceProvider = new TestApplicationBuilder(testOutputHelper).Build();
[Fact(DisplayName = "Scheduling an activity that is not part of the workflow should throw an exception")]
public async Task ScheduleActivityAsync_WithActivityNotPartOfWorkflow_ShouldThrowException()
{
await _serviceProvider.PopulateRegistriesAsync();
var writeLineA = new WriteLine("Test");
var writeLineB = new WriteLine("Test");
var workflow = new Workflow
{
Root = writeLineA
};
var workflowGraphBuilder = _serviceProvider.GetRequiredService<IWorkflowGraphBuilder>();
var workflowGraph = await workflowGraphBuilder.BuildAsync(workflow);
var workflowExecutionContext = await WorkflowExecutionContext.CreateAsync(_serviceProvider, workflowGraph, "test");
var activityExecutionContext = await workflowExecutionContext.CreateActivityExecutionContextAsync(writeLineA);
await Assert.ThrowsAsync<InvalidOperationException>(() => activityExecutionContext.ScheduleActivityAsync(writeLineB).AsTask());
}
}
@@ -0,0 +1,72 @@
using System.Text.Json;
using Elsa.Testing.Shared;
using Microsoft.Extensions.DependencyInjection;
using Xunit.Abstractions;
namespace Elsa.Workflows.IntegrationTests.Core;
public class SerializerUnicodeEncodingTests(ITestOutputHelper testOutputHelper)
{
private readonly IServiceProvider _serviceProvider = new TestApplicationBuilder(testOutputHelper).Build();
[Fact]
public void TestPayloadSerializer()
{
var serializer = _serviceProvider.GetRequiredService<IPayloadSerializer>();
TestSerializer(input => serializer.Serialize(input));
}
[Fact]
public void TestApiSerializer()
{
var serializer = _serviceProvider.GetRequiredService<IApiSerializer>();
TestSerializer(input => serializer.Serialize(input));
}
[Fact]
public void TestBookmarkPayloadSerializer()
{
var serializer = _serviceProvider.GetRequiredService<IBookmarkPayloadSerializer>();
TestSerializer(input => serializer.Serialize(input));
}
[Fact]
public void TestSafeSerializer()
{
var serializer = _serviceProvider.GetRequiredService<ISafeSerializer>();
TestSerializer(input => serializer.Serialize(input));
}
[Fact]
public void TestWorkflowStateSerializer()
{
var serializer = _serviceProvider.GetRequiredService<IWorkflowStateSerializer>();
TestSerializer(input => serializer.Serialize(input));
}
private void TestSerializer(Func<object, string> serialize)
{
var unicodeString = UnicodeRangeGenerator.GenerateUnicodeString();
var anonymousObject = new
{
Text = unicodeString
};
var serializedJson = serialize(anonymousObject);
var serializedStringValue = GetSerializedTextValue(serializedJson);
Assert.Equal(unicodeString, serializedStringValue);
}
private string GetSerializedTextValue(string serializedJson)
{
var rootElement = JsonDocument.Parse(serializedJson).RootElement;
return GetCaseInsensitiveSerializedTextValue(rootElement, "text", "Text");
}
private string GetCaseInsensitiveSerializedTextValue(JsonElement jsonElement, params string[] propertyNames)
{
foreach (string propertyName in propertyNames)
if (jsonElement.TryGetProperty(propertyName, out var property))
return property.GetString()!;
throw new KeyNotFoundException($"None of the following properties were found: {string.Join(", ", propertyNames)}");
}
}
@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ProjectGuid>{ca65b7d5-57e8-4932-b8b8-732d040c9b3a}</ProjectGuid>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Proto.Persistence.Sqlite" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\common\Elsa.Testing.Shared.Integration\Elsa.Testing.Shared.Integration.csproj" />
<ProjectReference Include="..\..\..\src\modules\Elsa.Http\Elsa.Http.csproj" />
<ProjectReference Include="..\..\..\src\modules\Elsa.JavaScript\Elsa.JavaScript.csproj" />
<ProjectReference Include="..\..\..\src\modules\Elsa.Workflows.Runtime.ProtoActor\Elsa.Workflows.Runtime.ProtoActor.csproj" />
<ProjectReference Include="..\..\..\src\modules\Elsa.Scheduling\Elsa.Scheduling.csproj" />
<ProjectReference Include="..\..\..\src\modules\Elsa.WorkflowProviders.BlobStorage\Elsa.WorkflowProviders.BlobStorage.csproj" />
<ProjectReference Include="..\..\..\src\modules\Elsa.Workflows.Api\Elsa.Workflows.Api.csproj" />
<ProjectReference Include="..\..\..\src\modules\Elsa.Workflows.Runtime\Elsa.Workflows.Runtime.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="Serialization\**\*.json" CopyToOutputDirectory="PreserveNewest" />
<None Update="Scenarios\**\Workflows\*.json" CopyToOutputDirectory="PreserveNewest" />
<None Update="Scenarios\DependencyWorkflowsPublishing\Workflows\child.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Scenarios\DependencyWorkflowsPublishing\Workflows\parent.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
@@ -0,0 +1,13 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=activities_005Cbreak/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=activities_005Cfinish/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=activities_005Cforeach/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=activities_005Cfork/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=activities_005Cif/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=activities_005Csequence/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=activities_005Cwriteline/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=break/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=foreach/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=if/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=sequence/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=writeline/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
@@ -0,0 +1,7 @@
namespace Elsa.Workflows.IntegrationTests.Scenarios.ActivityNotificationsMiddleware;
public class Spy
{
public bool ActivityExecutingWasCalled { get; set; }
public bool ActivityExecutedWasCalled { get; set; }
}
@@ -0,0 +1,26 @@
using Elsa.Mediator.Contracts;
using Elsa.Workflows.Notifications;
namespace Elsa.Workflows.IntegrationTests.Scenarios.ActivityNotificationsMiddleware;
public class TestHandler : INotificationHandler<ActivityExecuting>, INotificationHandler<ActivityExecuted>
{
private readonly Spy _spy;
public TestHandler(Spy spy)
{
_spy = spy;
}
public Task HandleAsync(ActivityExecuting notification, CancellationToken cancellationToken)
{
_spy.ActivityExecutingWasCalled = true;
return Task.CompletedTask;
}
public Task HandleAsync(ActivityExecuted notification, CancellationToken cancellationToken)
{
_spy.ActivityExecutedWasCalled = true;
return Task.CompletedTask;
}
}
@@ -0,0 +1,38 @@
using Elsa.Testing.Shared;
using Elsa.Workflows.Notifications;
using Microsoft.Extensions.DependencyInjection;
using Xunit.Abstractions;
namespace Elsa.Workflows.IntegrationTests.Scenarios.ActivityNotificationsMiddleware;
public class Tests
{
private readonly IWorkflowRunner _workflowRunner;
private readonly CapturingTextWriter _capturingTextWriter = new();
private readonly Spy _spy;
public Tests(ITestOutputHelper testOutputHelper)
{
var services = new TestApplicationBuilder(testOutputHelper)
.WithCapturingTextWriter(_capturingTextWriter)
.ConfigureServices(s =>
{
s.AddSingleton<Spy>();
s.AddNotificationHandler<TestHandler, ActivityExecuting>();
s.AddNotificationHandler<TestHandler, ActivityExecuted>();
})
.Build();
_workflowRunner = services.GetRequiredService<IWorkflowRunner>();
_spy = services.GetRequiredService<Spy>();
}
[Fact(DisplayName = "Running workflow with activity notifications middleware results in notifications being published")]
public async Task Test1()
{
await _workflowRunner.RunAsync<HelloWorldWorkflow>();
Assert.True(_spy.ActivityExecutingWasCalled);
Assert.True(_spy.ActivityExecutedWasCalled);
}
}
@@ -0,0 +1,11 @@
using Elsa.Workflows.Activities;
namespace Elsa.Workflows.IntegrationTests.Scenarios.ActivityNotificationsMiddleware;
class HelloWorldWorkflow : WorkflowBase
{
protected override void Build(IWorkflowBuilder workflow)
{
workflow.Root = new WriteLine("Hello World!");
}
}
@@ -0,0 +1,34 @@
using Elsa.Extensions;
using Elsa.Workflows.Activities;
namespace Elsa.Workflows.IntegrationTests.Scenarios.ActivityOutputs;
public class LoopingWorkflow : WorkflowBase
{
protected override void Build(IWorkflowBuilder builder)
{
var readCurrentValue = new Inline<string>(context => context.GetVariable<string>("CurrentValue")!);
builder.Root = new ForEach<string>(
[
"Item 1",
"Item 2",
"Item 3"
])
{
Body = new Sequence
{
Activities =
[
readCurrentValue,
new WriteLine(context =>
{
var currentValue = context.GetVariable<string>("CurrentValue");
var activityResult = context.GetActivityExecutionContext().GetResult(readCurrentValue);
return $"Current value: {currentValue}, Activity result: {activityResult}";
})
]
}
};
}
}
@@ -0,0 +1,27 @@
using Elsa.Extensions;
using Elsa.Workflows.Memory;
using Elsa.Workflows.Models;
namespace Elsa.Workflows.IntegrationTests.Scenarios.ActivityOutputs;
public class SumActivity : CodeActivity<int>
{
public SumActivity(Variable<int> a, Variable<int> b)
{
A = new(a);
B = new(b);
}
public Input<int> A { get; set; }
public Input<int> B { get; set; }
protected override void Execute(ActivityExecutionContext context)
{
var input1 = A.Get(context);
var input2 = B.Get(context);
var result = input1 + input2;
// All three work.
Result.Set(context, result);
}
}
@@ -0,0 +1,29 @@
using Elsa.Extensions;
using Elsa.Workflows.Activities;
using Elsa.Workflows.Memory;
namespace Elsa.Workflows.IntegrationTests.Scenarios.ActivityOutputs;
public class SumWorkflow : WorkflowBase
{
protected override void Build(IWorkflowBuilder workflow)
{
var a = new Variable<int>("A", 0);
var b = new Variable<int>("B", 0);
var sumActivity = new SumActivity(a, b);
workflow.Root = new Sequence
{
Variables = { a, b },
Activities =
{
new Inline(context => a.Set(context, 4)),
new Inline(context => b.Set(context, 6)),
sumActivity,
new WriteLine(context => $"The result of {a.Get(context)} and {b.Get(context)} is {sumActivity.GetResult<int>(context)}."),
new WriteLine(context => $"The last result is {context.GetLastResult<int>()}."),
}
};
}
}
@@ -0,0 +1,44 @@
using Elsa.Testing.Shared;
using Microsoft.Extensions.DependencyInjection;
using Xunit.Abstractions;
namespace Elsa.Workflows.IntegrationTests.Scenarios.ActivityOutputs;
public class Tests
{
private readonly IWorkflowRunner _workflowRunner;
private readonly CapturingTextWriter _capturingTextWriter = new();
private readonly IServiceProvider _services;
public Tests(ITestOutputHelper testOutputHelper)
{
_services = new TestApplicationBuilder(testOutputHelper).WithCapturingTextWriter(_capturingTextWriter).Build();
_workflowRunner = _services.GetRequiredService<IWorkflowRunner>();
}
[Fact(DisplayName = "Activity outputs can be accessed from other activities.")]
public async Task Test1()
{
await _services.PopulateRegistriesAsync();
await _workflowRunner.RunAsync<SumWorkflow>();
var lines = _capturingTextWriter.Lines.ToList();
Assert.Equal(new[]
{
"The result of 4 and 6 is 10.", "The last result is 10."
}, lines);
}
[Fact(DisplayName = "The last activity output is returned.")]
public async Task Test2()
{
await _services.PopulateRegistriesAsync();
await _workflowRunner.RunAsync<LoopingWorkflow>();
var lines = _capturingTextWriter.Lines.ToList();
Assert.Equal(new[]
{
"Current value: Item 1, Activity result: Item 1",
"Current value: Item 2, Activity result: Item 2",
"Current value: Item 3, Activity result: Item 3"
}, lines);
}
}
@@ -0,0 +1,50 @@
using Elsa.Extensions;
using Elsa.Testing.Shared;
using Elsa.Workflows.Options;
using Microsoft.Extensions.DependencyInjection;
using Xunit.Abstractions;
namespace Elsa.Workflows.IntegrationTests.Scenarios.Blocking;
public class Tests
{
private readonly IWorkflowRunner _workflowRunner;
private readonly CapturingTextWriter _capturingTextWriter = new();
private readonly IWorkflowBuilderFactory _workflowBuilderFactory;
private readonly IServiceProvider _services;
public Tests(ITestOutputHelper testOutputHelper)
{
_services = new TestApplicationBuilder(testOutputHelper).WithCapturingTextWriter(_capturingTextWriter).Build();
_workflowBuilderFactory = _services.GetRequiredService<IWorkflowBuilderFactory>();
_workflowRunner = _services.GetRequiredService<IWorkflowRunner>();
}
[Fact(DisplayName = "Subsequent activity does not get scheduled when previous activity created a bookmark")]
public async Task Test1()
{
await _services.PopulateRegistriesAsync();
await _workflowRunner.RunAsync<BlockingSequentialWorkflow>();
var lines = _capturingTextWriter.Lines.ToList();
Assert.Equal(new[] { "Line 1" }, lines);
}
[Fact(DisplayName = "Subsequent activities are scheduled when resuming workflow using bookmark")]
public async Task Test2()
{
await _services.PopulateRegistriesAsync();
var workflow = await _workflowBuilderFactory.CreateBuilder().BuildWorkflowAsync<BlockingSequentialWorkflow>();
// Start workflow.
var result = await _workflowRunner.RunAsync(workflow);
var bookmark = result.WorkflowState.Bookmarks.FirstOrDefault(x => x.ActivityId == "Resume");
// Resume workflow.
var runOptions = new RunWorkflowOptions { BookmarkId = bookmark!.Id };
await _workflowRunner.RunAsync(workflow, result.WorkflowState, runOptions);
// Verify expected output.
var lines = _capturingTextWriter.Lines.ToList();
Assert.Equal(new[] { "Line 1", "Line 2", "Line 3" }, lines);
}
}
@@ -0,0 +1,21 @@
using Elsa.Workflows.Activities;
using Elsa.Workflows.Runtime.Activities;
namespace Elsa.Workflows.IntegrationTests.Scenarios.Blocking;
class BlockingSequentialWorkflow : WorkflowBase
{
protected override void Build(IWorkflowBuilder workflow)
{
workflow.Root = new Sequence
{
Activities =
{
new WriteLine("Line 1"),
new Event("Resume"){ Id = "Resume"},
new WriteLine("Line 2"),
new WriteLine("Line 3")
}
};
}
}
@@ -0,0 +1,88 @@
using Elsa.Extensions;
using Elsa.Testing.Shared;
using Elsa.Workflows.IntegrationTests.Scenarios.BlockingAndBreaking.Workflows;
using Elsa.Workflows.Options;
using Microsoft.Extensions.DependencyInjection;
using Xunit.Abstractions;
namespace Elsa.Workflows.IntegrationTests.Scenarios.BlockingAndBreaking;
public class Tests
{
private readonly IWorkflowRunner _workflowRunner;
private readonly CapturingTextWriter _capturingTextWriter = new();
private readonly IWorkflowBuilderFactory _workflowBuilderFactory;
private readonly IServiceProvider _services;
public Tests(ITestOutputHelper testOutputHelper)
{
_services = new TestApplicationBuilder(testOutputHelper).WithCapturingTextWriter(_capturingTextWriter).Build();
_workflowBuilderFactory = _services.GetRequiredService<IWorkflowBuilderFactory>();
_workflowRunner = _services.GetRequiredService<IWorkflowRunner>();
}
[Fact(DisplayName = "Fork completes after all branches complete.")]
public async Task Test1()
{
await _services.PopulateRegistriesAsync();
var workflow = await _workflowBuilderFactory.CreateBuilder().BuildWorkflowAsync<WaitAllForkWorkflow>();
// Start workflow.
var result1 = await _workflowRunner.RunAsync(workflow);
// Resume the first branch.
var bookmark = result1.WorkflowState.Bookmarks.FirstOrDefault(x => x.ActivityId == "Branch 1");
var runOptions = new RunWorkflowOptions { BookmarkId = bookmark!.Id };
var result2 = await _workflowRunner.RunAsync(workflow, result1.WorkflowState, runOptions);
// Resume the second branch.
bookmark = result2.WorkflowState.Bookmarks.FirstOrDefault(x => x.ActivityId == "Branch 2");
runOptions = new RunWorkflowOptions { BookmarkId = bookmark!.Id };
var result3 = await _workflowRunner.RunAsync(workflow, result2.WorkflowState, runOptions);
// Verify expected output.
var lines = _capturingTextWriter.Lines.ToList();
Assert.Equal(new[] { "Start", "Branch 1", "Branch 2", "Branch 1 - Resumed", "Branch 2 - Resumed", "End" }, lines);
}
[Fact(DisplayName = "Fork completes after any branch completes.")]
public async Task Test2()
{
await _services.PopulateRegistriesAsync();
var workflow = await _workflowBuilderFactory.CreateBuilder().BuildWorkflowAsync<WaitAnyForkWorkflow>();
// Start workflow.
var result1 = await _workflowRunner.RunAsync(workflow);
// Resume the first branch.
var bookmark = result1.WorkflowState.Bookmarks.FirstOrDefault(x => x.ActivityId == "Branch 1");
var runOptions = new RunWorkflowOptions { BookmarkId = bookmark!.Id };
var result2 = await _workflowRunner.RunAsync(workflow, result1.WorkflowState, runOptions);
// Verify expected output.
var lines = _capturingTextWriter.Lines.ToList();
Assert.Equal(new[] { "Start", "Branch 1", "Branch 2", "Branch 1 - Resumed", "End" }, lines);
}
[Fact(DisplayName = "Break breaks out of While loop.")]
public async Task Test3()
{
await _services.PopulateRegistriesAsync();
var workflow = await _workflowBuilderFactory.CreateBuilder().BuildWorkflowAsync<BreakWhileFromForkWorkflow>();
// Start workflow.
var result1 = await _workflowRunner.RunAsync(workflow);
// Resume the first branch.
var bookmark = result1.WorkflowState.Bookmarks.FirstOrDefault(x => x.ActivityId == "Branch 1");
var runOptions = new RunWorkflowOptions { BookmarkId = bookmark!.Id };
var result2 = await _workflowRunner.RunAsync(workflow, result1.WorkflowState, runOptions);
// There should be no bookmarks left, since the while loop was broken out of.
Assert.Empty(result2.WorkflowState.Bookmarks);
// Verify expected output.
var lines = _capturingTextWriter.Lines.ToList();
Assert.Equal(new[] { "Start", "Branch 1", "Branch 2", "Branch 1 - Resumed", "End" }, lines);
}
}
@@ -0,0 +1,61 @@
using Elsa.Workflows.Activities;
using Elsa.Workflows.Memory;
using Elsa.Workflows.Runtime.Activities;
namespace Elsa.Workflows.IntegrationTests.Scenarios.BlockingAndBreaking.Workflows;
public class BreakWhileFromForkWorkflow : WorkflowBase
{
protected override void Build(IWorkflowBuilder workflow)
{
var currentValue = new Variable<int?>("CurrentValue", 0);
workflow.WithVariable(currentValue);
workflow.Root = new Sequence
{
Activities =
{
new WriteLine("Start"),
new While(() => true)
{
Body = new Sequence
{
Activities =
{
new Fork
{
JoinMode = ForkJoinMode.WaitAll,
Branches =
{
new Sequence
{
Activities =
{
new WriteLine("Branch 1"),
new Event("Branch 1") { Id = "Branch 1" },
new WriteLine("Branch 1 - Resumed"),
// This should break the while loop, not matter how high up in the tree it is.
new Break()
}
},
new Sequence
{
Activities =
{
new WriteLine("Branch 2"),
new Event("Branch 2") { Id = "Branch 2" },
new WriteLine("Branch 2 - Resumed"),
}
}
}
}
}
}
},
new WriteLine("End")
}
};
}
}
@@ -0,0 +1,50 @@
using Elsa.Workflows.Activities;
using Elsa.Workflows.Memory;
using Elsa.Workflows.Runtime.Activities;
namespace Elsa.Workflows.IntegrationTests.Scenarios.BlockingAndBreaking.Workflows;
public class WaitAllForkWorkflow : WorkflowBase
{
protected override void Build(IWorkflowBuilder workflow)
{
var currentValue = new Variable<int?>("CurrentValue", 0);
workflow.WithVariable(currentValue);
workflow.Root =
new Sequence
{
Activities =
{
new WriteLine("Start"),
new Fork
{
JoinMode = ForkJoinMode.WaitAll,
Branches =
{
new Sequence
{
Activities =
{
new WriteLine("Branch 1"),
new Event("Branch 1") { Id = "Branch 1" },
new WriteLine("Branch 1 - Resumed"),
}
},
new Sequence
{
Activities =
{
new WriteLine("Branch 2"),
new Event("Branch 2") { Id = "Branch 2" },
new WriteLine("Branch 2 - Resumed"),
}
}
}
},
new WriteLine("End")
}
};
}
}
@@ -0,0 +1,50 @@
using Elsa.Workflows.Activities;
using Elsa.Workflows.Memory;
using Elsa.Workflows.Runtime.Activities;
namespace Elsa.Workflows.IntegrationTests.Scenarios.BlockingAndBreaking.Workflows;
public class WaitAnyForkWorkflow : WorkflowBase
{
protected override void Build(IWorkflowBuilder workflow)
{
var currentValue = new Variable<int?>("CurrentValue", 0);
workflow.WithVariable(currentValue);
workflow.Root =
new Sequence
{
Activities =
{
new WriteLine("Start"),
new Fork
{
JoinMode = ForkJoinMode.WaitAny,
Branches =
{
new Sequence
{
Activities =
{
new WriteLine("Branch 1"),
new Event("Branch 1") { Id = "Branch 1" },
new WriteLine("Branch 1 - Resumed"),
}
},
new Sequence
{
Activities =
{
new WriteLine("Branch 2"),
new Event("Branch 2") { Id = "Branch 2" },
new WriteLine("Branch 2 - Resumed"),
}
}
}
},
new WriteLine("End")
}
};
}
}
@@ -0,0 +1,33 @@
using Elsa.Expressions.Models;
using Elsa.Extensions;
using Elsa.Workflows.Models;
namespace Elsa.Workflows.IntegrationTests.Scenarios.CanExecute.Activities;
public class CustomActivity : CodeActivity
{
public CustomActivity(int magicNumber)
{
MagicNumber = new (magicNumber);
}
public CustomActivity(Func<ExpressionExecutionContext, int> magicNumber)
{
MagicNumber = new (magicNumber);
}
public Input<int> MagicNumber { get; set; }
protected override bool CanExecute(ActivityExecutionContext context)
{
var magicNumber = MagicNumber.Get(context);
return magicNumber == 42;
}
protected override void Execute(ActivityExecutionContext context)
{
var provider = context.GetService<IStandardOutStreamProvider>() ?? new StandardOutStreamProvider(Console.Out);
var textWriter = provider.GetTextWriter();
textWriter.WriteLine("Welcome to the world of Might and Magic!");
}
}
@@ -0,0 +1,43 @@
using Elsa.Testing.Shared;
using Elsa.Workflows.IntegrationTests.Scenarios.CanExecute.Activities;
using Elsa.Workflows.IntegrationTests.Scenarios.CanExecute.Workflows;
using Elsa.Workflows.Options;
using Microsoft.Extensions.DependencyInjection;
using Xunit.Abstractions;
namespace Elsa.Workflows.IntegrationTests.Scenarios.CanExecute;
public class CanExecuteTests
{
private readonly CapturingTextWriter _capturingTextWriter = new();
private readonly IServiceProvider _services;
private readonly IWorkflowRunner _workflowRunner;
public CanExecuteTests(ITestOutputHelper testOutputHelper)
{
_services = new TestApplicationBuilder(testOutputHelper)
.WithCapturingTextWriter(_capturingTextWriter)
.AddActivitiesFrom<CustomActivity>()
.Build();
_workflowRunner = _services.GetRequiredService<IWorkflowRunner>();
}
[Theory(DisplayName = "Activities are executed only when they report that they can execute.")]
[InlineData(12, "Magic number is 12")]
[InlineData(42, "Magic number is 42\nWelcome to the world of Might and Magic!\nDone")]
public async Task Test1(int magicNumber, string expectedLines)
{
await _services.PopulateRegistriesAsync();
var runOptions = new RunWorkflowOptions
{
Input = new Dictionary<string, object>
{
["MagicNumber"] = magicNumber
}
};
await _workflowRunner.RunAsync<MagicWorkflow>(runOptions);
var lines = _capturingTextWriter.Lines.ToList();
Assert.Equal(expectedLines.Split('\n', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries), lines);
}
}
@@ -0,0 +1,24 @@
using Elsa.Extensions;
using Elsa.Workflows.Activities;
using Elsa.Workflows.IntegrationTests.Scenarios.CanExecute.Activities;
namespace Elsa.Workflows.IntegrationTests.Scenarios.CanExecute.Workflows;
public class MagicWorkflow : WorkflowBase
{
protected override void Build(IWorkflowBuilder builder)
{
var magicNumberVariable = builder.WithVariable<int>();
builder.Root = new Sequence
{
Activities =
{
new SetVariable<int>(magicNumberVariable, context => context.GetInput<int>("MagicNumber")),
new WriteLine(context => $"Magic number is {magicNumberVariable.Get(context)}"),
new CustomActivity(context => magicNumberVariable.Get(context)),
new WriteLine("Done")
}
};
}
}
@@ -0,0 +1,38 @@
using Elsa.Extensions;
using Elsa.Testing.Shared;
using Microsoft.Extensions.DependencyInjection;
using Xunit.Abstractions;
namespace Elsa.Workflows.IntegrationTests.Scenarios.Composites;
public class Tests
{
private readonly IWorkflowRunner _workflowRunner;
private readonly CapturingTextWriter _capturingTextWriter = new();
private readonly IWorkflowBuilderFactory _workflowBuilderFactory;
private readonly IServiceProvider _services;
public Tests(ITestOutputHelper testOutputHelper)
{
_services = new TestApplicationBuilder(testOutputHelper)
.WithCapturingTextWriter(_capturingTextWriter)
.ConfigureElsa(elsa => elsa.UseJavaScript())
.Build();
_workflowBuilderFactory = _services.GetRequiredService<IWorkflowBuilderFactory>();
_workflowRunner = _services.GetRequiredService<IWorkflowRunner>();
}
[Fact(DisplayName = "The main workflow can capture the result of the composite activity")]
public async Task Test1()
{
await _services.PopulateRegistriesAsync();
var workflow = await _workflowBuilderFactory.CreateBuilder().BuildWorkflowAsync<SumWorkflow>();
// Start workflow.
await _workflowRunner.RunAsync(workflow);
// Verify expected output.
var lines = _capturingTextWriter.Lines.ToList();
Assert.Equal(new[] { "Sum: 3" }, lines);
}
}
@@ -0,0 +1,72 @@
using Elsa.Extensions;
using Elsa.JavaScript.Activities;
using Elsa.Workflows.Activities;
using Elsa.Workflows.Memory;
using Elsa.Workflows.Models;
namespace Elsa.Workflows.IntegrationTests.Scenarios.Composites;
/// <summary>
/// A composite activity that sums two inputs and returns the result.
/// </summary>
public class Sum : Composite<int>
{
private readonly RunJavaScript _runJavaScript;
public Input<int> A { get; set; } = null!;
public Input<int> B { get; set; } = null!;
public Sum()
{
_runJavaScript = new()
{
Script = new("getA() + getB();"),
};
Root = new Sequence
{
Activities =
{
_runJavaScript
}
};
}
protected override void ConfigureActivities(ActivityExecutionContext context)
{
// Copy input into variables so that JS activity can access them.
var a = A.Get(context);
var b = B.Get(context);
Variables.Add(new Variable<int>("A", a));
Variables.Add(new Variable<int>("B", b));
// If the call site set a result variable, assign it to the JavaScript activity's result.
if (Result != null)
_runJavaScript.Result = new(Result.MemoryBlockReference)!;
}
}
/// <summary>
/// A workflow that uses the <see cref="Sum"/> composite activity.
/// </summary>
public class SumWorkflow : WorkflowBase
{
protected override void Build(IWorkflowBuilder workflow)
{
var sum = workflow.WithVariable<int>("Sum", 0);
workflow.Root = new Sequence
{
Activities =
{
new Sum
{
A = new(1),
B = new(2),
Result = new(sum)
},
new WriteLine(context => $"Sum: {sum.Get(context)}")
}
};
}
}
@@ -0,0 +1,36 @@
using Elsa.Testing.Shared;
using Xunit.Abstractions;
namespace Elsa.Workflows.IntegrationTests.Scenarios.CompositesAndOutcomes;
public class ExplicitJoinWaitAnyTests
{
private readonly CapturingTextWriter _capturingTextWriter = new();
private readonly IServiceProvider _services;
public ExplicitJoinWaitAnyTests(ITestOutputHelper testOutputHelper)
{
_services = new TestApplicationBuilder(testOutputHelper)
.WithCapturingTextWriter(_capturingTextWriter)
.WithWorkflowsFromDirectory("Scenarios", "CompositesAndOutcomes", "Workflows")
.Build();
}
[Fact(DisplayName = "Complete activity must not cascade.")]
public async Task Test1()
{
// Populate registries.
await _services.PopulateRegistriesAsync();
// Execute.
var workflowState = await _services.RunWorkflowUntilEndAsync("tester");
// Assert expected output.
var lines = _capturingTextWriter.Lines.ToList();
var expectedLinesArray = new[] { "Start", "End" };
Assert.Equal(expectedLinesArray, lines);
// Assert expected workflow status.
Assert.Equal(WorkflowStatus.Finished, workflowState.Status);
}
}
@@ -0,0 +1,71 @@
{
"id": "3457460b3fd74daa8f898564cf96fe48",
"definitionId": "99c1c6cf2fec47b3b4916a01425ec989",
"name": "Completor",
"createdAt": "2023-09-14T13:49:38.017111+00:00",
"version": 1,
"toolVersion": "3.0.0.0",
"variables": [],
"inputs": [],
"outputs": [],
"outcomes": [
"Success",
"Failure"
],
"customProperties": {},
"isReadonly": false,
"isLatest": true,
"isPublished": true,
"options": {
"usableAsActivity": true,
"autoUpdateConsumingWorkflows": true,
"activityCategory": "Testing"
},
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "Gn-5gEC2-UCmmzWTgvYCmw",
"metadata": {},
"customProperties": {
"source": "FlowchartJsonConverter.cs:46",
"notFoundConnections": [],
"canStartWorkflow": false,
"runAsynchronously": false
},
"activities": [
{
"outcomes": {
"typeName": "Object",
"expression": {
"type": "Literal",
"value": "Success"
},
"memoryReference": {
"id": "tyDvLNGiYkCbUabhgzcSdw:input-0"
}
},
"id": "tyDvLNGiYkCbUabhgzcSdw",
"name": "Complete1",
"type": "Elsa.Complete",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -186.5,
"y": -263
},
"size": {
"width": 136.8125,
"height": 50
}
}
}
}
],
"connections": []
}
}
@@ -0,0 +1,62 @@
{
"id": "9b6b4d4a6a8741c58ccbe3911eed7821",
"definitionId": "bc7f148b2a5b4f55811537a0d762e137",
"name": "Consumer",
"createdAt": "2023-09-14T13:50:13.31251+00:00",
"version": 1,
"toolVersion": "3.0.0.0",
"variables": [],
"inputs": [],
"outputs": [],
"outcomes": [],
"customProperties": {},
"isReadonly": false,
"isLatest": true,
"isPublished": true,
"options": {
"usableAsActivity": true,
"autoUpdateConsumingWorkflows": true,
"activityCategory": "Testing"
},
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "v-ii0Vp0yUCPWReLNUiosg",
"metadata": {},
"customProperties": {
"source": "FlowchartJsonConverter.cs:46",
"notFoundConnections": [],
"canStartWorkflow": false,
"runAsynchronously": false
},
"activities": [
{
"workflowDefinitionId": "99c1c6cf2fec47b3b4916a01425ec989",
"workflowDefinitionVersionId": "3457460b3fd74daa8f898564cf96fe48",
"latestAvailablePublishedVersion": 1,
"latestAvailablePublishedVersionId": "3457460b3fd74daa8f898564cf96fe48",
"id": "ctusr1Uv40uFqIws9Ymt9A",
"name": "Completor1",
"type": "Completor",
"version": 2,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": 20,
"y": -260
},
"size": {
"width": 106.859375,
"height": 116.015625
}
}
}
}
],
"connections": []
}
}
@@ -0,0 +1,145 @@
{
"id": "74144d614208497380a7bae783b78626",
"definitionId": "tester",
"name": "Tester",
"createdAt": "2023-09-14T13:50:38.720025+00:00",
"version": 1,
"toolVersion": "3.0.0.0",
"variables": [],
"inputs": [],
"outputs": [],
"outcomes": [],
"customProperties": {},
"isReadonly": false,
"isLatest": true,
"isPublished": true,
"options": {
"autoUpdateConsumingWorkflows": false
},
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "k0HQeDE_gk-OxcsjGj3tbQ",
"metadata": {},
"customProperties": {
"source": "FlowchartJsonConverter.cs:46",
"notFoundConnections": [],
"canStartWorkflow": false,
"runAsynchronously": false
},
"activities": [
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "Start"
},
"memoryReference": {
"id": "35MeNlWlAUKXd5JFDJtJ3w:input-0"
}
},
"id": "35MeNlWlAUKXd5JFDJtJ3w",
"name": "WriteLine1",
"type": "Elsa.WriteLine",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -198,
"y": -246.9921875
},
"size": {
"width": 139.296875,
"height": 50
}
}
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "End"
},
"memoryReference": {
"id": "CvdSrKs5DESezhkLjb0YIw:input-0"
}
},
"id": "CvdSrKs5DESezhkLjb0YIw",
"name": "WriteLine2",
"type": "Elsa.WriteLine",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": 253,
"y": -246.9921875
},
"size": {
"width": 139.296875,
"height": 50
}
}
}
},
{
"workflowDefinitionId": "bc7f148b2a5b4f55811537a0d762e137",
"workflowDefinitionVersionId": "9b6b4d4a6a8741c58ccbe3911eed7821",
"latestAvailablePublishedVersion": 1,
"latestAvailablePublishedVersionId": "9b6b4d4a6a8741c58ccbe3911eed7821",
"id": "QfkYnRZDxEO0rtBTIbTXzg",
"name": "Consumer1",
"type": "Consumer",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": 20,
"y": -280
},
"size": {
"width": 105.5625,
"height": 116.015625
}
}
}
}
],
"connections": [
{
"source": {
"activity": "35MeNlWlAUKXd5JFDJtJ3w",
"port": "Done"
},
"target": {
"activity": "QfkYnRZDxEO0rtBTIbTXzg",
"port": "In"
}
},
{
"source": {
"activity": "QfkYnRZDxEO0rtBTIbTXzg",
"port": "Done"
},
"target": {
"activity": "CvdSrKs5DESezhkLjb0YIw",
"port": "In"
}
}
]
}
}
@@ -0,0 +1,38 @@
using Elsa.Extensions;
using Elsa.Testing.Shared;
using Microsoft.Extensions.DependencyInjection;
using Xunit.Abstractions;
namespace Elsa.Workflows.IntegrationTests.Scenarios.CompositesPassingData;
public class Tests
{
private readonly IWorkflowRunner _workflowRunner;
private readonly CapturingTextWriter _capturingTextWriter = new();
private readonly IWorkflowBuilderFactory _workflowBuilderFactory;
private readonly IServiceProvider _services;
public Tests(ITestOutputHelper testOutputHelper)
{
_services = new TestApplicationBuilder(testOutputHelper)
.WithCapturingTextWriter(_capturingTextWriter)
.ConfigureElsa(elsa => elsa.UseJavaScript())
.Build();
_workflowBuilderFactory = _services.GetRequiredService<IWorkflowBuilderFactory>();
_workflowRunner = _services.GetRequiredService<IWorkflowRunner>();
}
[Fact(DisplayName = "The main workflow can capture the result of the composite activity")]
public async Task Test1()
{
await _services.PopulateRegistriesAsync();
var workflow = await _workflowBuilderFactory.CreateBuilder().BuildWorkflowAsync<AddTextMainWorkflow>();
// Start workflow.
await _workflowRunner.RunAsync(workflow);
// Verify expected output.
var line = _capturingTextWriter.Lines.ToList().Last();
Assert.Equal("hi there obi wan", line);
}
}
@@ -0,0 +1,61 @@
using Elsa.Extensions;
using Elsa.Workflows.Activities;
using Elsa.Workflows.Management.Activities.SetOutput;
using Elsa.Workflows.Memory;
using Elsa.Workflows.Models;
namespace Elsa.Workflows.IntegrationTests.Scenarios.CompositesPassingData;
/// <summary>
/// A composite activity that adds text to a string
/// </summary>
public class AddTextSubWorkflow : Composite
{
public Input<string> A { get; set; } = default!;
public Output<string> B { get; set; } = default!;
public AddTextSubWorkflow()
{
var setOutput = new SetOutput()
{
OutputName = new Input<string>("B"),
OutputValue = new Input<object?>(context => "hi there " + A.Get(context))
};
Root = new Sequence
{
Activities =
{
setOutput
}
};
}
}
/// <summary>
/// A workflow that uses the <see cref="AddTextSubWorkflow"/> composite activity.
/// </summary>
public class AddTextMainWorkflow : WorkflowBase
{
protected override void Build(IWorkflowBuilder workflow)
{
var outVariable = new Variable<string>("out", "test");
var subWorkflow = new AddTextSubWorkflow()
{
A = new("obi wan"),
B = new (outVariable)
};
workflow.Variables.Add(outVariable);
workflow.Root = new Sequence
{
Activities =
{
subWorkflow,
new WriteLine(context => outVariable.Get(context))
}
};
}
}
@@ -0,0 +1,33 @@
using Elsa.Testing.Shared;
using Xunit.Abstractions;
namespace Elsa.Workflows.IntegrationTests.Scenarios.DependencyWorkflows;
public class Tests
{
private readonly CapturingTextWriter _capturingTextWriter = new();
private readonly IServiceProvider _services;
public Tests(ITestOutputHelper testOutputHelper)
{
_services = new TestApplicationBuilder(testOutputHelper)
.WithCapturingTextWriter(_capturingTextWriter)
.WithWorkflowsFromDirectory("Scenarios", "DependencyWorkflows", "Workflows")
.Build();
}
[Fact(DisplayName = "Workflows provided from JSON files that depend on other workflows are executed correctly.")]
public async Task Test1()
{
// Populate registries.
await _services.PopulateRegistriesAsync();
// Run the "matter" workflow.
await _services.RunWorkflowUntilEndAsync("matter-workflow");
// Assert.
var lines = _capturingTextWriter.Lines.ToList();
Assert.Equal(new[] { "Atom", "Atom", "Atom", "Atom", "Atom", "Atom", "Atom", "Atom", "Atom" }, lines);
}
}
@@ -0,0 +1,61 @@
{
"id": "82b53ccf2a4e4ced966b85fb3cc13eed",
"definitionId": "40a40a0cdf5448acbc899391b2c50458",
"name": "AtomActivity",
"createdAt": "2023-06-01T14:00:29.374132+00:00",
"version": 1,
"variables": [],
"inputs": [],
"outputs": [],
"outcomes": [],
"customProperties": {},
"usableAsActivity": true,
"isLatest": true,
"isPublished": true,
"options": {
"autoUpdateConsumingWorkflows": true
},
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "Flowchart1",
"metadata": {},
"applicationProperties": {
"NotFoundConnectionsKey": [],
"CanStartWorkflow": false,
"RunAsynchronously": false
},
"start": "Atom",
"activities": [
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "Atom"
},
"memoryReference": {
"id": "65ee0198-2051-491d-9035-6d05e73fbd08"
}
},
"id": "Atom",
"type": "Elsa.WriteLine",
"version": 1,
"customProperties": {
"CanStartWorkflow": false,
"RunAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": 2394,
"y": 2472
}
},
"displayText": "Atom"
}
}
],
"connections": []
}
}
@@ -0,0 +1,105 @@
{
"id": "matter-workflow-v1",
"definitionId": "matter-workflow",
"name": "Matter Workflow",
"createdAt": "2023-06-01T14:06:25.895316+00:00",
"version": 1,
"variables": [],
"inputs": [],
"outputs": [],
"outcomes": [],
"customProperties": {},
"isLatest": true,
"isPublished": true,
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "Flowchart1",
"metadata": {},
"applicationProperties": {
"NotFoundConnectionsKey": [],
"CanStartWorkflow": false,
"RunAsynchronously": false
},
"start": "MoleculeActivity2",
"activities": [
{
"workflowDefinitionId": "86e355d944764f15872f8cfdcc1e9cec",
"workflowDefinitionVersionId": "fec9de7279a440f693a5901507140a77",
"latestAvailablePublishedVersion": 1,
"latestAvailablePublishedVersionId": "fec9de7279a440f693a5901507140a77",
"id": "MoleculeActivity1",
"type": "MoleculeActivity",
"version": 1,
"customProperties": {
"CanStartWorkflow": false,
"RunAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": 2347,
"y": 2407
}
}
}
},
{
"workflowDefinitionId": "86e355d944764f15872f8cfdcc1e9cec",
"workflowDefinitionVersionId": "fec9de7279a440f693a5901507140a77",
"latestAvailablePublishedVersion": 1,
"latestAvailablePublishedVersionId": "fec9de7279a440f693a5901507140a77",
"id": "MoleculeActivity3",
"type": "MoleculeActivity",
"version": 1,
"customProperties": {
"CanStartWorkflow": false,
"RunAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": 2560,
"y": 2560
}
}
}
},
{
"workflowDefinitionId": "86e355d944764f15872f8cfdcc1e9cec",
"workflowDefinitionVersionId": "fec9de7279a440f693a5901507140a77",
"latestAvailablePublishedVersion": 1,
"latestAvailablePublishedVersionId": "fec9de7279a440f693a5901507140a77",
"id": "MoleculeActivity2",
"type": "MoleculeActivity",
"version": 1,
"customProperties": {
"CanStartWorkflow": false,
"RunAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": 2140,
"y": 2560
}
}
}
}
],
"connections": [
{
"source": "MoleculeActivity2",
"target": "MoleculeActivity1",
"sourcePort": "Done",
"targetPort": "In"
},
{
"source": "MoleculeActivity1",
"target": "MoleculeActivity3",
"sourcePort": "Done",
"targetPort": "In"
}
]
}
}
@@ -0,0 +1,109 @@
{
"id": "fec9de7279a440f693a5901507140a77",
"definitionId": "86e355d944764f15872f8cfdcc1e9cec",
"name": "MoleculeActivity",
"createdAt": "2023-06-01T14:05:32.409643+00:00",
"version": 1,
"variables": [],
"inputs": [],
"outputs": [],
"outcomes": [],
"customProperties": {},
"isLatest": true,
"isPublished": true,
"options": {
"usableAsActivity": true,
"autoUpdateConsumingWorkflows": true
},
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "Flowchart1",
"metadata": {},
"applicationProperties": {
"NotFoundConnectionsKey": [],
"CanStartWorkflow": false,
"RunAsynchronously": false
},
"start": "AtomActivity1",
"activities": [
{
"workflowDefinitionId": "40a40a0cdf5448acbc899391b2c50458",
"workflowDefinitionVersionId": "82b53ccf2a4e4ced966b85fb3cc13eed",
"latestAvailablePublishedVersion": 1,
"latestAvailablePublishedVersionId": "82b53ccf2a4e4ced966b85fb3cc13eed",
"id": "AtomActivity1",
"type": "AtomActivity",
"version": 1,
"customProperties": {
"CanStartWorkflow": false,
"RunAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": 2228,
"y": 2418
}
}
}
},
{
"workflowDefinitionId": "40a40a0cdf5448acbc899391b2c50458",
"workflowDefinitionVersionId": "82b53ccf2a4e4ced966b85fb3cc13eed",
"latestAvailablePublishedVersion": 1,
"latestAvailablePublishedVersionId": "82b53ccf2a4e4ced966b85fb3cc13eed",
"id": "AtomActivity2",
"type": "AtomActivity",
"version": 1,
"customProperties": {
"CanStartWorkflow": false,
"RunAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": 2458,
"y": 2487
}
}
}
},
{
"workflowDefinitionId": "40a40a0cdf5448acbc899391b2c50458",
"workflowDefinitionVersionId": "82b53ccf2a4e4ced966b85fb3cc13eed",
"latestAvailablePublishedVersion": 1,
"latestAvailablePublishedVersionId": "82b53ccf2a4e4ced966b85fb3cc13eed",
"id": "AtomActivity3",
"type": "AtomActivity",
"version": 1,
"customProperties": {
"CanStartWorkflow": false,
"RunAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": 2660,
"y": 2560
}
}
}
}
],
"connections": [
{
"source": "AtomActivity1",
"target": "AtomActivity2",
"sourcePort": "Done",
"targetPort": "In"
},
{
"source": "AtomActivity2",
"target": "AtomActivity3",
"sourcePort": "Done",
"targetPort": "In"
}
]
}
}
@@ -0,0 +1,71 @@
using Elsa.Common.Models;
using Elsa.Extensions;
using Elsa.Testing.Shared;
using Elsa.Workflows.Management;
using Elsa.Workflows.Management.Activities.WorkflowDefinitionActivity;
using Elsa.Workflows.Management.Entities;
using Microsoft.Extensions.DependencyInjection;
using Xunit.Abstractions;
namespace Elsa.Workflows.IntegrationTests.Scenarios.DependencyWorkflowsPublishing;
/// <summary>
/// Contains tests for the "DependencyWorkflowsPublishing" scenario.
/// </summary>
public class Tests
{
private readonly CapturingTextWriter _capturingTextWriter = new();
private readonly IServiceProvider _services;
private readonly IWorkflowDefinitionPublisher _workflowDefinitionPublisher;
private readonly IActivitySerializer _activitySerializer;
private readonly IActivityVisitor _activityVisitor;
/// <summary>
/// Initializes a new instance of the <see cref="Tests"/> class.
/// </summary>
public Tests(ITestOutputHelper testOutputHelper)
{
_services = new TestApplicationBuilder(testOutputHelper)
.WithCapturingTextWriter(_capturingTextWriter)
.WithWorkflowsFromDirectory("Scenarios", "DependencyWorkflowsPublishing", "Workflows")
.Build();
_workflowDefinitionPublisher = _services.GetRequiredService<IWorkflowDefinitionPublisher>();
_activitySerializer = _services.GetRequiredService<IActivitySerializer>();
_activityVisitor = _services.GetRequiredService<IActivityVisitor>();
}
/// <summary>
/// When a dependency workflow is published, all consuming workflows are updated to point to the new version of the dependency.
/// </summary>
[Fact(DisplayName = "When a dependency workflow is published, all consuming workflows are updated to point to the new version of the dependency.")]
public async Task Test1()
{
await _services.PopulateRegistriesAsync();
var childDefinitionV1 = await _services.GetWorkflowDefinitionAsync("child", VersionOptions.Latest);
var parentDefinition = await _services.GetWorkflowDefinitionAsync("parent", VersionOptions.Latest);
var childActivityV1 = await GetChildActivityAsync(parentDefinition);
// Assert initial state.
Assert.Equal(1, childDefinitionV1.Version);
Assert.False(parentDefinition.IsPublished);
Assert.Equal(1, childActivityV1.Version);
// Create a new draft for the child workflow and publish it.
var childDefinitionV2 = (await _workflowDefinitionPublisher.GetDraftAsync(childDefinitionV1.DefinitionId, VersionOptions.Published))!;
await _workflowDefinitionPublisher.PublishAsync(childDefinitionV2);
// Assert that the parent workflow now points to the new version of the child workflow.
parentDefinition = await _services.GetWorkflowDefinitionAsync("parent", VersionOptions.Latest);
var childActivityV2 = await GetChildActivityAsync(parentDefinition);
Assert.Equal(2, childActivityV2.Version);
}
private async Task<WorkflowDefinitionActivity> GetChildActivityAsync(WorkflowDefinition parent)
{
var root = _activitySerializer.Deserialize(parent.StringData!);
var graph = await _activityVisitor.VisitAsync(root);
var flattenedList = graph.Flatten().ToList();
return (WorkflowDefinitionActivity)flattenedList.Single(x => x.Activity is WorkflowDefinitionActivity { WorkflowDefinitionId: "child" }).Activity;
}
}
@@ -0,0 +1,37 @@
{
"id": "child-v1",
"definitionId": "child",
"name": "Child",
"createdAt": "2024-02-07T14:57:47.115341+00:00",
"version": 1,
"toolVersion": "3.0.0.0",
"variables": [],
"inputs": [],
"outputs": [],
"outcomes": [],
"customProperties": {
"Elsa:WorkflowContextProviderTypes": []
},
"isReadonly": false,
"isLatest": true,
"isPublished": true,
"options": {
"usableAsActivity": true,
"autoUpdateConsumingWorkflows": true
},
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "c22918340b6cb7cd",
"nodeId": "Workflow1:c22918340b6cb7cd",
"metadata": {},
"customProperties": {
"source": "FlowchartJsonConverter.cs:45",
"notFoundConnections": [],
"canStartWorkflow": false,
"runAsynchronously": false
},
"activities": [],
"connections": []
}
}
@@ -0,0 +1,64 @@
{
"id": "ffd312ccf7a2af4f",
"definitionId": "parent",
"name": "Parent",
"createdAt": "2024-02-07T14:58:12.954939+00:00",
"version": 1,
"toolVersion": "3.0.0.0",
"variables": [],
"inputs": [],
"outputs": [],
"outcomes": [],
"customProperties": {
"Elsa:WorkflowContextProviderTypes": []
},
"isReadonly": false,
"isLatest": true,
"isPublished": false,
"options": {
"autoUpdateConsumingWorkflows": false
},
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "3535b9598a0962f0",
"nodeId": "Workflow1:3535b9598a0962f0",
"metadata": {},
"customProperties": {
"source": "FlowchartJsonConverter.cs:45",
"notFoundConnections": [],
"canStartWorkflow": false,
"runAsynchronously": false
},
"activities": [
{
"workflowDefinitionId": "child",
"workflowDefinitionVersionId": "child-v1",
"latestAvailablePublishedVersion": 1,
"latestAvailablePublishedVersionId": "child-v1",
"id": "35525d3b78b8e518",
"nodeId": "Workflow1:3535b9598a0962f0:35525d3b78b8e518",
"name": "Child1",
"type": "Child",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -95.17782592773438,
"y": -137.3333282470703
},
"size": {
"width": 67.78125,
"height": 50
}
}
}
}
],
"connections": []
}
}
@@ -0,0 +1,42 @@
using Elsa.Testing.Shared;
using Xunit.Abstractions;
namespace Elsa.Workflows.IntegrationTests.Scenarios.ExplicitJoins;
public class ExplicitJoinWaitAnyTests
{
private readonly CapturingTextWriter _capturingTextWriter = new();
private readonly IServiceProvider _services;
public ExplicitJoinWaitAnyTests(ITestOutputHelper testOutputHelper)
{
_services = new TestApplicationBuilder(testOutputHelper)
.WithCapturingTextWriter(_capturingTextWriter)
.Build();
}
[Theory(DisplayName = "Workflows with explicit joins complete the workflow.")]
[InlineData("join-any-1.json", "Start; End")]
[InlineData("join-all-1.json", "Start; Line 1; Line 2; End")]
[InlineData("join-all-2.json", "Start; Line 1; Line 2; End")]
public async Task Test1(string filename, string expectedLines)
{
// Populate registries.
await _services.PopulateRegistriesAsync();
// Import workflow.
var fileName = $"Scenarios/ExplicitJoins/Workflows/{filename}";
var workflowDefinition = await _services.ImportWorkflowDefinitionAsync(fileName);
// Execute.
var workflowState = await _services.RunWorkflowUntilEndAsync(workflowDefinition.DefinitionId);
// Assert expected output.
var lines = _capturingTextWriter.Lines.ToList();
var expectedLinesArray = expectedLines.Split(";", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).ToList();
Assert.Equal(expectedLinesArray, lines);
// Assert expected workflow status.
Assert.Equal(WorkflowStatus.Finished, workflowState.Status);
}
}
@@ -0,0 +1,248 @@
{
"id": "9948e541b0e540c384c6eb9fdcf1d544",
"definitionId": "296428cc22bd4f309e858f4b461dbb3d",
"name": "Join 1",
"createdAt": "2023-09-12T18:12:54.370149+00:00",
"version": 2,
"toolVersion": "3.0.0.0",
"variables": [],
"inputs": [],
"outputs": [],
"outcomes": [],
"customProperties": {},
"isReadonly": false,
"isLatest": true,
"isPublished": true,
"options": {
"autoUpdateConsumingWorkflows": false
},
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "sL581qWiIEOpRncv5Ez33A",
"metadata": {},
"customProperties": {
"source": "FlowchartJsonConverter.cs:46",
"notFoundConnections": [],
"canStartWorkflow": false,
"runAsynchronously": false
},
"activities": [
{
"mode": {
"typeName": "Elsa.Workflows.Core.Activities.Flowchart.Models.FlowJoinMode, Elsa.Workflows.Core",
"expression": {
"type": "Literal",
"value": "WaitAll"
},
"memoryReference": {
"id": "aA4pfJOzyEqmLOY3U3vCoQ:input-0"
}
},
"id": "aA4pfJOzyEqmLOY3U3vCoQ",
"name": "FlowJoin1",
"type": "Elsa.FlowJoin",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": 198,
"y": -266
},
"size": {
"width": 136.859375,
"height": 50
}
}
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "Start"
},
"memoryReference": {
"id": "FRXh0PlHekejZiWANAplUg:input-0"
}
},
"id": "FRXh0PlHekejZiWANAplUg",
"name": "WriteLine1",
"type": "Elsa.WriteLine",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -287,
"y": -266
},
"size": {
"width": 102.21875,
"height": 50
}
},
"displayText": "Start"
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "Line 1"
},
"memoryReference": {
"id": "GaVrCWqBDkS6YrvydxKvFA:input-0"
}
},
"id": "GaVrCWqBDkS6YrvydxKvFA",
"name": "WriteLine2",
"type": "Elsa.WriteLine",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -40,
"y": -316
},
"size": {
"width": 110.65625,
"height": 50
}
},
"displayText": "Line 1"
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "Line 2"
},
"memoryReference": {
"id": "9Z7bt98j00uRMt9TS2FD0g:input-0"
}
},
"id": "9Z7bt98j00uRMt9TS2FD0g",
"name": "WriteLine3",
"type": "Elsa.WriteLine",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -40,
"y": -207
},
"size": {
"width": 110.65625,
"height": 50
}
},
"displayText": "Line 2"
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "End"
},
"memoryReference": {
"id": "MplDrS_bUE-8td-XBUvWmA:input-0"
}
},
"id": "MplDrS_bUE-8td-XBUvWmA",
"name": "WriteLine4",
"type": "Elsa.WriteLine",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": 430,
"y": -266
},
"size": {
"width": 139.296875,
"height": 50
}
}
}
}
],
"connections": [
{
"source": {
"activity": "FRXh0PlHekejZiWANAplUg",
"port": "Done"
},
"target": {
"activity": "GaVrCWqBDkS6YrvydxKvFA",
"port": "In"
}
},
{
"source": {
"activity": "FRXh0PlHekejZiWANAplUg",
"port": "Done"
},
"target": {
"activity": "9Z7bt98j00uRMt9TS2FD0g",
"port": "In"
}
},
{
"source": {
"activity": "9Z7bt98j00uRMt9TS2FD0g",
"port": "Done"
},
"target": {
"activity": "aA4pfJOzyEqmLOY3U3vCoQ",
"port": "In"
}
},
{
"source": {
"activity": "GaVrCWqBDkS6YrvydxKvFA",
"port": "Done"
},
"target": {
"activity": "aA4pfJOzyEqmLOY3U3vCoQ",
"port": "In"
}
},
{
"source": {
"activity": "aA4pfJOzyEqmLOY3U3vCoQ",
"port": "Done"
},
"target": {
"activity": "MplDrS_bUE-8td-XBUvWmA",
"port": "In"
}
}
]
}
}
@@ -0,0 +1,287 @@
{
"id": "14984c048db34cceaf5e15ece80f9703",
"definitionId": "64f8f2c852f04dc28f60707034d7a906",
"name": "Join 2",
"createdAt": "2023-09-12T18:12:43.392272+00:00",
"version": 1,
"toolVersion": "3.0.0.0",
"variables": [],
"inputs": [],
"outputs": [],
"outcomes": [],
"customProperties": {},
"isReadonly": false,
"isLatest": true,
"isPublished": true,
"options": {
"autoUpdateConsumingWorkflows": false
},
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "B2jpPDriPEe-zSOhVuAGeQ",
"metadata": {},
"customProperties": {
"source": "FlowchartJsonConverter.cs:46",
"notFoundConnections": [],
"canStartWorkflow": false,
"runAsynchronously": false
},
"activities": [
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "Start"
},
"memoryReference": {
"id": "2yPLgUudQEGPaschdLdkGg:input-0"
}
},
"id": "2yPLgUudQEGPaschdLdkGg",
"name": "WriteLine1",
"type": "Elsa.WriteLine",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -358.5,
"y": -229
},
"size": {
"width": 139.296875,
"height": 50
}
},
"displayText": "Start"
}
},
{
"body": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "zaAIlUtQDEaPqeITeRJ4_w",
"metadata": {},
"customProperties": {
"source": "FlowchartJsonConverter.cs:46",
"notFoundConnections": [],
"canStartWorkflow": false,
"runAsynchronously": false
},
"activities": [
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "Line 2"
},
"memoryReference": {
"id": "g3jVTdvU3EK7V5JqRqfqjQ:input-0"
}
},
"id": "g3jVTdvU3EK7V5JqRqfqjQ",
"name": "WriteLine1",
"type": "Elsa.WriteLine",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -279.5,
"y": -294
},
"size": {
"width": 139.296875,
"height": 50
}
},
"displayText": "Line 2"
}
}
],
"connections": []
},
"id": "eHAEEQ-Iu0GwM3mRxZin0Q",
"name": "FlowNode1",
"type": "Elsa.FlowNode",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -120,
"y": -179
},
"size": {
"width": 211.15625,
"height": 120
}
},
"displayText": "Line 2"
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "Line 1"
},
"memoryReference": {
"id": "7RQ2sbVHo0SxsyK0dvTKEA:input-0"
}
},
"id": "7RQ2sbVHo0SxsyK0dvTKEA",
"name": "WriteLine2",
"type": "Elsa.WriteLine",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -120,
"y": -279
},
"size": {
"width": 139.296875,
"height": 50
}
},
"displayText": "Line 1"
}
},
{
"mode": {
"typeName": "Elsa.Workflows.Core.Activities.Flowchart.Models.FlowJoinMode, Elsa.Workflows.Core",
"expression": {
"type": "Literal",
"value": "WaitAll"
},
"memoryReference": {
"id": "dVUuwMm5XUiYpiYoGo5JKA:input-0"
}
},
"id": "dVUuwMm5XUiYpiYoGo5JKA",
"name": "FlowJoin1",
"type": "Elsa.FlowJoin",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": 180.5,
"y": -229
},
"size": {
"width": 136.859375,
"height": 50
}
}
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "End"
},
"memoryReference": {
"id": "NpZ_yT_irUuE9ASEfYLxbw:input-0"
}
},
"id": "NpZ_yT_irUuE9ASEfYLxbw",
"name": "WriteLine3",
"type": "Elsa.WriteLine",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": 400,
"y": -229
},
"size": {
"width": 139.296875,
"height": 50
}
},
"displayText": "End"
}
}
],
"connections": [
{
"source": {
"activity": "2yPLgUudQEGPaschdLdkGg",
"port": "Done"
},
"target": {
"activity": "7RQ2sbVHo0SxsyK0dvTKEA",
"port": "In"
}
},
{
"source": {
"activity": "2yPLgUudQEGPaschdLdkGg",
"port": "Done"
},
"target": {
"activity": "eHAEEQ-Iu0GwM3mRxZin0Q",
"port": "In"
}
},
{
"source": {
"activity": "7RQ2sbVHo0SxsyK0dvTKEA",
"port": "Done"
},
"target": {
"activity": "dVUuwMm5XUiYpiYoGo5JKA",
"port": "In"
}
},
{
"source": {
"activity": "eHAEEQ-Iu0GwM3mRxZin0Q",
"port": "Done"
},
"target": {
"activity": "dVUuwMm5XUiYpiYoGo5JKA",
"port": "In"
}
},
{
"source": {
"activity": "dVUuwMm5XUiYpiYoGo5JKA",
"port": "Done"
},
"target": {
"activity": "NpZ_yT_irUuE9ASEfYLxbw",
"port": "In"
}
}
]
}
}
@@ -0,0 +1,245 @@
{
"id": "2484d1b14e2348f7a916ecef042536e6",
"definitionId": "7204b870946b4968aa786a2c91431242",
"name": "Flow Join Any",
"createdAt": "2023-09-08T19:07:08.435872+00:00",
"version": 1,
"toolVersion": "3.0.0.0",
"variables": [],
"inputs": [],
"outputs": [],
"outcomes": [],
"customProperties": {},
"isReadonly": false,
"isLatest": true,
"isPublished": false,
"options": {
"autoUpdateConsumingWorkflows": false
},
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "F44h770hvkSaOWI_jjbM_A",
"metadata": {},
"customProperties": {
"source": "FlowchartJsonConverter.cs:45",
"NotFoundConnectionsKey": [],
"canStartWorkflow": false,
"runAsynchronously": false
},
"activities": [
{
"mode": {
"typeName": "Elsa.Workflows.Core.Activities.Flowchart.Models.FlowJoinMode, Elsa.Workflows.Core",
"expression": {
"type": "Literal",
"value": "WaitAny"
},
"memoryReference": {
"id": "ImdIbHXtHkiLlrExFlW51A:input-0"
}
},
"id": "ImdIbHXtHkiLlrExFlW51A",
"name": "FlowJoin1",
"type": "Elsa.FlowJoin",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -180,
"y": -320
},
"size": {
"width": 136.859375,
"height": 50
}
}
}
},
{
"mode": {
"typeName": "Elsa.Workflows.Core.Activities.Flowchart.Models.FlowJoinMode, Elsa.Workflows.Core",
"expression": {
"type": "Literal",
"value": "WaitAny"
},
"memoryReference": {
"id": "cxnS1whoCESvwwPyBnno_Q:input-0"
}
},
"id": "cxnS1whoCESvwwPyBnno_Q",
"name": "FlowJoin2",
"type": "Elsa.FlowJoin",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -180,
"y": -80
},
"size": {
"width": 136.859375,
"height": 50
}
}
}
},
{
"mode": {
"typeName": "Elsa.Workflows.Core.Activities.Flowchart.Models.FlowJoinMode, Elsa.Workflows.Core",
"expression": {
"type": "Literal",
"value": "WaitAny"
},
"memoryReference": {
"id": "H0apwXYdoESn2TJ5T_3xAg:input-0"
}
},
"id": "H0apwXYdoESn2TJ5T_3xAg",
"name": "FlowJoin3",
"type": "Elsa.FlowJoin",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": 40,
"y": -194
},
"size": {
"width": 136.859375,
"height": 50
}
}
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "Start"
},
"memoryReference": {
"id": "Kn9d-EVn2EiaYJqHd8VcmQ:input-0"
}
},
"id": "Kn9d-EVn2EiaYJqHd8VcmQ",
"name": "WriteLine1",
"type": "Elsa.WriteLine",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -393.5,
"y": -194
},
"size": {
"width": 139.296875,
"height": 50
}
}
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "End"
},
"memoryReference": {
"id": "haHoM3A5EU6aYu_8dCYaYg:input-0"
}
},
"id": "haHoM3A5EU6aYu_8dCYaYg",
"name": "WriteLine2",
"type": "Elsa.WriteLine",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": 310.5,
"y": -194
},
"size": {
"width": 139.296875,
"height": 50
}
}
}
}
],
"connections": [
{
"source": {
"activity": "Kn9d-EVn2EiaYJqHd8VcmQ",
"port": "Done"
},
"target": {
"activity": "ImdIbHXtHkiLlrExFlW51A",
"port": "In"
}
},
{
"source": {
"activity": "Kn9d-EVn2EiaYJqHd8VcmQ",
"port": "Done"
},
"target": {
"activity": "cxnS1whoCESvwwPyBnno_Q",
"port": "In"
}
},
{
"source": {
"activity": "ImdIbHXtHkiLlrExFlW51A",
"port": "Done"
},
"target": {
"activity": "H0apwXYdoESn2TJ5T_3xAg",
"port": "In"
}
},
{
"source": {
"activity": "cxnS1whoCESvwwPyBnno_Q",
"port": "Done"
},
"target": {
"activity": "H0apwXYdoESn2TJ5T_3xAg",
"port": "In"
}
},
{
"source": {
"activity": "H0apwXYdoESn2TJ5T_3xAg",
"port": "Done"
},
"target": {
"activity": "haHoM3A5EU6aYu_8dCYaYg",
"port": "In"
}
}
]
}
}
@@ -0,0 +1,41 @@
using Elsa.Testing.Shared;
using Xunit.Abstractions;
namespace Elsa.Workflows.IntegrationTests.Scenarios.FlowchartCompletion;
/// <summary>
/// Tests for the flowchart completion feature using various workflow setups.
/// </summary>
public class Tests
{
private readonly CapturingTextWriter _capturingTextWriter = new();
private readonly IServiceProvider _services;
public Tests(ITestOutputHelper testOutputHelper)
{
_services = new TestApplicationBuilder(testOutputHelper).WithCapturingTextWriter(_capturingTextWriter).Build();
}
[Theory(DisplayName = "Workflows should complete successfully.")]
[InlineData("workflow1.json")]
[InlineData("workflow2.json")]
[InlineData("workflow3.json")]
[InlineData("workflow4.json")]
[InlineData("workflow5.json")]
[InlineData("workflow6.json")]
public async Task Test1(string workflowFileName)
{
// Populate registries.
await _services.PopulateRegistriesAsync();
// Import workflow.
var fileName = $"Scenarios/FlowchartCompletion/Workflows/{workflowFileName}";
var workflowDefinition = await _services.ImportWorkflowDefinitionAsync(fileName);
// Execute.
var workflowState = await _services.RunWorkflowUntilEndAsync(workflowDefinition.DefinitionId);
// Assert that the workflow has completed.
Assert.Equal(WorkflowStatus.Finished, workflowState.Status);
}
}
@@ -0,0 +1,127 @@
{
"id": "f7f7563b32de444797363df61e5109d3",
"definitionId": "d91eff09eaad43efb9d58332f1f22c82",
"name": "Flows",
"createdAt": "2023-04-18T13:32:26.294066+00:00",
"version": 1,
"variables": [],
"inputs": [],
"outputs": [],
"outcomes": [],
"metadata": {},
"isLatest": true,
"isPublished": false,
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "Flowchart1",
"metadata": {},
"applicationProperties": {
"NotFoundConnectionsKey": []
},
"start": "WriteLine1",
"activities": [
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "WriteLine1"
},
"memoryReference": {
"id": "WriteLine1:input-1"
}
},
"id": "WriteLine1",
"type": "Elsa.WriteLine",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 2020,
"y": 2320
}
},
"displayText": "WriteLine1"
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "WriteLine2"
},
"memoryReference": {
"id": "WriteLine2:input-1"
}
},
"id": "WriteLine2",
"type": "Elsa.WriteLine",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 2260,
"y": 2260
}
},
"displayText": "WriteLine2"
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "WriteLine3"
},
"memoryReference": {
"id": "WriteLine3:input-1"
}
},
"id": "WriteLine3",
"type": "Elsa.WriteLine",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 2260,
"y": 2380
}
},
"displayText": "WriteLine3"
}
}
],
"connections": [
{
"source": "WriteLine1",
"target": "WriteLine2",
"sourcePort": "Done",
"targetPort": "In"
},
{
"source": "WriteLine1",
"target": "WriteLine3",
"sourcePort": "Done",
"targetPort": "In"
}
]
}
}
@@ -0,0 +1,361 @@
{
"id": "dc6576d848aa4f46812356467575705f",
"definitionId": "991a4f39f7074a9988465230cfdb8d81",
"name": "Braided flow",
"createdAt": "2023-04-18T13:45:03.454228+00:00",
"version": 1,
"variables": [],
"inputs": [],
"outputs": [],
"outcomes": [],
"metadata": {},
"isLatest": true,
"isPublished": false,
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "Flowchart1",
"metadata": {},
"applicationProperties": {
"NotFoundConnectionsKey": []
},
"start": "WriteLine1",
"activities": [
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "WriteLine7"
},
"memoryReference": {
"id": "WriteLine7:input-1"
}
},
"id": "WriteLine7",
"type": "Elsa.WriteLine",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 2340,
"y": 2220
}
},
"displayText": "WriteLine7"
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "WriteLine9"
},
"memoryReference": {
"id": "WriteLine9:input-1"
}
},
"id": "WriteLine9",
"type": "Elsa.WriteLine",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 2560,
"y": 2320
}
},
"displayText": "WriteLine9"
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "WriteLine1"
},
"memoryReference": {
"id": "WriteLine1:input-1"
}
},
"id": "WriteLine1",
"type": "Elsa.WriteLine",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 1600,
"y": 2320
}
},
"displayText": "WriteLine1"
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "WriteLine2"
},
"memoryReference": {
"id": "WriteLine2:input-1"
}
},
"id": "WriteLine2",
"type": "Elsa.WriteLine",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 1820,
"y": 2220
}
},
"displayText": "WriteLine2"
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "WriteLine3"
},
"memoryReference": {
"id": "WriteLine3:input-1"
}
},
"id": "WriteLine3",
"type": "Elsa.WriteLine",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 1820,
"y": 2400
}
},
"displayText": "WriteLine3"
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "WriteLine4"
},
"memoryReference": {
"id": "WriteLine4:input-1"
}
},
"id": "WriteLine4",
"type": "Elsa.WriteLine",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 2060,
"y": 2160
}
},
"displayText": "WriteLine4"
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "WriteLine5"
},
"memoryReference": {
"id": "WriteLine5:input-1"
}
},
"id": "WriteLine5",
"type": "Elsa.WriteLine",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 2060,
"y": 2320
}
},
"displayText": "WriteLine5"
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "WriteLine6"
},
"memoryReference": {
"id": "WriteLine6:input-1"
}
},
"id": "WriteLine6",
"type": "Elsa.WriteLine",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 2060,
"y": 2480
}
},
"displayText": "WriteLine6"
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "WriteLine8"
},
"memoryReference": {
"id": "WriteLine8:input-1"
}
},
"id": "WriteLine8",
"type": "Elsa.WriteLine",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 2340,
"y": 2400
}
},
"displayText": "WriteLine8"
}
}
],
"connections": [
{
"source": "WriteLine5",
"target": "WriteLine7",
"sourcePort": "Done",
"targetPort": "In"
},
{
"source": "WriteLine8",
"target": "WriteLine9",
"sourcePort": "Done",
"targetPort": "In"
},
{
"source": "WriteLine7",
"target": "WriteLine9",
"sourcePort": "Done",
"targetPort": "In"
},
{
"source": "WriteLine1",
"target": "WriteLine2",
"sourcePort": "Done",
"targetPort": "In"
},
{
"source": "WriteLine2",
"target": "WriteLine4",
"sourcePort": "Done",
"targetPort": "In"
},
{
"source": "WriteLine3",
"target": "WriteLine5",
"sourcePort": "Done",
"targetPort": "In"
},
{
"source": "WriteLine1",
"target": "WriteLine3",
"sourcePort": "Done",
"targetPort": "In"
},
{
"source": "WriteLine4",
"target": "WriteLine7",
"sourcePort": "Done",
"targetPort": "In"
},
{
"source": "WriteLine2",
"target": "WriteLine5",
"sourcePort": "Done",
"targetPort": "In"
},
{
"source": "WriteLine6",
"target": "WriteLine8",
"sourcePort": "Done",
"targetPort": "In"
},
{
"source": "WriteLine3",
"target": "WriteLine6",
"sourcePort": "Done",
"targetPort": "In"
},
{
"source": "WriteLine5",
"target": "WriteLine8",
"sourcePort": "Done",
"targetPort": "In"
}
]
}
}
@@ -0,0 +1,469 @@
{
"id": "01146119d31041c195148662710d95c7",
"definitionId": "abb565fd716f4eb7be0510ab8ff36329",
"name": "Braided delayed flow",
"createdAt": "2023-04-18T14:06:58.250625+00:00",
"version": 4,
"variables": [],
"inputs": [],
"outputs": [],
"outcomes": [],
"metadata": {},
"isLatest": true,
"isPublished": false,
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "Flowchart1",
"metadata": {},
"applicationProperties": {
"NotFoundConnectionsKey": []
},
"start": "WriteLine1",
"activities": [
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "WriteLine1"
},
"memoryReference": {
"id": "WriteLine1:input-1"
}
},
"id": "WriteLine1",
"type": "Elsa.WriteLine",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 1600,
"y": 2320
}
},
"displayText": "WriteLine1"
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "WriteLine2"
},
"memoryReference": {
"id": "WriteLine2:input-1"
}
},
"id": "WriteLine2",
"type": "Elsa.WriteLine",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 1820,
"y": 2220
}
},
"displayText": "WriteLine2"
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "WriteLine3"
},
"memoryReference": {
"id": "WriteLine3:input-1"
}
},
"id": "WriteLine3",
"type": "Elsa.WriteLine",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 1820,
"y": 2400
}
},
"displayText": "WriteLine3"
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "WriteLine4"
},
"memoryReference": {
"id": "WriteLine4:input-1"
}
},
"id": "WriteLine4",
"type": "Elsa.WriteLine",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 2060,
"y": 2160
}
},
"displayText": "WriteLine4"
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "WriteLine5"
},
"memoryReference": {
"id": "WriteLine5:input-1"
}
},
"id": "WriteLine5",
"type": "Elsa.WriteLine",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 2060,
"y": 2320
}
},
"displayText": "WriteLine5"
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "WriteLine6"
},
"memoryReference": {
"id": "WriteLine6:input-1"
}
},
"id": "WriteLine6",
"type": "Elsa.WriteLine",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 2060,
"y": 2480
}
},
"displayText": "WriteLine6"
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "WriteLine8"
},
"memoryReference": {
"id": "WriteLine8:input-1"
}
},
"id": "WriteLine8",
"type": "Elsa.WriteLine",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 2460,
"y": 2400
}
},
"displayText": "WriteLine8"
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "WriteLine7"
},
"memoryReference": {
"id": "WriteLine7:input-1"
}
},
"id": "WriteLine7",
"type": "Elsa.WriteLine",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 2460,
"y": 2240
}
},
"displayText": "WriteLine7"
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "WriteLine9"
},
"memoryReference": {
"id": "WriteLine9:input-1"
}
},
"id": "WriteLine9",
"type": "Elsa.WriteLine",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 2680,
"y": 2300
}
},
"displayText": "WriteLine9"
}
},
{
"timeSpan": {
"typeName": "TimeSpan",
"expression": {
"type": "Literal",
"value": "00:00:05"
},
"memoryReference": {
"id": "Delay1:input-1"
}
},
"strategy": {
"typeName": "Elsa.Scheduling.Models.DelayBlockingStrategy, Elsa.Scheduling",
"expression": {
"type": "Literal",
"value": "NonBlocking"
},
"memoryReference": {
"id": "Delay1:input-2"
}
},
"autoBlockingThreshold": {
"typeName": "TimeSpan",
"expression": {
"type": "Literal",
"value": "00:00:05"
},
"memoryReference": {
"id": "Delay1:input-3"
}
},
"id": "Delay1",
"type": "Elsa.Delay",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 2260,
"y": 2240
}
}
}
},
{
"timeSpan": {
"typeName": "TimeSpan",
"expression": {
"type": "Literal",
"value": "00:00:10"
},
"memoryReference": {
"id": "Delay2:input-1"
}
},
"strategy": {
"typeName": "Elsa.Scheduling.Models.DelayBlockingStrategy, Elsa.Scheduling",
"expression": {
"type": "Literal",
"value": "NonBlocking"
},
"memoryReference": {
"id": "Delay2:input-2"
}
},
"autoBlockingThreshold": {
"typeName": "TimeSpan",
"expression": {
"type": "Literal",
"value": "00:00:05"
},
"memoryReference": {
"id": "Delay2:input-3"
}
},
"id": "Delay2",
"type": "Elsa.Delay",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 2280,
"y": 2440
}
}
}
}
],
"connections": [
{
"source": "WriteLine1",
"target": "WriteLine2",
"sourcePort": "Done",
"targetPort": "In"
},
{
"source": "WriteLine2",
"target": "WriteLine4",
"sourcePort": "Done",
"targetPort": "In"
},
{
"source": "WriteLine3",
"target": "WriteLine5",
"sourcePort": "Done",
"targetPort": "In"
},
{
"source": "WriteLine1",
"target": "WriteLine3",
"sourcePort": "Done",
"targetPort": "In"
},
{
"source": "WriteLine5",
"target": "WriteLine7",
"sourcePort": "Done",
"targetPort": "In"
},
{
"source": "WriteLine2",
"target": "WriteLine5",
"sourcePort": "Done",
"targetPort": "In"
},
{
"source": "WriteLine6",
"target": "Delay2",
"sourcePort": "Done",
"targetPort": "In"
},
{
"source": "WriteLine3",
"target": "WriteLine6",
"sourcePort": "Done",
"targetPort": "In"
},
{
"source": "WriteLine8",
"target": "WriteLine9",
"sourcePort": "Done",
"targetPort": "In"
},
{
"source": "Delay2",
"target": "WriteLine8",
"sourcePort": "Done",
"targetPort": "In"
},
{
"source": "WriteLine5",
"target": "WriteLine8",
"sourcePort": "Done",
"targetPort": "In"
},
{
"source": "Delay1",
"target": "WriteLine7",
"sourcePort": "Done",
"targetPort": "In"
},
{
"source": "WriteLine7",
"target": "WriteLine9",
"sourcePort": "Done",
"targetPort": "In"
},
{
"source": "WriteLine4",
"target": "Delay1",
"sourcePort": "Done",
"targetPort": "In"
}
]
}
}
@@ -0,0 +1,415 @@
{
"id": "44200e995aaf429d9975bd7177e271d0",
"definitionId": "7d0420cacaad4ae09c589a0355bbef44",
"name": "Braided conditional delayed flow",
"createdAt": "2023-04-18T14:37:02.902303+00:00",
"version": 1,
"variables": [],
"inputs": [],
"outputs": [],
"outcomes": [],
"metadata": {},
"isLatest": true,
"isPublished": false,
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "Flowchart1",
"metadata": {},
"applicationProperties": {
"NotFoundConnectionsKey": []
},
"start": "WriteLine1",
"activities": [
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "WriteLine1"
},
"memoryReference": {
"id": "WriteLine1:input-1"
}
},
"id": "WriteLine1",
"type": "Elsa.WriteLine",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 1600,
"y": 2320
}
},
"displayText": "WriteLine1"
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "WriteLine2"
},
"memoryReference": {
"id": "WriteLine2:input-1"
}
},
"id": "WriteLine2",
"type": "Elsa.WriteLine",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 1820,
"y": 2220
}
},
"displayText": "WriteLine2"
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "WriteLine3"
},
"memoryReference": {
"id": "WriteLine3:input-1"
}
},
"id": "WriteLine3",
"type": "Elsa.WriteLine",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 1820,
"y": 2400
}
},
"displayText": "WriteLine3"
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "WriteLine4"
},
"memoryReference": {
"id": "WriteLine4:input-1"
}
},
"id": "WriteLine4",
"type": "Elsa.WriteLine",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 2060,
"y": 2160
}
},
"displayText": "WriteLine4"
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "WriteLine6"
},
"memoryReference": {
"id": "WriteLine6:input-1"
}
},
"id": "WriteLine6",
"type": "Elsa.WriteLine",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 2060,
"y": 2480
}
},
"displayText": "WriteLine6"
}
},
{
"condition": {
"typeName": "Boolean",
"expression": {
"type": "Literal",
"value": "True"
},
"memoryReference": {
"id": "FlowDecision1:input-1"
}
},
"id": "FlowDecision1",
"type": "Elsa.FlowDecision",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 2060,
"y": 2320
}
}
}
},
{
"timeSpan": {
"typeName": "TimeSpan",
"expression": {
"type": "Literal",
"value": "00:00:05"
},
"memoryReference": {
"id": "Delay1:input-1"
}
},
"strategy": {
"typeName": "Elsa.Scheduling.Models.DelayBlockingStrategy, Elsa.Scheduling",
"expression": {
"type": "Literal",
"value": "NonBlocking"
},
"memoryReference": {
"id": "Delay1:input-2"
}
},
"autoBlockingThreshold": {
"typeName": "TimeSpan",
"expression": {
"type": "Literal",
"value": "00:00:05"
},
"memoryReference": {
"id": "Delay1:input-3"
}
},
"id": "Delay1",
"type": "Elsa.Delay",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 2320,
"y": 2220
}
}
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "WriteLine7"
},
"memoryReference": {
"id": "WriteLine7:input-1"
}
},
"id": "WriteLine7",
"type": "Elsa.WriteLine",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 2520,
"y": 2220
}
},
"displayText": "WriteLine7"
}
},
{
"timeSpan": {
"typeName": "TimeSpan",
"expression": {
"type": "Literal",
"value": "00:00:10"
},
"memoryReference": {
"id": "Delay2:input-1"
}
},
"strategy": {
"typeName": "Elsa.Scheduling.Models.DelayBlockingStrategy, Elsa.Scheduling",
"expression": {
"type": "Literal",
"value": "NonBlocking"
},
"memoryReference": {
"id": "Delay2:input-2"
}
},
"autoBlockingThreshold": {
"typeName": "TimeSpan",
"expression": {
"type": "Literal",
"value": "00:00:05"
},
"memoryReference": {
"id": "Delay2:input-3"
}
},
"id": "Delay2",
"type": "Elsa.Delay",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 2320,
"y": 2420
}
}
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "WriteLine8"
},
"memoryReference": {
"id": "WriteLine8:input-1"
}
},
"id": "WriteLine8",
"type": "Elsa.WriteLine",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 2520,
"y": 2420
}
},
"displayText": "WriteLine8"
}
}
],
"connections": [
{
"source": "WriteLine1",
"target": "WriteLine2",
"sourcePort": "Done",
"targetPort": "In"
},
{
"source": "WriteLine2",
"target": "WriteLine4",
"sourcePort": "Done",
"targetPort": "In"
},
{
"source": "WriteLine3",
"target": "FlowDecision1",
"sourcePort": "Done",
"targetPort": "In"
},
{
"source": "WriteLine1",
"target": "WriteLine3",
"sourcePort": "Done",
"targetPort": "In"
},
{
"source": "WriteLine3",
"target": "WriteLine6",
"sourcePort": "Done",
"targetPort": "In"
},
{
"source": "FlowDecision1",
"target": "Delay1",
"sourcePort": "True",
"targetPort": "In"
},
{
"source": "WriteLine2",
"target": "FlowDecision1",
"sourcePort": "Done",
"targetPort": "In"
},
{
"source": "Delay1",
"target": "WriteLine7",
"sourcePort": "Done",
"targetPort": "In"
},
{
"source": "Delay2",
"target": "WriteLine8",
"sourcePort": "Done",
"targetPort": "In"
},
{
"source": "FlowDecision1",
"target": "Delay2",
"sourcePort": "False",
"targetPort": "In"
}
]
}
}
@@ -0,0 +1,207 @@
{
"id": "3365d4c0198e459f834b799cbfe91154",
"definitionId": "4aa1e566da264ab6b5495bedb734096d",
"name": "Forking, Containment and Blocking",
"description": "A combination of a fork, a containment and a blocking activity.",
"version": 1,
"toolVersion": "3.0.0.0",
"variables": [],
"inputs": [],
"outputs": [],
"outcomes": [],
"customProperties": {},
"isReadonly": false,
"isLatest": true,
"isPublished": true,
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "uEnFWEdaWkadgIMHT2umLA",
"metadata": {},
"customProperties": {
"NotFoundConnectionsKey": [],
"canStartWorkflow": false,
"runAsynchronously": false
},
"activities": [
{
"id": "MLDV_altcUO5MwP7p5xJWw",
"name": "Start1",
"type": "Elsa.Start",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -480,
"y": -566.046875
},
"size": {
"width": 102.21875,
"height": 50
}
}
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "Intermediary"
},
"memoryReference": {
"id": "EIh3Q0Um1U-QSCh1b3GBjQ:input-0"
}
},
"id": "EIh3Q0Um1U-QSCh1b3GBjQ",
"name": "WriteLine1",
"type": "Elsa.WriteLine",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -228.5,
"y": -476.046875
},
"size": {
"width": 139.296875,
"height": 50
}
}
}
},
{
"text": null,
"id": "0Z7Velr5CEW_r7YYoeEjBQ",
"name": "WriteLine2",
"type": "Elsa.WriteLine",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": 69.34375,
"y": -476.046875
},
"size": {
"width": 139.296875,
"height": 50
}
}
}
},
{
"body": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "NJ5fh2bUWU-TxWeXsbNaSg",
"metadata": {},
"customProperties": {
"NotFoundConnectionsKey": [],
"canStartWorkflow": false,
"runAsynchronously": false
},
"activities": [
{
"eventName": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "Event4"
},
"memoryReference": {
"id": "8KVHRw-WpUizyxOq0C6Nvg:input-0"
}
},
"result": null,
"id": "8KVHRw-WpUizyxOq0C6Nvg",
"name": "Event1",
"type": "Elsa.Event",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -840,
"y": -500
},
"size": {
"width": 106.828125,
"height": 50
}
}
}
}
],
"connections": []
},
"id": "HG4wfBhvlEW_149HwNyzHw",
"name": "FlowNode1",
"type": "Elsa.FlowNode",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -228.5,
"y": -704.109375
},
"size": {
"width": 211.15625,
"height": 120
}
}
}
}
],
"connections": [
{
"source": {
"activity": "MLDV_altcUO5MwP7p5xJWw",
"port": "Done"
},
"target": {
"activity": "HG4wfBhvlEW_149HwNyzHw",
"port": "In"
}
},
{
"source": {
"activity": "MLDV_altcUO5MwP7p5xJWw",
"port": "Done"
},
"target": {
"activity": "EIh3Q0Um1U-QSCh1b3GBjQ",
"port": "In"
}
},
{
"source": {
"activity": "EIh3Q0Um1U-QSCh1b3GBjQ",
"port": "Done"
},
"target": {
"activity": "0Z7Velr5CEW_r7YYoeEjBQ",
"port": "In"
}
}
]
}
}
@@ -0,0 +1,198 @@
{
"id": "e5190066597846cc926f8ae644d706f4",
"definitionId": "b07725956291433abee7ff229b415194",
"name": "Parallel For Each",
"createdAt": "2023-09-10T16:32:58.422674+00:00",
"version": 3,
"toolVersion": "3.0.0.0",
"variables": [],
"inputs": [],
"outputs": [],
"outcomes": [],
"customProperties": {},
"isReadonly": false,
"isLatest": true,
"isPublished": true,
"options": {
"autoUpdateConsumingWorkflows": false
},
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "oplDmRa_zEqH8PCdTcFh8A",
"metadata": {},
"customProperties": {
"source": "FlowchartJsonConverter.cs:45",
"NotFoundConnectionsKey": [],
"canStartWorkflow": false,
"runAsynchronously": false
},
"activities": [
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "Start"
},
"memoryReference": {
"id": "NTW8X70bTEaBtlYyU44PcA:input-0"
}
},
"id": "NTW8X70bTEaBtlYyU44PcA",
"name": "WriteLine1",
"type": "Elsa.WriteLine",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -352,
"y": -286.9921875
},
"size": {
"width": 139.296875,
"height": 50
}
}
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "End"
},
"memoryReference": {
"id": "6Bf011ZOvUWOfQ1rKJEDFA:input-0"
}
},
"id": "6Bf011ZOvUWOfQ1rKJEDFA",
"name": "WriteLine2",
"type": "Elsa.WriteLine",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": 169,
"y": -286.9921875
},
"size": {
"width": 139.296875,
"height": 50
}
}
}
},
{
"items": {
"typeName": "Object[]",
"expression": {
"type": "JavaScript",
"value": "[\u0022Apple\u0022, \u0022Banana\u0022, \u0022Cherry\u0022]"
},
"memoryReference": {
"id": "By4rVs_-2kSDENFO-bniRg:input-0"
}
},
"body": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "ylZLDh7FQ0WmnbdnRJHutg",
"metadata": {},
"customProperties": {
"source": "FlowchartJsonConverter.cs:45",
"NotFoundConnectionsKey": [],
"canStartWorkflow": false,
"runAsynchronously": false
},
"activities": [
{
"text": {
"typeName": "String",
"expression": {
"type": "JavaScript",
"value": "\u0060Current fruit: ${getCurrentValue()}\u0060"
},
"memoryReference": {
"id": "fht2JVY0G0KURaRYB9jqlw:input-0"
}
},
"id": "fht2JVY0G0KURaRYB9jqlw",
"name": "WriteLine1",
"type": "Elsa.WriteLine",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -459.5,
"y": -374
},
"size": {
"width": 139.296875,
"height": 50
}
}
}
}
],
"connections": []
},
"id": "By4rVs_-2kSDENFO-bniRg",
"name": "ParallelForEach1",
"type": "Elsa.ParallelForEach",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -100,
"y": -320
},
"size": {
"width": 149.203125,
"height": 116.015625
}
}
}
}
],
"connections": [
{
"source": {
"activity": "NTW8X70bTEaBtlYyU44PcA",
"port": "Done"
},
"target": {
"activity": "By4rVs_-2kSDENFO-bniRg",
"port": "In"
}
},
{
"source": {
"activity": "By4rVs_-2kSDENFO-bniRg",
"port": "Done"
},
"target": {
"activity": "6Bf011ZOvUWOfQ1rKJEDFA",
"port": "In"
}
}
]
}
}
@@ -0,0 +1,9 @@
namespace Elsa.Workflows.IntegrationTests.Scenarios.FlowchartNextActivity.Activities;
public class CustomActivity : CodeActivity
{
protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
{
await context.CompleteActivityAsync();
}
}
@@ -0,0 +1,315 @@
using Elsa.Expressions.Models;
using Elsa.Testing.Shared;
using Elsa.Workflows.Activities;
using Elsa.Workflows.Activities.Flowchart.Activities;
using Elsa.Workflows.Activities.Flowchart.Models;
using Elsa.Workflows.IntegrationTests.Scenarios.FlowchartNextActivity.Workflows;
using Elsa.Workflows.Memory;
using Microsoft.Extensions.DependencyInjection;
using Xunit.Abstractions;
namespace Elsa.Workflows.IntegrationTests.Scenarios.FlowchartNextActivity;
public class FlowchartNextActivityTests
{
private readonly CapturingTextWriter _capturingTextWriter = new();
private readonly IServiceProvider _services;
private readonly IWorkflowRunner _workflowRunner;
public FlowchartNextActivityTests(ITestOutputHelper testOutputHelper)
{
_services = new TestApplicationBuilder(testOutputHelper)
.WithCapturingTextWriter(_capturingTextWriter)
.AddActivitiesFrom<FlowchartNextActivityTests>()
.Build();
_workflowRunner = _services.GetRequiredService<IWorkflowRunner>();
}
[Fact(DisplayName = "Flowchart only schedules next activity connected to outcome of previous activity.")]
public async Task Test1()
{
await _services.PopulateRegistriesAsync();
await _workflowRunner.RunAsync<FlowchartWorkflow>();
var lines = _capturingTextWriter.Lines.ToList();
Assert.Equal(new[] { "Line 1" }, lines);
}
[Fact(DisplayName = "Flowchart with backward connections and a dangling activity")]
public async Task BackwardConnectionTest()
{
var workflow = new TestWorkflow(workflowBuilder =>
{
var loopVariable = new Variable<int>("LoopCount", 0);
var start = new Start();
var dangling = new WriteLine("dangling");
var writeLineDecision = new FlowSwitch()
{
Cases = {
new FlowSwitchCase("LessThanThree", new Expression("JavaScript", "getVariable('LoopCount') < 3")),
new FlowSwitchCase("LessThanOne", new Expression("JavaScript", "getVariable('LoopCount') < 1")),
},
Mode = new(SwitchMode.MatchAny)
};
var a = new WriteLine("A");
var b = new WriteLine("B");
var c = new WriteLine("C");
var incrementLoop = new SetVariable()
{
Variable = loopVariable,
Value = new Models.Input<object?>(new Expression("JavaScript", "getVariable('LoopCount') + 1"))
};
var loopbackDecision = new FlowSwitch()
{
Cases = {
new FlowSwitchCase("EqualOne", new Expression("JavaScript", "getVariable('LoopCount') == 1")),
new FlowSwitchCase("LessThanFour", new Expression("JavaScript", "getVariable('LoopCount') < 4")),
},
Mode = new(SwitchMode.MatchFirst)
};
var d = new WriteLine("D");
var e = new WriteLine("E");
var f = new WriteLine("F");
var end = new End();
workflowBuilder.Root = new Flowchart
{
Variables =
{
loopVariable
},
Activities =
{
start,
dangling,
writeLineDecision,
a,
b,
c,
incrementLoop,
loopbackDecision,
d,
e,
f,
end
},
Connections =
{
new(start, writeLineDecision),
new(dangling, writeLineDecision),
new(new Endpoint(writeLineDecision, "LessThanThree"), new Endpoint(a)),
new(new Endpoint(writeLineDecision, "LessThanThree"), new Endpoint(b)),
new(new Endpoint(writeLineDecision, "LessThanOne"), new Endpoint(c)),
new(new Endpoint(writeLineDecision, "Default"), new Endpoint(incrementLoop)),
new(a, incrementLoop),
new(b, incrementLoop),
new(c, incrementLoop),
new(incrementLoop, loopbackDecision),
new(new Endpoint(loopbackDecision, "EqualOne"), new Endpoint(d)),
new(d, incrementLoop),
new(new Endpoint(loopbackDecision, "LessThanFour"), new Endpoint(e)),
new(e, writeLineDecision),
new(new Endpoint(loopbackDecision, "Default"), new Endpoint(f)),
new(f, end),
}
};
});
await _services.PopulateRegistriesAsync();
var result = await _workflowRunner.RunAsync(workflow);
var lines = _capturingTextWriter.Lines.ToList();
Assert.Equal(WorkflowSubStatus.Finished, result.WorkflowState.SubStatus);
Assert.Equal(new[] { "A", "B", "C", "D", "E", "A", "B", "E", "F" }, lines);
}
[Fact(DisplayName = "Flowchart with an invalid backward connection")]
public async Task InvalidBackwardConnectionTest()
{
var workflow = new TestWorkflow(workflowBuilder =>
{
var start = new Start() { Id = "Start" };
var a = new WriteLine("A") { Id = "WriteLineA" };
var b = new WriteLine("B") { Id = "WriteLineB" };
var c = new WriteLine("C") { Id = "WriteLineC" };
var d = new WriteLine("D") { Id = "WriteLineD" };
var e = new WriteLine("E") { Id = "WriteLineE" };
workflowBuilder.Root = new Flowchart
{
Activities =
{
start,
a,
b,
c,
d,
e,
},
Connections =
{
new(start, a),
new(a, b),
new(b, c),
new(b, d),
new(c, e),
new(d, e),
new(e, c),
}
};
});
await _services.PopulateRegistriesAsync();
var result = await _workflowRunner.RunAsync(workflow);
var lines = _capturingTextWriter.Lines.ToList();
Assert.Equal(WorkflowSubStatus.Faulted, result.WorkflowState.SubStatus);
Assert.Equal(1, result.WorkflowState.Incidents.Count());
Assert.Equal("Invalid backward connection: Every path from the source ('WriteLineE') must go through the target ('WriteLineC') when tracing back to the start.", result.WorkflowState.Incidents.First().Message);
Assert.Equal(new[] { "A", "B", "C", "D", "E" }, lines);
}
[Theory(DisplayName = "Flowchart with a Join activity executed multiple times")]
[InlineData(FlowJoinMode.WaitAll)]
[InlineData(FlowJoinMode.WaitAny)]
public async Task WaitAnyLoopTest(FlowJoinMode joinMode)
{
var workflow = new TestWorkflow(workflowBuilder =>
{
var loopVariable = new Variable<int>("LoopCount", 0);
var start = new Start();
var a = new WriteLine("A");
var b = new WriteLine("B");
var c = new WriteLine("C");
var d = new WriteLine("D");
var join = new FlowJoin()
{
Mode = new(joinMode)
};
var incrementLoop = new SetVariable()
{
Variable = loopVariable,
Value = new Models.Input<object?>(new Expression("JavaScript", "getVariable('LoopCount') + 1"))
};
var loopbackDecision = new FlowSwitch()
{
Cases = {
new FlowSwitchCase("LessThanThree", new Expression("JavaScript", "getVariable('LoopCount') < 3")),
},
Mode = new(SwitchMode.MatchFirst)
};
var end = new End();
workflowBuilder.Root = new Flowchart
{
Variables =
{
loopVariable
},
Activities =
{
start,
a,
b,
c,
d,
join,
incrementLoop,
loopbackDecision,
end
},
Connections =
{
new(start, a),
new(a, b),
new(a, c),
new(a, d),
new(b, join),
new(c, join),
new(d, join),
new(join, incrementLoop),
new(incrementLoop,loopbackDecision),
new(new Endpoint(loopbackDecision, "LessThanThree"), new Endpoint(a)),
new(new Endpoint(loopbackDecision, "Default"), new Endpoint(end)),
}
};
});
await _services.PopulateRegistriesAsync();
var result = await _workflowRunner.RunAsync(workflow);
var lines = _capturingTextWriter.Lines.ToList();
Assert.Equal(WorkflowSubStatus.Finished, result.WorkflowState.SubStatus);
Assert.Equal(new[] { "A", "B", "C", "D", "A", "B", "C", "D", "A", "B", "C", "D"}, lines);
}
[Theory(DisplayName = "Flowchart with a Join activity executed multiple times, bug 6479")]
[InlineData(FlowJoinMode.WaitAll)]
[InlineData(FlowJoinMode.WaitAny)]
public async Task WaitLoopBug6479Test(FlowJoinMode joinMode)
{
var workflow = new TestWorkflow(workflowBuilder =>
{
var loopVariable = new Variable<int>("LoopCount", 0);
var start = new Start();
var loopbackSwitch = new FlowSwitch()
{
Cases = {
new FlowSwitchCase("DoLoopback", new Expression("JavaScript", "getVariable('LoopCount') < 3")),
},
Mode = new(SwitchMode.MatchFirst)
};
var a = new WriteLine("A");
var incrementLoop = new SetVariable()
{
Variable = loopVariable,
Value = new Models.Input<object?>(new Expression("JavaScript", "getVariable('LoopCount') + 1"))
};
var join = new FlowJoin()
{
Mode = new(joinMode)
};
var b = new WriteLine("B");
var end = new End();
workflowBuilder.Root = new Flowchart
{
Variables =
{
loopVariable
},
Activities =
{
start,
loopbackSwitch,
a,
incrementLoop,
join,
b,
end
},
Connections =
{
new(start, loopbackSwitch),
new(new Endpoint(loopbackSwitch, "DoLoopback"), new Endpoint(a)),
new(new Endpoint(loopbackSwitch, "DoLoopback"), new Endpoint(incrementLoop)),
new(new Endpoint(loopbackSwitch, "Default"), new Endpoint(b)),
new(a, join),
new(incrementLoop, join),
new(join, loopbackSwitch),
new(b, end),
}
};
});
await _services.PopulateRegistriesAsync();
var result = await _workflowRunner.RunAsync(workflow);
var lines = _capturingTextWriter.Lines.ToList();
Assert.Equal(WorkflowSubStatus.Finished, result.WorkflowState.SubStatus);
Assert.Equal(new[] { "A", "A", "A", "B" }, lines);
}
}
@@ -0,0 +1,31 @@
using Elsa.Workflows.Activities;
using Elsa.Workflows.Activities.Flowchart.Activities;
using Elsa.Workflows.Activities.Flowchart.Models;
using Elsa.Workflows.IntegrationTests.Scenarios.FlowchartNextActivity.Activities;
namespace Elsa.Workflows.IntegrationTests.Scenarios.FlowchartNextActivity.Workflows;
public class FlowchartWorkflow : WorkflowBase
{
protected override void Build(IWorkflowBuilder builder)
{
var customActivity = new CustomActivity();
var writeLine1 = new WriteLine("Line 1");
var writeLine2 = new WriteLine("Line 2");
builder.Root = new Flowchart
{
Activities =
{
customActivity,
writeLine1,
writeLine2
},
Connections =
{
new Connection(new Endpoint(customActivity, "Done"), new Endpoint(writeLine1)),
new Connection(new Endpoint(customActivity, "Fake"), new Endpoint(writeLine2)),
}
};
}
}
@@ -0,0 +1,40 @@
using Elsa.Expressions.Models;
using Elsa.JavaScript.Activities;
using Elsa.Workflows.Activities;
using Elsa.Workflows.Memory;
namespace Elsa.Workflows.IntegrationTests.Scenarios.HttpRequestWithLiquid;
/// <summary>
/// A workflow that use javascript to get some data, use them with some liquid expressions
/// </summary>
public class JavascriptAndLiquidWorkflow : WorkflowBase
{
protected override void Build(IWorkflowBuilder builder)
{
var products = new Variable<object>("Products", null!).WithStorageDriver<WorkflowInstanceStorageDriver>();
var product = new Variable<object>("Product", null!).WithStorageDriver<WorkflowInstanceStorageDriver>();
builder.Root = new Sequence
{
Variables = { products, product },
Activities =
{
new RunJavaScript
{
Script = new(@"setProducts([{""id"":1, ""price"":12.99}, {""id"":2, ""price"":10}, {""id"":3, ""price"":1}])")
},
new WriteLine(new Expression("Liquid", "First product id: {{ Variables.Products[0].id }}")),
new WriteLine(new Expression("Liquid", "First product price rounded: {{ Variables.Products[0].price | round }}")),
new WriteLine(new Expression("Liquid", "First product as json: {{ Variables.Products[0] | json }}")),
new WriteLine(new Expression("Liquid", "Second product id: {{ Variables.Products[1].id }}")),
new RunJavaScript
{
Script = new(@"variables.Product = {""id"":2, ""price"":10}")
},
new WriteLine(new Expression("Liquid", "Single product id: {{ Variables.Product.id }}")),
new WriteLine(new Expression("Liquid", "Single product as json: {{ Variables.Product | json }}")),
}
};
}
}
@@ -0,0 +1,48 @@
using Elsa.Extensions;
using Elsa.Testing.Shared;
using Elsa.Workflows.Models;
using Microsoft.Extensions.DependencyInjection;
using Xunit.Abstractions;
namespace Elsa.Workflows.IntegrationTests.Scenarios.HttpRequestWithLiquid;
public sealed class Tests : IDisposable
{
private readonly IWorkflowRunner _workflowRunner;
private readonly CapturingTextWriter _capturingTextWriter = new();
private readonly IServiceProvider _services;
public Tests(ITestOutputHelper testOutputHelper)
{
_services = new TestApplicationBuilder(testOutputHelper)
.WithCapturingTextWriter(_capturingTextWriter)
.ConfigureElsa(configure => configure.UseHttp())
.Build();
_workflowRunner = _services.GetRequiredService<IWorkflowRunner>();
}
[Fact]
public async Task GetProducts()
{
await _services.PopulateRegistriesAsync();
RunWorkflowResult result = await _workflowRunner.RunAsync(new JavascriptAndLiquidWorkflow());
Assert.Equal(WorkflowStatus.Finished, result.WorkflowState.Status);
Assert.Empty(result.WorkflowState.Incidents);
Assert.Equal(WorkflowSubStatus.Finished, result.WorkflowState.SubStatus);
var lines = _capturingTextWriter.Lines.ToList();
Assert.Contains("First product id: 1", lines);
Assert.Contains("First product price rounded: 13", lines);
Assert.Contains("First product as json: {\"id\":1,\"price\":12.99}", lines);
Assert.Contains("Second product id: 2", lines);
Assert.Contains("Single product id: 2", lines);
Assert.Contains("Single product as json: {\"id\":2,\"price\":10}", lines);
}
public void Dispose()
{
_capturingTextWriter.Dispose();
GC.SuppressFinalize(this);
}
}
@@ -0,0 +1,36 @@
using Elsa.Testing.Shared;
using Elsa.Workflows.IntegrationTests.Scenarios.ImplicitJoins.Workflows;
using Microsoft.Extensions.DependencyInjection;
using Xunit.Abstractions;
namespace Elsa.Workflows.IntegrationTests.Scenarios.ImplicitJoins;
public class BraidedWorkflowTests
{
private readonly IWorkflowRunner _workflowRunner;
private readonly CapturingTextWriter _capturingTextWriter = new();
private readonly IServiceProvider _services;
public BraidedWorkflowTests(ITestOutputHelper testOutputHelper)
{
_services = new TestApplicationBuilder(testOutputHelper).WithCapturingTextWriter(_capturingTextWriter).Build();
_workflowRunner = _services.GetRequiredService<IWorkflowRunner>();
}
[Fact(DisplayName = "Braided workflows are executed correctly")]
public async Task Test1()
{
await _services.PopulateRegistriesAsync();
await _workflowRunner.RunAsync<BraidedWorkflow>();
var lines = _capturingTextWriter.Lines.ToList();
Assert.Equal(new[] { "WriteLine1", "WriteLine2", "WriteLine3", "WriteLine4", "WriteLine5", "WriteLine6", "WriteLine7" }, lines);
}
[Fact(DisplayName = "Braided workflows complete the workflow")]
public async Task Test2()
{
await _services.PopulateRegistriesAsync();
var result = await _workflowRunner.RunAsync<BraidedWorkflow>();
Assert.Equal(WorkflowStatus.Finished, result.WorkflowState.Status);
}
}
@@ -0,0 +1,36 @@
using Elsa.Testing.Shared;
using Elsa.Workflows.IntegrationTests.Scenarios.ImplicitJoins.Workflows;
using Microsoft.Extensions.DependencyInjection;
using Xunit.Abstractions;
namespace Elsa.Workflows.IntegrationTests.Scenarios.ImplicitJoins;
public class ImplicitWorkflowTests
{
private readonly IWorkflowRunner _workflowRunner;
private readonly CapturingTextWriter _capturingTextWriter = new();
private readonly IServiceProvider _services;
public ImplicitWorkflowTests(ITestOutputHelper testOutputHelper)
{
_services = new TestApplicationBuilder(testOutputHelper).WithCapturingTextWriter(_capturingTextWriter).Build();
_workflowRunner = _services.GetRequiredService<IWorkflowRunner>();
}
[Fact(DisplayName = "Implicit loop workflows are executed correctly")]
public async Task Test1()
{
await _services.PopulateRegistriesAsync();
await _workflowRunner.RunAsync<ImplicitLoopWorkflow>();
var lines = _capturingTextWriter.Lines.ToList();
Assert.Equal(new[] { "Start", "Retry", "End" }, lines);
}
[Fact(DisplayName = "Implicit loop workflows complete the workflow")]
public async Task Test2()
{
await _services.PopulateRegistriesAsync();
var result = await _workflowRunner.RunAsync<ImplicitLoopWorkflow>();
Assert.Equal(WorkflowStatus.Finished, result.WorkflowState.Status);
}
}
@@ -0,0 +1,42 @@
using Elsa.Common.Models;
using Elsa.Testing.Shared;
using Elsa.Workflows.Runtime;
using Elsa.Workflows.Runtime.Filters;
using Microsoft.Extensions.DependencyInjection;
using Xunit.Abstractions;
namespace Elsa.Workflows.IntegrationTests.Scenarios.ImplicitJoins;
public class JoinRunsOnceTests
{
private readonly CapturingTextWriter _capturingTextWriter = new();
private readonly IServiceProvider _services;
public JoinRunsOnceTests(ITestOutputHelper testOutputHelper)
{
_services = new TestApplicationBuilder(testOutputHelper).WithCapturingTextWriter(_capturingTextWriter).Build();
}
[Fact(DisplayName = "The Join activity executes only once, not twice")]
public async Task Test1()
{
// Populate registries.
await _services.PopulateRegistriesAsync();
// Import workflow.
var workflowDefinition = await _services.ImportWorkflowDefinitionAsync($"Scenarios/ImplicitJoins/Workflows/join.json");
// Execute.
var state = await _services.RunWorkflowUntilEndAsync(workflowDefinition.DefinitionId);
// Assert.
var journal = await _services.GetRequiredService<IWorkflowExecutionLogStore>().FindManyAsync(new WorkflowExecutionLogRecordFilter
{
WorkflowInstanceId = state.Id,
ActivityId = "802725996be1b582",
EventName = "Completed"
}, PageArgs.All);
Assert.Single(journal.Items);
}
}
@@ -0,0 +1,42 @@
using Elsa.Common.Models;
using Elsa.Testing.Shared;
using Elsa.Workflows.Runtime;
using Elsa.Workflows.Runtime.Filters;
using Microsoft.Extensions.DependencyInjection;
using Xunit.Abstractions;
namespace Elsa.Workflows.IntegrationTests.Scenarios.ImplicitJoins;
public class ParallelJoinCompletesTests
{
private readonly CapturingTextWriter _capturingTextWriter = new();
private readonly IServiceProvider _services;
public ParallelJoinCompletesTests(ITestOutputHelper testOutputHelper)
{
_services = new TestApplicationBuilder(testOutputHelper).WithCapturingTextWriter(_capturingTextWriter).Build();
}
[Fact(DisplayName = "The ParallelForEach activity completes when its Body contains a Join activity")]
public async Task Test1()
{
// Populate registries.
await _services.PopulateRegistriesAsync();
// Import workflow.
var workflowDefinition = await _services.ImportWorkflowDefinitionAsync("Scenarios/ImplicitJoins/Workflows/parallel-join.json");
// Execute.
var state = await _services.RunWorkflowUntilEndAsync(workflowDefinition.DefinitionId);
// Assert.
var journal = await _services.GetRequiredService<IWorkflowExecutionLogStore>().FindManyAsync(new()
{
WorkflowInstanceId = state.Id,
ActivityId = "70fc1183cd5800f2",
EventName = "Completed"
}, PageArgs.All);
Assert.Single(journal.Items);
}
}
@@ -0,0 +1,51 @@
using Elsa.Workflows.Activities;
using Elsa.Workflows.Activities.Flowchart.Activities;
using Elsa.Workflows.Activities.Flowchart.Models;
namespace Elsa.Workflows.IntegrationTests.Scenarios.ImplicitJoins.Workflows;
public class BraidedWorkflow : WorkflowBase
{
protected override void Build(IWorkflowBuilder workflow)
{
var writeLine1 = new WriteLine("WriteLine1");
var writeLine2 = new WriteLine("WriteLine2");
var writeLine3 = new WriteLine("WriteLine3");
var writeLine4 = new WriteLine("WriteLine4");
var writeLine5 = new WriteLine("WriteLine5");
var writeLine6 = new WriteLine("WriteLine6");
var writeLine7 = new WriteLine("WriteLine7");
workflow.Root = new Flowchart
{
Start = writeLine1,
Activities =
{
writeLine1,
writeLine2,
writeLine3,
writeLine4,
writeLine5,
writeLine6,
writeLine7,
},
Connections =
{
new Connection(writeLine1, writeLine2),
new Connection(writeLine1, writeLine3),
new Connection(writeLine2, writeLine4),
new Connection(writeLine2, writeLine5),
new Connection(writeLine3, writeLine5),
new Connection(writeLine3, writeLine6),
new Connection(writeLine4, writeLine7),
new Connection(writeLine5, writeLine7),
new Connection(writeLine6, writeLine7),
}
};
}
}
@@ -0,0 +1,41 @@
using Elsa.Workflows.Activities;
using Elsa.Workflows.Activities.Flowchart.Activities;
using Elsa.Workflows.Activities.Flowchart.Models;
namespace Elsa.Workflows.IntegrationTests.Scenarios.ImplicitJoins.Workflows;
public class ImplicitLoopWorkflow : WorkflowBase
{
protected override void Build(IWorkflowBuilder builder)
{
var counterVariable = builder.WithVariable(0);
var start = new WriteLine("Start");
var incrementCounter = new SetVariable<int>(counterVariable, context => counterVariable.Get(context) + 1);
var counterGreaterThanOne = new FlowDecision(context => counterVariable.Get(context) > 1);
var retry = new WriteLine("Retry");
var end = new WriteLine("End");
builder.Root = new Flowchart
{
Start = start,
Activities =
{
start,
incrementCounter,
counterGreaterThanOne,
retry,
end
},
Connections =
{
new Connection(start, incrementCounter),
new Connection(incrementCounter, counterGreaterThanOne),
new Connection(new Endpoint(counterGreaterThanOne, "False"), new Endpoint(retry)),
new Connection(new Endpoint(counterGreaterThanOne, "True"), new Endpoint(end)),
new Connection(retry, incrementCounter),
}
};
}
}
@@ -0,0 +1,215 @@
{
"id": "825e9d347e2f3094",
"definitionId": "f64e42304661f407",
"name": "Joins",
"createdAt": "2023-12-13T21:41:53.628415+00:00",
"version": 3,
"toolVersion": "3.0.0.0",
"variables": [],
"inputs": [],
"outputs": [],
"outcomes": [],
"customProperties": {
"Elsa:WorkflowContextProviderTypes": []
},
"isReadonly": false,
"isLatest": true,
"isPublished": false,
"options": {
"autoUpdateConsumingWorkflows": false
},
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "8a03bf79d15fd48a",
"nodeId": "Workflow1:8a03bf79d15fd48a",
"metadata": {},
"customProperties": {
"source": "FlowchartJsonConverter.cs:45",
"notFoundConnections": [],
"canStartWorkflow": false,
"runAsynchronously": false
},
"activities": [
{
"id": "c7624645ff77c7d0",
"nodeId": "Workflow1:8a03bf79d15fd48a:c7624645ff77c7d0",
"name": "Start1",
"type": "Elsa.Start",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -400.5,
"y": 212
},
"size": {
"width": 102.21875,
"height": 50
}
}
}
},
{
"text": null,
"id": "281e9ea6d278b231",
"nodeId": "Workflow1:8a03bf79d15fd48a:281e9ea6d278b231",
"name": "WriteLine2",
"type": "Elsa.WriteLine",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -160.5,
"y": 305
},
"size": {
"width": 139.296875,
"height": 50
}
}
}
},
{
"mode": {
"typeName": "Elsa.Workflows.Core.Activities.Flowchart.Models.FlowJoinMode, Elsa.Workflows.Core",
"expression": {
"type": "Literal",
"value": "WaitAny"
},
"memoryReference": {
"id": "802725996be1b582:input-0"
}
},
"id": "802725996be1b582",
"nodeId": "Workflow1:8a03bf79d15fd48a:802725996be1b582",
"name": "FlowJoin1",
"type": "Elsa.FlowJoin",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": 80,
"y": 200
},
"size": {
"width": 98.265625,
"height": 50
}
}
}
},
{
"text": null,
"id": "bfc88f064cf394ed",
"nodeId": "Workflow1:8a03bf79d15fd48a:bfc88f064cf394ed",
"name": "WriteLine1",
"type": "Elsa.WriteLine",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -160,
"y": 120
},
"size": {
"width": 139.296875,
"height": 50
}
}
}
},
{
"id": "5467123c7973d011",
"nodeId": "Workflow1:8a03bf79d15fd48a:5467123c7973d011",
"name": "End1",
"type": "Elsa.End",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": 274.5,
"y": 200
},
"size": {
"width": 94.40625,
"height": 50
}
}
}
}
],
"connections": [
{
"source": {
"activity": "c7624645ff77c7d0",
"port": "Done"
},
"target": {
"activity": "bfc88f064cf394ed",
"port": "In"
}
},
{
"source": {
"activity": "bfc88f064cf394ed",
"port": "Done"
},
"target": {
"activity": "802725996be1b582",
"port": "In"
}
},
{
"source": {
"activity": "c7624645ff77c7d0",
"port": "Done"
},
"target": {
"activity": "281e9ea6d278b231",
"port": "In"
}
},
{
"source": {
"activity": "281e9ea6d278b231",
"port": "Done"
},
"target": {
"activity": "802725996be1b582",
"port": "In"
}
},
{
"source": {
"activity": "802725996be1b582",
"port": "Done"
},
"target": {
"activity": "5467123c7973d011",
"port": "In"
}
}
]
}
}
@@ -0,0 +1,241 @@
{
"id": "2effd8f5f4205a5b",
"definitionId": "2da8d4cb0fcfbacd",
"name": "Parallel \u002B Join",
"createdAt": "2023-12-13T21:42:12.634042+00:00",
"version": 2,
"toolVersion": "3.0.0.0",
"variables": [],
"inputs": [],
"outputs": [],
"outcomes": [],
"customProperties": {
"Elsa:WorkflowContextProviderTypes": []
},
"isReadonly": false,
"isLatest": true,
"isPublished": false,
"options": {
"autoUpdateConsumingWorkflows": false
},
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "d3350290ed96e720",
"nodeId": "Workflow1:d3350290ed96e720",
"metadata": {},
"customProperties": {
"source": "FlowchartJsonConverter.cs:45",
"notFoundConnections": [],
"canStartWorkflow": false,
"runAsynchronously": false
},
"activities": [
{
"items": {
"typeName": "Object[]",
"expression": {
"type": "JavaScript",
"value": "[1,2,3]"
},
"memoryReference": {
"id": "70fc1183cd5800f2:input-0"
}
},
"body": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "b07170f2d68980cd",
"nodeId": "Workflow1:d3350290ed96e720:70fc1183cd5800f2:b07170f2d68980cd",
"metadata": {},
"customProperties": {
"source": "FlowchartJsonConverter.cs:45",
"notFoundConnections": [],
"canStartWorkflow": false,
"runAsynchronously": false
},
"activities": [
{
"condition": {
"typeName": "Boolean",
"expression": {
"type": "JavaScript",
"value": "false"
},
"memoryReference": {
"id": "33665f061d96b957:input-0"
}
},
"id": "33665f061d96b957",
"nodeId": "Workflow1:d3350290ed96e720:70fc1183cd5800f2:b07170f2d68980cd:33665f061d96b957",
"name": "FlowDecision1",
"type": "Elsa.FlowDecision",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -329.140625,
"y": -78.5
},
"size": {
"width": 129.515625,
"height": 50
}
}
}
},
{
"text": null,
"id": "e27177eb5965bd6b",
"nodeId": "Workflow1:d3350290ed96e720:70fc1183cd5800f2:b07170f2d68980cd:e27177eb5965bd6b",
"name": "WriteLine1",
"type": "Elsa.WriteLine",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -40,
"y": -160
},
"size": {
"width": 139.296875,
"height": 50
}
}
}
},
{
"text": null,
"id": "343d233a75d8d8e9",
"nodeId": "Workflow1:d3350290ed96e720:70fc1183cd5800f2:b07170f2d68980cd:343d233a75d8d8e9",
"name": "WriteLine2",
"type": "Elsa.WriteLine",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": 371.859375,
"y": -78.5
},
"size": {
"width": 139.296875,
"height": 50
}
}
}
},
{
"mode": {
"typeName": "Elsa.Workflows.Core.Activities.Flowchart.Models.FlowJoinMode, Elsa.Workflows.Core",
"expression": {
"type": "Literal",
"value": "WaitAny"
},
"memoryReference": {
"id": "1f8625e278b74aa3:input-0"
}
},
"id": "1f8625e278b74aa3",
"nodeId": "Workflow1:d3350290ed96e720:70fc1183cd5800f2:b07170f2d68980cd:1f8625e278b74aa3",
"name": "FlowJoin1",
"type": "Elsa.FlowJoin",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": 188.859375,
"y": -78.5
},
"size": {
"width": 98.265625,
"height": 50
}
}
}
}
],
"connections": [
{
"source": {
"activity": "33665f061d96b957",
"port": "True"
},
"target": {
"activity": "e27177eb5965bd6b",
"port": "In"
}
},
{
"source": {
"activity": "e27177eb5965bd6b",
"port": "Done"
},
"target": {
"activity": "1f8625e278b74aa3",
"port": "In"
}
},
{
"source": {
"activity": "1f8625e278b74aa3",
"port": "Done"
},
"target": {
"activity": "343d233a75d8d8e9",
"port": "In"
}
},
{
"source": {
"activity": "33665f061d96b957",
"port": "False"
},
"target": {
"activity": "1f8625e278b74aa3",
"port": "In"
}
}
]
},
"id": "70fc1183cd5800f2",
"nodeId": "Workflow1:d3350290ed96e720:70fc1183cd5800f2",
"name": "ParallelForEach1",
"type": "Elsa.ParallelForEach",
"version": 1,
"customProperties": {
"canStartWorkflow": false,
"runAsynchronously": false
},
"metadata": {
"designer": {
"position": {
"x": -260,
"y": 140
},
"size": {
"width": 185.203125,
"height": 116.015625
}
}
}
}
],
"connections": []
}
}
@@ -0,0 +1,45 @@
using Elsa.Testing.Shared;
using Xunit.Abstractions;
namespace Elsa.Workflows.IntegrationTests.Scenarios.ImportAndExecute;
public class Tests
{
private readonly CapturingTextWriter _capturingTextWriter = new();
private readonly IServiceProvider _services;
public Tests(ITestOutputHelper testOutputHelper)
{
_services = new TestApplicationBuilder(testOutputHelper)
.WithCapturingTextWriter(_capturingTextWriter)
.Build();
}
[Theory(DisplayName = "Workflow imported from file should execute successfully.")]
[MemberData(memberName: nameof(GetSpecimen))]
public async Task Test1(string workflowFileName, string[] expectedOutput)
{
// Populate registries.
await _services.PopulateRegistriesAsync();
// Import workflow.
var workflowDefinition = await _services.ImportWorkflowDefinitionAsync($"Scenarios/ImportAndExecute/Workflows/{workflowFileName}");
// Execute.
await _services.RunWorkflowUntilEndAsync(workflowDefinition.DefinitionId);
// Assert.
var lines = _capturingTextWriter.Lines.ToList();
Assert.Equal(expectedOutput, lines);
}
public static TheoryData<string, string[]> GetSpecimen()
{
return new TheoryData<string, string[]>
{
{ "writeline.json", new[] { "Dummy Text" } },
{ "implicit-loop.json", new[] { "Do something", "Retry", "Do something", "Done" } }
};
}
}
@@ -0,0 +1,243 @@
{
"id": "0975a7e7cff7421ea03d1cc0247db02b",
"definitionId": "68e6bca3d86c4a5a8dfb825f1d5c0875",
"name": "Looping construct",
"createdAt": "2023-04-18T19:04:50.945222+00:00",
"version": 1,
"variables": [
{
"id": "b1198ecccf3f435abe462dc37012cda9",
"name": "Counter",
"typeName": "Int32",
"isArray": false,
"value": "0",
"storageDriverTypeName": "Elsa.Workflows.Services.WorkflowStorageDriver, Elsa.Workflows.Core"
}
],
"inputs": [],
"outputs": [],
"outcomes": [],
"metadata": {},
"isLatest": true,
"isPublished": true,
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "Flowchart1",
"metadata": {},
"applicationProperties": {
"NotFoundConnectionsKey": []
},
"start": "Start1",
"activities": [
{
"variable": {
"id": "b1198ecccf3f435abe462dc37012cda9",
"name": "Counter",
"typeName": "Int32",
"value": "0",
"storageDriverTypeName": "Elsa.Workflows.Services.WorkflowStorageDriver, Elsa.Workflows.Core"
},
"value": {
"typeName": "Object",
"expression": {
"type": "JavaScript",
"value": "getCounter() \u002B 1"
},
"memoryReference": {
"id": "SetVariable1:input-1"
}
},
"id": "SetVariable1",
"type": "Elsa.SetVariable",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 2620,
"y": 2280
}
},
"displayText": "Increment counter"
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "Done"
},
"memoryReference": {
"id": "WriteLine3:input-1"
}
},
"id": "WriteLine3",
"type": "Elsa.WriteLine",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 3220,
"y": 2400
}
},
"displayText": "Done"
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "Retry"
},
"memoryReference": {
"id": "WriteLine2:input-1"
}
},
"id": "WriteLine2",
"type": "Elsa.WriteLine",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 2460,
"y": 2500
}
},
"displayText": "Retry"
}
},
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "Do something"
},
"memoryReference": {
"id": "WriteLine1:input-1"
}
},
"id": "WriteLine1",
"type": "Elsa.WriteLine",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 2320,
"y": 2280
}
},
"displayText": "Do something"
}
},
{
"condition": {
"typeName": "Boolean",
"expression": {
"type": "JavaScript",
"value": "getCounter() \u003E 1"
},
"memoryReference": {
"id": "FlowDecision1:input-1"
}
},
"id": "FlowDecision1",
"type": "Elsa.FlowDecision",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 2940,
"y": 2400
}
},
"displayText": "Counter \u003E 1"
}
},
{
"id": "Start1",
"type": "Elsa.Start",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 2060,
"y": 2380
}
}
}
}
],
"connections": [
{
"source": "WriteLine1",
"target": "SetVariable1",
"sourcePort": "Done",
"targetPort": "In"
},
{
"source": "FlowDecision1",
"target": "WriteLine3",
"sourcePort": "True",
"targetPort": "In"
},
{
"source": "FlowDecision1",
"target": "WriteLine2",
"sourcePort": "False",
"targetPort": "In"
},
{
"source": "Start1",
"target": "WriteLine1",
"sourcePort": "Done",
"targetPort": "In"
},
{
"source": "WriteLine2",
"target": "WriteLine1",
"sourcePort": "Done",
"targetPort": "In"
},
{
"source": "SetVariable1",
"target": "FlowDecision1",
"sourcePort": "Done",
"targetPort": "In"
}
]
}
}
@@ -0,0 +1,55 @@
{
"id": "3a793167cf6948c899d2f40c3c05cf0b",
"definitionId": "11e2b350dae8485498c16d1ce3843829",
"name": "Dummy workflow",
"createdAt": "2023-04-04T08:35:22.841043+00:00",
"version": 1,
"variables": [],
"inputs": [],
"outputs": [],
"outcomes": [],
"metadata": {},
"isLatest": true,
"isPublished": true,
"root": {
"type": "Elsa.Flowchart",
"version": 1,
"id": "Flowchart1",
"metadata": {},
"applicationProperties": {
"NotFoundConnectionsKey": []
},
"start": "WriteLine1",
"activities": [
{
"text": {
"typeName": "String",
"expression": {
"type": "Literal",
"value": "Dummy Text"
},
"memoryReference": {
"id": "WriteLine1:input-1"
}
},
"id": "WriteLine1",
"type": "Elsa.WriteLine",
"version": 1,
"canStartWorkflow": false,
"runAsynchronously": false,
"customProperties": {},
"source": null,
"line": null,
"metadata": {
"designer": {
"position": {
"x": 2256,
"y": 2462
}
}
}
}
],
"connections": []
}
}

Some files were not shown because too many files have changed in this diff Show More