fix(workflows): persist named WithVariable values across suspend/resume (#8166)
Named WithVariable(name, value) never set a storage driver, so values were memory-only and vanished after bookmark resume. Default it to workflow instance storage, matching the parameterless overload. Fixes #8159 Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This commit is contained in:
parent
2138f0997b
commit
81d630ea1c
|
|
@ -93,6 +93,7 @@ public class WorkflowBuilder(IActivityVisitor activityVisitor, IIdentityGraphSer
|
|||
{
|
||||
var variable = new Variable<T>(name, value);
|
||||
Variables.Add(variable);
|
||||
variable.WithWorkflowStorage();
|
||||
return variable;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -116,7 +116,8 @@ public interface IWorkflowBuilder
|
|||
Variable<T> WithVariable<T>();
|
||||
|
||||
/// <summary>
|
||||
/// A fluent method for adding a variable to <see cref="Variables"/>.
|
||||
/// A fluent method for adding a named variable to <see cref="Variables"/>.
|
||||
/// The variable uses workflow instance storage by default so its value survives suspend and resume.
|
||||
/// </summary>
|
||||
Variable<T> WithVariable<T>(string name, T value);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
using Elsa.Extensions;
|
||||
using Elsa.Testing.Shared;
|
||||
using Elsa.Workflows.Options;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Elsa.Workflows.IntegrationTests.Scenarios.NamedVariablePersistence;
|
||||
|
||||
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 = "Named WithVariable value survives suspend and resume")]
|
||||
public async Task NamedWithVariable_ValueSurvivesSuspendAndResume()
|
||||
{
|
||||
// Arrange
|
||||
await _services.PopulateRegistriesAsync();
|
||||
var workflow = await _workflowBuilderFactory.CreateBuilder().BuildWorkflowAsync<NamedVariableSurvivesSuspendWorkflow>();
|
||||
|
||||
// Act
|
||||
var started = await _workflowRunner.RunAsync(workflow);
|
||||
var bookmark = started.WorkflowState.Bookmarks.Single(x => x.ActivityId == "Resume");
|
||||
var runOptions = new RunWorkflowOptions { BookmarkId = bookmark.Id };
|
||||
var resumed = await _workflowRunner.RunAsync(workflow, started.WorkflowState, runOptions);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(WorkflowStatus.Running, started.WorkflowState.Status);
|
||||
Assert.Equal(WorkflowSubStatus.Suspended, started.WorkflowState.SubStatus);
|
||||
Assert.Equal(WorkflowStatus.Finished, resumed.WorkflowState.Status);
|
||||
Assert.Equal(
|
||||
[
|
||||
"before suspend: hello",
|
||||
"after resume: hello"
|
||||
],
|
||||
_capturingTextWriter.Lines.ToList());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
using Elsa.Workflows.Activities;
|
||||
using Elsa.Workflows.Runtime.Activities;
|
||||
|
||||
namespace Elsa.Workflows.IntegrationTests.Scenarios.NamedVariablePersistence;
|
||||
|
||||
class NamedVariableSurvivesSuspendWorkflow : WorkflowBase
|
||||
{
|
||||
protected override void Build(IWorkflowBuilder builder)
|
||||
{
|
||||
var message = builder.WithVariable<string>("message", null!);
|
||||
|
||||
builder.Root = new Sequence
|
||||
{
|
||||
Activities =
|
||||
{
|
||||
new SetVariable<string>(message, "hello"),
|
||||
new WriteLine(context => $"before suspend: {message.Get(context) ?? "<null>"}"),
|
||||
new Event("Resume") { Id = "Resume" },
|
||||
new WriteLine(context => $"after resume: {message.Get(context) ?? "<null>"}")
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
using Elsa.Workflows.Builders;
|
||||
using Elsa.Workflows.Memory;
|
||||
using NSubstitute;
|
||||
|
||||
namespace Elsa.Workflows.Core.UnitTests.Builders;
|
||||
|
||||
public class WorkflowBuilderTests
|
||||
{
|
||||
[Fact]
|
||||
public void WithVariable_NameAndValue_UsesWorkflowInstanceStorage()
|
||||
{
|
||||
// Arrange
|
||||
var builder = CreateBuilder();
|
||||
|
||||
// Act
|
||||
var variable = builder.WithVariable("message", "hello");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("message", variable.Name);
|
||||
Assert.Equal("hello", variable.Value);
|
||||
Assert.Equal(typeof(WorkflowInstanceStorageDriver), variable.StorageDriverType);
|
||||
Assert.Contains(variable, builder.Variables);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WithVariable_NameAndValue_UsesSameStorageAsParameterlessOverload()
|
||||
{
|
||||
// Arrange
|
||||
var builder = CreateBuilder();
|
||||
|
||||
// Act
|
||||
#pragma warning disable CS0618 // Parameterless overload is obsolete but remains the persistence baseline.
|
||||
var unnamed = builder.WithVariable<string>();
|
||||
#pragma warning restore CS0618
|
||||
var named = builder.WithVariable("message", "hello");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(typeof(WorkflowInstanceStorageDriver), unnamed.StorageDriverType);
|
||||
Assert.Equal(unnamed.StorageDriverType, named.StorageDriverType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WithVariable_ExistingVariable_DoesNotOverrideStorageDriver()
|
||||
{
|
||||
// Arrange
|
||||
var builder = CreateBuilder();
|
||||
var variable = new Variable<string>("message", "hello");
|
||||
|
||||
// Act
|
||||
builder.WithVariable(variable);
|
||||
|
||||
// Assert
|
||||
Assert.Null(variable.StorageDriverType);
|
||||
Assert.Contains(variable, builder.Variables);
|
||||
}
|
||||
|
||||
private static WorkflowBuilder CreateBuilder() =>
|
||||
new(
|
||||
Substitute.For<IActivityVisitor>(),
|
||||
Substitute.For<IIdentityGraphService>(),
|
||||
Substitute.For<IActivityRegistry>());
|
||||
}
|
||||
Loading…
Reference in a new issue