Add integration test for reading composite activity output

This commit is contained in:
Sipke Schoorstra 2023-03-24 22:25:48 +01:00
parent 0e992a9cbd
commit cebf6f12e3
7 changed files with 153 additions and 14 deletions

View file

@ -1,4 +1,5 @@
using Elsa.Extensions;
using Elsa.Features.Services;
using Elsa.Workflows.Core.Contracts;
using Elsa.Workflows.Core.Services;
using Microsoft.Extensions.DependencyInjection;
@ -11,26 +12,36 @@ public class TestApplicationBuilder
{
private readonly ITestOutputHelper _testOutputHelper;
private readonly ServiceCollection _services;
private Action<IModule> _configureElsa;
public TestApplicationBuilder(ITestOutputHelper testOutputHelper)
{
_testOutputHelper = testOutputHelper;
_services = new ServiceCollection();
_services.AddElsa(elsa => elsa
.UseWorkflows(workflows => workflows
.WithStandardOutStreamProvider(_ => new StandardOutStreamProvider(new XunitConsoleTextWriter(testOutputHelper)))
)
);
_services
.AddSingleton(testOutputHelper)
.AddLogging(logging => logging.AddProvider(new XunitLoggerProvider(testOutputHelper)).SetMinimumLevel(LogLevel.Debug));
_configureElsa += elsa => elsa
.UseWorkflows(workflows => workflows
.WithStandardOutStreamProvider(_ => new StandardOutStreamProvider(new XunitConsoleTextWriter(_testOutputHelper)))
);
}
public IServiceProvider Build() => _services.BuildServiceProvider();
public IServiceProvider Build()
{
_services.AddElsa(_configureElsa);
return _services.BuildServiceProvider();
}
public TestApplicationBuilder Configure(Action<IServiceCollection> configure)
public TestApplicationBuilder ConfigureElsa(Action<IModule> configure)
{
_configureElsa += configure;
return this;
}
public TestApplicationBuilder ConfigureServices(Action<IServiceCollection> configure)
{
configure(_services);
return this;
@ -39,7 +50,9 @@ public class TestApplicationBuilder
public TestApplicationBuilder WithCapturingTextWriter(CapturingTextWriter capturingTextWriter)
{
var combinedTextWriter = new CombinedTextWriter(capturingTextWriter, new XunitConsoleTextWriter(_testOutputHelper));
_services.AddSingleton<IStandardOutStreamProvider>(new StandardOutStreamProvider(combinedTextWriter));
var provider = new StandardOutStreamProvider(combinedTextWriter);
ConfigureElsa(elsa => elsa.UseWorkflows(workflows => workflows.WithStandardOutStreamProvider(_ => provider)));
return this;
}
}

View file

@ -65,8 +65,8 @@ public class JintJavaScriptEvaluator : IJavaScriptEvaluator
engine.SetValue("getVariable", (Func<string, object?>)(id => context.GetVariable(id)));
engine.SetValue("getInput", (Func<string, object?>)(name => context.GetWorkflowExecutionContext().Input.GetValue(name)));
// Create variable & input setters and getters for each variable.
CreateMemoryBlockAccessors(engine, context);
// Create variable getters and setters for each variable.
CreateVariableAccessors(engine, context);
engine.SetValue("isNullOrWhiteSpace", (Func<string, bool>)(value => string.IsNullOrWhiteSpace(value)));
engine.SetValue("isNullOrEmpty", (Func<string, bool>)(value => string.IsNullOrEmpty(value)));
@ -88,7 +88,7 @@ public class JintJavaScriptEvaluator : IJavaScriptEvaluator
return engine;
}
private static void CreateMemoryBlockAccessors(Engine engine, ExpressionExecutionContext context)
private static void CreateVariableAccessors(Engine engine, ExpressionExecutionContext context)
{
var variableNames = GetVariableNamesInScope(context).ToList();
@ -100,7 +100,11 @@ public class JintJavaScriptEvaluator : IJavaScriptEvaluator
}
}
private static IEnumerable<string> GetVariableNamesInScope(ExpressionExecutionContext context) => EnumerateVariablesInScope(context).Select(x => x.Name).Distinct();
private static IEnumerable<string> GetVariableNamesInScope(ExpressionExecutionContext context) =>
EnumerateVariablesInScope(context)
.Select(x => x.Name)
.Where(x => !string.IsNullOrWhiteSpace(x))
.Distinct();
private static object GetVariableInScope(ExpressionExecutionContext context, string variableName)
{

View file

@ -48,10 +48,11 @@ public abstract class Composite : Activity, IVariableContainer
/// <inheritdoc />
protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
{
ConfigureActivities(context);
// Register variables.
context.ExpressionExecutionContext.Memory.Declare(Variables);
ConfigureActivities(context);
await context.ScheduleActivityAsync(Root, OnRootCompletedAsync);
}

View file

@ -45,6 +45,12 @@ public class Variable<T> : Variable
{
Value = value;
}
public Variable(string name, T value)
{
Name = name;
Value = value;
}
public T? Get(ActivityExecutionContext context) => Get(context.ExpressionExecutionContext).ConvertTo<T?>();
public new T? Get(ExpressionExecutionContext context) => base.Get(context).ConvertTo<T?>();

View file

@ -22,6 +22,7 @@
<ItemGroup>
<ProjectReference Include="..\..\..\src\common\Elsa.Testing.Shared\Elsa.Testing.Shared.csproj" />
<ProjectReference Include="..\..\..\src\modules\Elsa.JavaScript\Elsa.JavaScript.csproj" />
<ProjectReference Include="..\..\..\src\modules\Elsa.Workflows.Runtime\Elsa.Workflows.Runtime.csproj" />
</ItemGroup>

View file

@ -0,0 +1,40 @@
using System.Linq;
using System.Threading.Tasks;
using Elsa.Extensions;
using Elsa.Testing.Shared;
using Elsa.Workflows.Core.Contracts;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
using Xunit.Abstractions;
namespace Elsa.IntegrationTests.Scenarios.Composites;
public class Tests
{
private readonly IWorkflowRunner _workflowRunner;
private readonly CapturingTextWriter _capturingTextWriter = new();
private readonly IWorkflowBuilderFactory _workflowBuilderFactory;
public Tests(ITestOutputHelper testOutputHelper)
{
var 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()
{
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);
}
}

View file

@ -0,0 +1,74 @@
using Elsa.Extensions;
using Elsa.JavaScript.Activities;
using Elsa.Workflows.Core.Abstractions;
using Elsa.Workflows.Core.Activities;
using Elsa.Workflows.Core.Contracts;
using Elsa.Workflows.Core.Models;
namespace Elsa.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; } = default!;
public Input<int> B { get; set; } = default!;
public Sum()
{
_runJavaScript = new RunJavaScript
{
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 Output<object>(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 = new Variable<int>();
workflow.Root = new Sequence
{
Variables = { sum },
Activities =
{
new Sum
{
A = new(1),
B = new(2),
Result = new(sum)
},
new WriteLine(context => $"Sum: {sum.Get(context)}")
}
};
}
}