Activity Preconditions (#4507)
* Add CanExecuteAsync precondition method * Rename GetInput to GetWorkflowInput to disambiguate between input sent to workflow and input sent to indiviudal activities.
This commit is contained in:
parent
523f301bf7
commit
bddf522e55
|
|
@ -88,7 +88,7 @@ public class MessageReceived : Trigger
|
|||
protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
|
||||
{
|
||||
// If we did not receive external input, it means we are just now encountering this activity.
|
||||
if (!context.TryGetInput<ReceivedServiceBusMessageModel>(InputKey, out var receivedMessage))
|
||||
if (!context.TryGetWorkflowInput<ReceivedServiceBusMessageModel>(InputKey, out var receivedMessage))
|
||||
{
|
||||
// Create bookmarks for when we receive the expected HTTP request.
|
||||
context.CreateBookmark(GetBookmarkPayload(context.ExpressionExecutionContext), Resume);
|
||||
|
|
@ -102,7 +102,7 @@ public class MessageReceived : Trigger
|
|||
|
||||
private async ValueTask Resume(ActivityExecutionContext context)
|
||||
{
|
||||
var receivedMessage = context.GetInput<ReceivedServiceBusMessageModel>(InputKey);
|
||||
var receivedMessage = context.GetWorkflowInput<ReceivedServiceBusMessageModel>(InputKey);
|
||||
await SetResultAsync(receivedMessage, context);
|
||||
await context.CompleteActivityAsync();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -198,7 +198,7 @@ public class HttpEndpoint : Trigger<HttpRequest>
|
|||
context.Set(Result, request);
|
||||
|
||||
// Read route data, if any.
|
||||
var path = context.GetInput<PathString>(RequestPathInputKey);
|
||||
var path = context.GetWorkflowInput<PathString>(RequestPathInputKey);
|
||||
var routeData = GetRouteData(httpContext, path);
|
||||
|
||||
var routeDictionary = routeData.Values.ToDictionary(route => route.Key, route => route.Value!);
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ public class MessageReceived : Trigger<object>
|
|||
protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
|
||||
{
|
||||
// If we did not receive external input, it means we are just now encountering this activity and we need to block execution by creating a bookmark.
|
||||
if (!context.TryGetInput<object>(InputKey, out var message))
|
||||
if (!context.TryGetWorkflowInput<object>(InputKey, out var message))
|
||||
{
|
||||
// Create bookmarks for when we receive the expected HTTP request.
|
||||
context.CreateBookmark(GetBookmarkPayload(context.ExpressionExecutionContext));
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ public class StartAt : Trigger
|
|||
protected override void Execute(ActivityExecutionContext context)
|
||||
{
|
||||
// If external input was received, it means this activity got triggered and does not need to create a bookmark.
|
||||
if (context.TryGetInput<DateTimeOffset>(InputKey, out _))
|
||||
if (context.TryGetWorkflowInput<DateTimeOffset>(InputKey, out _))
|
||||
return;
|
||||
|
||||
// No external input received, so create a bookmark.
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ public abstract class AnswerCallBase : Activity<CallAnsweredPayload>
|
|||
|
||||
private async ValueTask ResumeAsync(ActivityExecutionContext context)
|
||||
{
|
||||
var payload = context.GetInput<CallAnsweredPayload>();
|
||||
var payload = context.GetWorkflowInput<CallAnsweredPayload>();
|
||||
context.Set(Result, payload);
|
||||
await HandleConnectedAsync(context);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ public abstract class BridgeCallsBase : Activity<BridgedCallsOutput>
|
|||
|
||||
private async ValueTask ResumeAsync(ActivityExecutionContext context)
|
||||
{
|
||||
var payload = context.GetInput<CallBridgedPayload>()!;
|
||||
var payload = context.GetWorkflowInput<CallBridgedPayload>()!;
|
||||
var callControlIdA = CallControlIdA.Get(context);
|
||||
var callControlIdB = CallControlIdB.Get(context);;
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ public class CallAnswered : Activity<CallAnsweredPayload>
|
|||
|
||||
private async ValueTask Resume(ActivityExecutionContext context)
|
||||
{
|
||||
var input = context.GetInput<CallAnsweredPayload>(WebhookSerializerOptions.Create());
|
||||
var input = context.GetWorkflowInput<CallAnsweredPayload>(WebhookSerializerOptions.Create());
|
||||
context.Set(Result, input);
|
||||
await context.CompleteActivityAsync();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ public class CallHangup : Activity<CallHangupPayload>
|
|||
|
||||
private async ValueTask Resume(ActivityExecutionContext context)
|
||||
{
|
||||
var input = context.GetInput<CallHangupPayload>(WebhookSerializerOptions.Create());
|
||||
var input = context.GetWorkflowInput<CallHangupPayload>(WebhookSerializerOptions.Create());
|
||||
context.Set(Result, input);
|
||||
await context.CompleteActivityAsync();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,14 +89,14 @@ public class DialAndWait : Activity<CallPayload>
|
|||
|
||||
private async ValueTask OnCallAnswered(ActivityExecutionContext context)
|
||||
{
|
||||
var payload = context.GetInput<CallAnsweredPayload>();
|
||||
var payload = context.GetWorkflowInput<CallAnsweredPayload>();
|
||||
Result.Set(context, payload);
|
||||
await context.CompleteActivityWithOutcomesAsync("Answered");
|
||||
}
|
||||
|
||||
private async ValueTask OnCallHangup(ActivityExecutionContext context)
|
||||
{
|
||||
var payload = context.GetInput<CallHangupPayload>();
|
||||
var payload = context.GetWorkflowInput<CallHangupPayload>();
|
||||
Result.Set(context, payload);
|
||||
await context.CompleteActivityWithOutcomesAsync("Hangup");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ public class GatherUsingAudio : Activity<CallGatherEndedPayload>, IBookmarksPers
|
|||
|
||||
private async ValueTask ResumeAsync(ActivityExecutionContext context)
|
||||
{
|
||||
var payload = context.GetInput<CallGatherEndedPayload>();
|
||||
var payload = context.GetWorkflowInput<CallGatherEndedPayload>();
|
||||
var outcome = payload.Status == "valid" ? "Valid input" : "Invalid input";
|
||||
context.Set(Result, payload);
|
||||
await context.CompleteActivityWithOutcomesAsync(outcome);
|
||||
|
|
|
|||
|
|
@ -181,7 +181,7 @@ public class GatherUsingSpeak : Activity<CallGatherEndedPayload>
|
|||
|
||||
private async ValueTask ResumeAsync(ActivityExecutionContext context)
|
||||
{
|
||||
var payload = context.GetInput<CallGatherEndedPayload>();
|
||||
var payload = context.GetWorkflowInput<CallGatherEndedPayload>();
|
||||
var outcome = payload.Status == "valid" ? "Valid input" : "Invalid input";
|
||||
context.Set(Result, payload);
|
||||
await context.CompleteActivityWithOutcomesAsync(outcome);
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ public class IncomingCall : Trigger<CallInitiatedPayload>
|
|||
|
||||
private async ValueTask ResumeAsync(ActivityExecutionContext context)
|
||||
{
|
||||
var webhookModel = context.GetInput<TelnyxWebhook>(WebhookSerializerOptions.Create());
|
||||
var webhookModel = context.GetWorkflowInput<TelnyxWebhook>(WebhookSerializerOptions.Create());
|
||||
var callInitiatedPayload = (CallInitiatedPayload)webhookModel.Data.Payload;
|
||||
|
||||
// Store webhook payload as output.
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ public abstract class StartRecordingBase : Activity<CallRecordingSavedPayload>
|
|||
|
||||
private async ValueTask ResumeAsync(ActivityExecutionContext context)
|
||||
{
|
||||
var payload = context.GetInput<CallRecordingSavedPayload>();
|
||||
var payload = context.GetWorkflowInput<CallRecordingSavedPayload>();
|
||||
context.Set(Result, payload);
|
||||
await HandleCallRecordingSavedAsync(context);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ public class TransferCall : Activity<CallPayload>
|
|||
|
||||
private ValueTask InitiatedAsync(ActivityExecutionContext context)
|
||||
{
|
||||
var payload = context.GetInput<CallInitiatedPayload>();
|
||||
var payload = context.GetWorkflowInput<CallInitiatedPayload>();
|
||||
var callControlId = payload.CallControlId;
|
||||
var answeredBookmark = new WebhookEventBookmarkPayload(WebhookEventTypes.CallAnswered, callControlId);
|
||||
var hangupBookmark = new WebhookEventBookmarkPayload(WebhookEventTypes.CallHangup, callControlId);
|
||||
|
|
@ -116,14 +116,14 @@ public class TransferCall : Activity<CallPayload>
|
|||
|
||||
private async ValueTask AnsweredAsync(ActivityExecutionContext context)
|
||||
{
|
||||
var payload = context.GetInput<CallAnsweredPayload>();
|
||||
var payload = context.GetWorkflowInput<CallAnsweredPayload>();
|
||||
Result.Set(context, payload);
|
||||
await context.CompleteActivityWithOutcomesAsync("Transferred");
|
||||
}
|
||||
|
||||
private async ValueTask HangupAsync(ActivityExecutionContext context)
|
||||
{
|
||||
var payload = context.GetInput<CallHangupPayload>();
|
||||
var payload = context.GetWorkflowInput<CallHangupPayload>();
|
||||
Result.Set(context, payload);
|
||||
await context.CompleteActivityWithOutcomesAsync("Hangup");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ public class WebhookEvent : Activity<Payload>
|
|||
|
||||
private async ValueTask Resume(ActivityExecutionContext context)
|
||||
{
|
||||
var input = context.GetInput<TelnyxWebhook>(WebhookSerializerOptions.Create());
|
||||
var input = context.GetWorkflowInput<TelnyxWebhook>(WebhookSerializerOptions.Create());
|
||||
context.Set(Result, input.Data.Payload);
|
||||
await CompleteAsync(context);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,6 +88,23 @@ public abstract class Activity : IActivity, ISignalHandler
|
|||
[JsonIgnore]
|
||||
public ICollection<IBehavior> Behaviors { get; } = new List<IBehavior>();
|
||||
|
||||
/// <summary>
|
||||
/// Override this method to return a value indicating whether the activity can execute.
|
||||
/// </summary>
|
||||
protected virtual ValueTask<bool> CanExecuteAsync(ActivityExecutionContext context)
|
||||
{
|
||||
var result = CanExecute(context);
|
||||
return new(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override this method to return a value indicating whether the activity can execute.
|
||||
/// </summary>
|
||||
protected virtual bool CanExecute(ActivityExecutionContext context)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override this method to implement activity-specific logic.
|
||||
/// </summary>
|
||||
|
|
@ -187,6 +204,11 @@ public abstract class Activity : IActivity, ISignalHandler
|
|||
{
|
||||
await context.CompleteActivityAsync();
|
||||
}
|
||||
|
||||
async ValueTask<bool> IActivity.CanExecuteAsync(ActivityExecutionContext context)
|
||||
{
|
||||
return await CanExecuteAsync(context);
|
||||
}
|
||||
|
||||
async ValueTask IActivity.ExecuteAsync(ActivityExecutionContext context)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ public class ActivityExecutionContext : IExecutionContext
|
|||
Activity = activity;
|
||||
ActivityDescriptor = activityDescriptor;
|
||||
StartedAt = startedAt;
|
||||
Status = ActivityStatus.Pending;
|
||||
Tag = tag;
|
||||
CancellationToken = cancellationToken;
|
||||
Id = Guid.NewGuid().ToString();
|
||||
|
|
@ -140,7 +141,7 @@ public class ActivityExecutionContext : IExecutionContext
|
|||
/// <summary>
|
||||
/// Returns the <see cref="ActivityNode"/> metadata about the current activity.
|
||||
/// </summary>
|
||||
public ActivityNode? ActivityNode => WorkflowExecutionContext.FindNodeByActivity(Activity);
|
||||
public ActivityNode ActivityNode => WorkflowExecutionContext.FindNodeByActivity(Activity)!;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the global node ID for the current activity within the graph.
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ public class WorkflowExecutionContext : IExecutionContext
|
|||
|
||||
workflowExecutionContext.MemoryRegister = workflow.CreateRegister();
|
||||
workflowExecutionContext.ExpressionExecutionContext = new ExpressionExecutionContext(serviceProvider, workflowExecutionContext.MemoryRegister, cancellationToken: cancellationTokens.ApplicationCancellationToken);
|
||||
|
||||
|
||||
await workflowExecutionContext.SetWorkflowAsync(workflow);
|
||||
return workflowExecutionContext;
|
||||
}
|
||||
|
|
@ -339,7 +339,9 @@ public class WorkflowExecutionContext : IExecutionContext
|
|||
/// <summary>
|
||||
/// A list of <see cref="ActivityExecutionContext"/>s that are currently active.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<ActivityExecutionContext> ActiveActivityExecutionContexts => ActivityExecutionContexts.Where(x => !x.IsCompleted || x.ParentActivityExecutionContext == null).ToList();
|
||||
public IReadOnlyCollection<ActivityExecutionContext> ActiveActivityExecutionContexts => ActivityExecutionContexts
|
||||
.Where(x => !x.IsCompleted || x.ParentActivityExecutionContext == null && x.Status != ActivityStatus.Pending)
|
||||
.ToList();
|
||||
|
||||
/// <summary>
|
||||
/// A list of <see cref="ActivityExecutionContext"/>s that are currently active.
|
||||
|
|
@ -363,7 +365,7 @@ public class WorkflowExecutionContext : IExecutionContext
|
|||
/// <summary>
|
||||
/// The expression execution context for the current workflow execution.
|
||||
/// </summary>
|
||||
public ExpressionExecutionContext? ExpressionExecutionContext { get; private set; } = default!;
|
||||
public ExpressionExecutionContext? ExpressionExecutionContext { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<Variable> Variables => Workflow.Variables;
|
||||
|
|
@ -450,7 +452,7 @@ public class WorkflowExecutionContext : IExecutionContext
|
|||
/// Returns the <see cref="ActivityNode"/> containing the specified activity from the workflow graph.
|
||||
/// </summary>
|
||||
public ActivityNode? FindNodeByActivity(IActivity activity) => NodeActivityLookup[activity];
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="ActivityNode"/> associated with the specified activity ID.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -43,6 +43,11 @@ public interface IActivity
|
|||
/// </summary>
|
||||
IDictionary<string, object> Metadata { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value indicating whether the activity can execute.
|
||||
/// </summary>
|
||||
ValueTask<bool> CanExecuteAsync(ActivityExecutionContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the activity executes.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,11 @@ namespace Elsa.Workflows.Core;
|
|||
/// </summary>
|
||||
public enum ActivityStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// The activity is in the Pending state.
|
||||
/// </summary>
|
||||
Pending,
|
||||
|
||||
/// <summary>
|
||||
/// The activity is in the Running state. Note that event if an activity is running, it may not be executing.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ public static class ActivityExecutionContextExtensions
|
|||
/// <summary>
|
||||
/// Attempts to get a value from the input provided via <see cref="WorkflowExecutionContext"/>. If a value was found, an attempt is made to convert it into the specified type <code>T</code>.
|
||||
/// </summary>
|
||||
public static bool TryGetInput<T>(this ActivityExecutionContext context, string key, out T value, JsonSerializerOptions? serializerOptions = default)
|
||||
public static bool TryGetWorkflowInput<T>(this ActivityExecutionContext context, string key, out T value, JsonSerializerOptions? serializerOptions = default)
|
||||
{
|
||||
var wellKnownTypeRegistry = context.GetRequiredService<IWellKnownTypeRegistry>();
|
||||
|
||||
|
|
@ -46,12 +46,12 @@ public static class ActivityExecutionContextExtensions
|
|||
/// <summary>
|
||||
/// Gets a value from the input provided via <see cref="WorkflowExecutionContext"/>. If a value was found, an attempt is made to convert it into the specified type <code>T</code>.
|
||||
/// </summary>
|
||||
public static T GetInput<T>(this ActivityExecutionContext context, JsonSerializerOptions? serializerOptions = default) => context.GetInput<T>(typeof(T).Name, serializerOptions);
|
||||
public static T GetWorkflowInput<T>(this ActivityExecutionContext context, JsonSerializerOptions? serializerOptions = default) => context.GetWorkflowInput<T>(typeof(T).Name, serializerOptions);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value from the input provided via <see cref="WorkflowExecutionContext"/>. If a value was found, an attempt is made to convert it into the specified type <code>T</code>.
|
||||
/// </summary>
|
||||
public static T GetInput<T>(this ActivityExecutionContext context, string key, JsonSerializerOptions? serializerOptions = default)
|
||||
public static T GetWorkflowInput<T>(this ActivityExecutionContext context, string key, JsonSerializerOptions? serializerOptions = default)
|
||||
{
|
||||
var wellKnownTypeRegistry = context.GetRequiredService<IWellKnownTypeRegistry>();
|
||||
return context.WorkflowInput[key].ConvertTo<T>(new ObjectConverterOptions(serializerOptions, wellKnownTypeRegistry))!;
|
||||
|
|
|
|||
|
|
@ -72,6 +72,16 @@ public static class ExpressionExecutionContextExtensions
|
|||
/// </summary>
|
||||
public static IDictionary<string, object> GetInput(this ExpressionExecutionContext context) => (IDictionary<string, object>)context.TransientProperties[InputKey];
|
||||
|
||||
/// <summary>
|
||||
/// Returns input sent to the workflow.
|
||||
/// </summary>
|
||||
public static T GetWorkflowInput<T>(this ExpressionExecutionContext context, string key) => context.GetActivityExecutionContext().GetWorkflowInput<T>(key);
|
||||
|
||||
/// <summary>
|
||||
/// Returns input sent to the workflow.
|
||||
/// </summary>
|
||||
public static T GetWorkflowInput<T>(this ExpressionExecutionContext context) => context.GetActivityExecutionContext().GetWorkflowInput<T>();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value of the specified input.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -42,6 +42,16 @@ public class DefaultActivityInvokerMiddleware : IActivityExecutionMiddleware
|
|||
// Evaluate input properties.
|
||||
await EvaluateInputPropertiesAsync(context);
|
||||
|
||||
// Check if the activity can be executed.
|
||||
if (!await context.Activity.CanExecuteAsync(context))
|
||||
{
|
||||
context.Status = ActivityStatus.Pending;
|
||||
context.AddExecutionLogEntry("Precondition Failed", "Cannot execute at this time");
|
||||
return;
|
||||
}
|
||||
|
||||
context.Status = ActivityStatus.Running;
|
||||
|
||||
// Execute activity.
|
||||
await ExecuteActivityAsync(context);
|
||||
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ public class RunTask : Activity<object>, IBookmarksPersistedHandler
|
|||
|
||||
private async ValueTask ResumeAsync(ActivityExecutionContext context)
|
||||
{
|
||||
var input = context.GetInput<object>(InputKey);
|
||||
var input = context.GetWorkflowInput<object>(InputKey);
|
||||
context.Set(Result, input);
|
||||
await context.CompleteActivityAsync();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
using System;
|
||||
using Elsa.Expressions.Models;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Workflows.Core;
|
||||
using Elsa.Workflows.Core.Contracts;
|
||||
using Elsa.Workflows.Core.Models;
|
||||
using Elsa.Workflows.Core.Services;
|
||||
|
||||
namespace Elsa.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,49 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.IntegrationTests.Scenarios.CanExecute.Activities;
|
||||
using Elsa.IntegrationTests.Scenarios.CanExecute.Workflows;
|
||||
using Elsa.Testing.Shared;
|
||||
using Elsa.Workflows.Core.Contracts;
|
||||
using Elsa.Workflows.Core.Options;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Elsa.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,26 @@
|
|||
using Elsa.Extensions;
|
||||
using Elsa.IntegrationTests.Scenarios.CanExecute.Activities;
|
||||
using Elsa.Workflows.Core;
|
||||
using Elsa.Workflows.Core.Activities;
|
||||
using Elsa.Workflows.Core.Contracts;
|
||||
|
||||
namespace Elsa.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.GetWorkflowInput<int>("MagicNumber")),
|
||||
new WriteLine(context => $"Magic number is {magicNumberVariable.Get(context)}"),
|
||||
new CustomActivity(context => magicNumberVariable.Get(context)),
|
||||
new WriteLine("Done")
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -9,5 +9,5 @@ public class CustomActivity : CodeActivity
|
|||
protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
|
||||
{
|
||||
await context.CompleteActivityAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue