WIP #728 - Add test cases for persistence
This also includes a change to the workflow used in the test-case. There are quite complex reasons for this, as explained here: https://github.com/elsa-workflows/elsa-core/issues/728#issuecomment-794319236 The real crux of it is that the workflow must have an activity which suspends it, and that activity that suspends the workflow must not be the starting activity. This is why I added an unused set-variable activity as the starter. This commit shows that the various persistence test cases _mostly_ work OK, with the one exception of ActivityExecuted.
This commit is contained in:
parent
2c79a9395c
commit
32bcf9a751
|
|
@ -28,12 +28,16 @@ namespace Elsa.Handlers
|
|||
|
||||
public async Task Handle(WorkflowSuspended notification, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogTrace("Received notification {notification}", notification);
|
||||
|
||||
if (notification.WorkflowExecutionContext.WorkflowBlueprint.PersistenceBehavior == WorkflowPersistenceBehavior.Suspended)
|
||||
await SaveWorkflowAsync(notification.WorkflowExecutionContext, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task Handle(WorkflowExecutionBurstStarting notification, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogTrace("Received notification {notification}", notification);
|
||||
|
||||
var behavior = notification.WorkflowExecutionContext.WorkflowBlueprint.PersistenceBehavior;
|
||||
|
||||
if (behavior == WorkflowPersistenceBehavior.WorkflowBurst || behavior == WorkflowPersistenceBehavior.WorkflowPassCompleted)
|
||||
|
|
@ -42,6 +46,8 @@ namespace Elsa.Handlers
|
|||
|
||||
public async Task Handle(WorkflowExecutionBurstCompleted notification, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogTrace("Received notification {notification}", notification);
|
||||
|
||||
var behavior = notification.WorkflowExecutionContext.WorkflowBlueprint.PersistenceBehavior;
|
||||
|
||||
if (behavior == WorkflowPersistenceBehavior.WorkflowBurst)
|
||||
|
|
@ -50,6 +56,8 @@ namespace Elsa.Handlers
|
|||
|
||||
public async Task Handle(WorkflowExecutionPassCompleted notification, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogTrace("Received notification {notification}", notification);
|
||||
|
||||
var behavior = notification.WorkflowExecutionContext.WorkflowBlueprint.PersistenceBehavior;
|
||||
|
||||
if (behavior == WorkflowPersistenceBehavior.WorkflowPassCompleted || notification.ActivityExecutionContext.ActivityBlueprint.PersistWorkflow)
|
||||
|
|
@ -58,6 +66,8 @@ namespace Elsa.Handlers
|
|||
|
||||
public async Task Handle(WorkflowExecuted notification, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogTrace("Received notification {notification}", notification);
|
||||
|
||||
var behavior = notification.WorkflowExecutionContext.WorkflowBlueprint.PersistenceBehavior;
|
||||
|
||||
if (behavior == WorkflowPersistenceBehavior.WorkflowPassCompleted || behavior == WorkflowPersistenceBehavior.WorkflowBurst)
|
||||
|
|
@ -66,6 +76,8 @@ namespace Elsa.Handlers
|
|||
|
||||
public async Task Handle(WorkflowExecutionFinished notification, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogTrace("Received notification {notification}", notification);
|
||||
|
||||
var workflowExecutionContext = notification.WorkflowExecutionContext;
|
||||
|
||||
if (workflowExecutionContext.DeleteCompletedInstances)
|
||||
|
|
@ -84,9 +96,12 @@ namespace Elsa.Handlers
|
|||
|
||||
private async ValueTask SaveWorkflowAsync(WorkflowExecutionContext workflowExecutionContext, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogTrace("Persisting workflow instance {instanceId}", workflowExecutionContext.WorkflowInstance.Id);
|
||||
|
||||
workflowExecutionContext.PruneActivityData();
|
||||
var workflowInstance = workflowExecutionContext.WorkflowInstance;
|
||||
await _workflowInstanceStore.SaveAsync(workflowInstance, cancellationToken);
|
||||
|
||||
_logger.LogDebug("Committed workflow {WorkflowInstanceId} to storage", workflowInstance.Id);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -171,6 +171,8 @@ namespace Elsa.Services
|
|||
using var workflowExecutionScope = _serviceScopeFactory.CreateScope();
|
||||
var workflowExecutionContext = new WorkflowExecutionContext(workflowExecutionScope.ServiceProvider, workflowBlueprint, workflowInstance, input);
|
||||
|
||||
_logger.LogDebug("{methodName} is running workflow instance Id {instanceId}", nameof(RunWorkflowAsync), workflowInstance.Id);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(workflowInstance.ContextId))
|
||||
{
|
||||
var loadContext = new LoadWorkflowContext(workflowExecutionContext);
|
||||
|
|
@ -204,7 +206,10 @@ namespace Elsa.Services
|
|||
{
|
||||
case WorkflowStatus.Idle:
|
||||
if (!await BeginWorkflow(workflowExecutionContext, activity, input, cancellationToken))
|
||||
{
|
||||
_logger.LogDebug("Workflow {WorkflowInstanceId} cannot begin from an idle state (perhaps it needs a specific input)", workflowInstance.Id);
|
||||
return workflowInstance;
|
||||
}
|
||||
break;
|
||||
|
||||
case WorkflowStatus.Running:
|
||||
|
|
@ -213,7 +218,10 @@ namespace Elsa.Services
|
|||
|
||||
case WorkflowStatus.Suspended:
|
||||
if (!await ResumeWorkflowAsync(workflowExecutionContext, activity!, input, cancellationToken))
|
||||
{
|
||||
_logger.LogDebug("Workflow {WorkflowInstanceId} cannot be resumed from a suspended state (perhaps it needs a specific input)", workflowInstance.Id);
|
||||
return workflowInstance;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
|
|
@ -231,7 +239,10 @@ namespace Elsa.Services
|
|||
};
|
||||
|
||||
if (statusEvent != null)
|
||||
{
|
||||
_logger.LogTrace("Publishing a status event of type {eventType} for workflow {WorkflowInstanceId}", statusEvent.GetType().Name, workflowInstance.Id);
|
||||
await _mediator.Publish(statusEvent, cancellationToken);
|
||||
}
|
||||
|
||||
await _mediator.Publish(new WorkflowExecutionFinished(workflowExecutionContext), cancellationToken);
|
||||
return workflowExecutionContext.WorkflowInstance;
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ namespace Elsa.Core.IntegrationTests.Autofixture
|
|||
opts.Db = "IntegrationTests";
|
||||
});
|
||||
elsa.AddActivity<UserTask>();
|
||||
elsa.AddWorkflow<SampleWorkflow>();
|
||||
elsa.AddWorkflow<PersistableWorkflow>();
|
||||
})
|
||||
;
|
||||
}, parameter);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using Elsa.Activities.UserTask.Activities;
|
|||
using Elsa.Core.IntegrationTests.Workflows;
|
||||
using Elsa.Testing.Shared.AutoFixture.Customizations;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Elsa.Core.IntegrationTests.Autofixture
|
||||
{
|
||||
|
|
@ -16,9 +17,8 @@ namespace Elsa.Core.IntegrationTests.Autofixture
|
|||
services
|
||||
.AddElsa(elsa => {
|
||||
elsa.AddActivity<UserTask>();
|
||||
elsa.AddWorkflow<SampleWorkflow>();
|
||||
})
|
||||
;
|
||||
elsa.AddWorkflow<PersistableWorkflow>();
|
||||
});
|
||||
}, parameter);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,21 +12,59 @@ namespace Elsa.Core.IntegrationTests.Persistence.InMemory
|
|||
{
|
||||
public class InMemoryStoreIntegrationTests
|
||||
{
|
||||
[Theory(DisplayName = "A saved workflow instance should be persisted-to and readable-from an in-memory store after being run"), AutoMoqData]
|
||||
public async Task ASavedWorkflowInstanceShouldBeRoundTrippable([HostBuilderWithElsaSampleWorkflow] IHostBuilder hostBuilder)
|
||||
[Theory(DisplayName = "A persistable workflow instance with default persistence behaviour should be persisted-to and readable-from an in-memory store after being run"), AutoMoqData]
|
||||
public async Task APersistableWorkflowInstanceWithDefaultPersistanceBehaviourShouldBeRoundTrippable([HostBuilderWithElsaSampleWorkflow] IHostBuilder hostBuilder)
|
||||
{
|
||||
hostBuilder.ConfigureServices((ctx, services) => services.AddHostedService<HostedWorkflowRunner>());
|
||||
hostBuilder.ConfigureServices((ctx, services) => {
|
||||
services.AddHostedService<HostedWorkflowRunner<PersistableWorkflow>>();
|
||||
});
|
||||
var host = await hostBuilder.StartAsync();
|
||||
}
|
||||
|
||||
class HostedWorkflowRunner : IHostedService
|
||||
[Theory(DisplayName = "A persistable-on-suspend workflow instance should be persisted-to and readable-from an in-memory store after being run"), AutoMoqData]
|
||||
public async Task APersistableOnSuspendWorkflowInstanceShouldBeRoundTrippable([HostBuilderWithElsaSampleWorkflow] IHostBuilder hostBuilder)
|
||||
{
|
||||
hostBuilder.ConfigureServices((ctx, services) => {
|
||||
services.AddHostedService<HostedWorkflowRunner<PersistableWorkflow.OnSuspend>>();
|
||||
});
|
||||
var host = await hostBuilder.StartAsync();
|
||||
}
|
||||
|
||||
[Theory(DisplayName = "A persistable-on-activity-executed workflow instance should be persisted-to and readable-from an in-memory store after being run"), AutoMoqData]
|
||||
public async Task APersistableOnActivityExecutedWorkflowInstanceShouldBeRoundTrippable([HostBuilderWithElsaSampleWorkflow] IHostBuilder hostBuilder)
|
||||
{
|
||||
hostBuilder.ConfigureServices((ctx, services) => {
|
||||
services.AddHostedService<HostedWorkflowRunner<PersistableWorkflow.OnActivityExecuted>>();
|
||||
});
|
||||
var host = await hostBuilder.StartAsync();
|
||||
}
|
||||
|
||||
[Theory(DisplayName = "A persistable-on-workflow-burst workflow instance should be persisted-to and readable-from an in-memory store after being run"), AutoMoqData]
|
||||
public async Task APersistableOnWorkflowBurstWorkflowInstanceShouldBeRoundTrippable([HostBuilderWithElsaSampleWorkflow] IHostBuilder hostBuilder)
|
||||
{
|
||||
hostBuilder.ConfigureServices((ctx, services) => {
|
||||
services.AddHostedService<HostedWorkflowRunner<PersistableWorkflow.OnWorkflowBurst>>();
|
||||
});
|
||||
var host = await hostBuilder.StartAsync();
|
||||
}
|
||||
|
||||
[Theory(DisplayName = "A persistable-on-workflow-pass-completed workflow instance should be persisted-to and readable-from an in-memory store after being run"), AutoMoqData]
|
||||
public async Task APersistableOnWorkflowPassCompletedWorkflowInstanceShouldBeRoundTrippable([HostBuilderWithElsaSampleWorkflow] IHostBuilder hostBuilder)
|
||||
{
|
||||
hostBuilder.ConfigureServices((ctx, services) => {
|
||||
services.AddHostedService<HostedWorkflowRunner<PersistableWorkflow.OnWorkflowPassCompleted>>();
|
||||
});
|
||||
var host = await hostBuilder.StartAsync();
|
||||
}
|
||||
|
||||
class HostedWorkflowRunner<TWorkflow> : IHostedService where TWorkflow : PersistableWorkflow
|
||||
{
|
||||
readonly IWorkflowRunner workflowRunner;
|
||||
readonly IWorkflowInstanceStore instanceStore;
|
||||
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var instance = await workflowRunner.RunWorkflowAsync<SampleWorkflow>();
|
||||
var instance = await workflowRunner.RunWorkflowAsync<TWorkflow>();
|
||||
var retrievedInstance = await instanceStore.FindByIdAsync(instance.Id);
|
||||
|
||||
// An instance should totally be retrieved from the store
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ namespace Elsa.Core.IntegrationTests.Persistence.MongoDb
|
|||
{
|
||||
public class MongoDbIntegrationTests
|
||||
{
|
||||
[Theory(DisplayName = "A saved workflow instance should be persisted-to and readable-from a MongoDb store after being run"), AutoMoqData]
|
||||
public async Task ASavedWorkflowInstanceShouldBeRoundTrippable([HostBuilderWithElsaSampleWorkflowAndMongoDbAttribute] IHostBuilder hostBuilder)
|
||||
[Theory(DisplayName = "A persistable workflow instance with default persistence behaviour should be persisted-to and readable-from a MongoDb store after being run"), AutoMoqData]
|
||||
public async Task APersistableWorkflowInstanceWithDefaultPersistanceBehaviourShouldBeRoundTrippable([HostBuilderWithElsaSampleWorkflowAndMongoDbAttribute] IHostBuilder hostBuilder)
|
||||
{
|
||||
hostBuilder.ConfigureServices((ctx, services) => services.AddHostedService<HostedWorkflowRunner>());
|
||||
var host = await hostBuilder.StartAsync();
|
||||
|
|
@ -27,7 +27,7 @@ namespace Elsa.Core.IntegrationTests.Persistence.MongoDb
|
|||
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var instance = await workflowRunner.RunWorkflowAsync<SampleWorkflow>();
|
||||
var instance = await workflowRunner.RunWorkflowAsync<PersistableWorkflow>();
|
||||
var retrievedInstance = await instanceStore.FindByIdAsync(instance.Id);
|
||||
|
||||
Assert.NotNull(retrievedInstance);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
using Elsa.Activities.UserTask.Activities;
|
||||
using Elsa.Builders;
|
||||
using Elsa.Activities.ControlFlow;
|
||||
using Elsa.Activities.Primitives;
|
||||
using Elsa.Models;
|
||||
|
||||
namespace Elsa.Core.IntegrationTests.Workflows
|
||||
{
|
||||
public class PersistableWorkflow : IWorkflow
|
||||
{
|
||||
public static readonly object Result = new object();
|
||||
|
||||
public virtual void Build(IWorkflowBuilder builder)
|
||||
{
|
||||
builder
|
||||
.StartWith<SetVariable>(a => a.Set(x => x.VariableName, "Unused").Set(x => x.Value, "Unused"))
|
||||
.Then<UserTask>(setup: s => {
|
||||
s.Set(x => x.Actions, c => new [] { "Foo", "Bar" });
|
||||
})
|
||||
.Finish(x => x.WithOutput(Result));
|
||||
}
|
||||
|
||||
public class OnSuspend : PersistableWorkflow
|
||||
{
|
||||
public override void Build(IWorkflowBuilder builder)
|
||||
{
|
||||
builder.WithPersistenceBehavior(WorkflowPersistenceBehavior.Suspended);
|
||||
base.Build(builder);
|
||||
}
|
||||
}
|
||||
|
||||
public class OnActivityExecuted : PersistableWorkflow
|
||||
{
|
||||
public override void Build(IWorkflowBuilder builder)
|
||||
{
|
||||
builder.WithPersistenceBehavior(WorkflowPersistenceBehavior.ActivityExecuted);
|
||||
base.Build(builder);
|
||||
}
|
||||
}
|
||||
|
||||
public class OnWorkflowBurst : PersistableWorkflow
|
||||
{
|
||||
public override void Build(IWorkflowBuilder builder)
|
||||
{
|
||||
builder.WithPersistenceBehavior(WorkflowPersistenceBehavior.WorkflowBurst);
|
||||
base.Build(builder);
|
||||
}
|
||||
}
|
||||
|
||||
public class OnWorkflowPassCompleted : PersistableWorkflow
|
||||
{
|
||||
public override void Build(IWorkflowBuilder builder)
|
||||
{
|
||||
builder.WithPersistenceBehavior(WorkflowPersistenceBehavior.WorkflowPassCompleted);
|
||||
base.Build(builder);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
using Elsa.Activities.UserTask.Activities;
|
||||
using Elsa.Builders;
|
||||
using Elsa.Activities.ControlFlow;
|
||||
|
||||
namespace Elsa.Core.IntegrationTests.Workflows
|
||||
{
|
||||
public class SampleWorkflow : IWorkflow
|
||||
{
|
||||
public static readonly object Result = new object();
|
||||
|
||||
public void Build(IWorkflowBuilder builder)
|
||||
{
|
||||
builder
|
||||
.StartWith<UserTask>(setup: s => {
|
||||
s.Set(x => x.Actions, c => new [] { "Foo", "Bar" });
|
||||
})
|
||||
.Finish(x => x.WithOutput(Result));
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue