Merge pull request #775 from craigfowler/feature/738-Composite-Activities-may-be-triggers
Refactor for comprehension & add test coverage
This commit is contained in:
commit
ee7a8cdb1b
|
|
@ -9,21 +9,6 @@ namespace Elsa
|
|||
{
|
||||
public static class CompositeActivityBlueprintExtensions
|
||||
{
|
||||
public static IEnumerable<IActivityBlueprint> GetStartActivities(this ICompositeActivityBlueprint workflowBlueprint)
|
||||
{
|
||||
var targetActivityIds = workflowBlueprint.Connections.Select(x => x.Target.Activity?.Id).Distinct().ToLookup(x => x);
|
||||
|
||||
var query =
|
||||
from activity in workflowBlueprint.Activities
|
||||
where !targetActivityIds.Contains(activity.Id)
|
||||
select activity;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
public static IEnumerable<IActivityBlueprint> GetStartActivities(this ICompositeActivityBlueprint workflowBlueprint, string activityType) => workflowBlueprint.GetStartActivities().Where(x => x.Type == activityType);
|
||||
public static IEnumerable<IActivityBlueprint> GetStartActivities(this ICompositeActivityBlueprint workflowBlueprint, Type activityType) => workflowBlueprint.GetStartActivities(activityType.Name);
|
||||
public static IEnumerable<IActivityBlueprint> GetStartActivities<T>(this ICompositeActivityBlueprint workflowBlueprint) where T : IActivity => workflowBlueprint.GetStartActivities(typeof(T));
|
||||
public static IEnumerable<IActivityBlueprint> GetEndActivities(this ICompositeActivityBlueprint workflowBlueprint) => workflowBlueprint.Activities.Where(x => !workflowBlueprint.GetOutboundConnections(x.Id).Any());
|
||||
public static IActivityBlueprint? GetActivity(this ICompositeActivityBlueprint workflowBlueprint, string id) => workflowBlueprint.Activities.FirstOrDefault(x => x.Id == id);
|
||||
public static IEnumerable<IActivityBlueprint> GetActivities(this ICompositeActivityBlueprint workflowBlueprint, IEnumerable<string> ids) => workflowBlueprint.Activities.Where(x => ids.Contains(x.Id));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Elsa.Services;
|
||||
using Elsa.Services.Models;
|
||||
|
||||
namespace Elsa.Extensions
|
||||
{
|
||||
public static class StartActivitiesForCompositeActivityBlueprintProviderExtensions
|
||||
{
|
||||
public static IEnumerable<IActivityBlueprint> GetStartActivities(this IGetsStartActivitiesForCompositeActivityBlueprint startActivitiesProvider,
|
||||
ICompositeActivityBlueprint workflowBlueprint,
|
||||
string activityType)
|
||||
=> startActivitiesProvider.GetStartActivities(workflowBlueprint).Where(x => x.Type == activityType);
|
||||
|
||||
public static IEnumerable<IActivityBlueprint> GetStartActivities(this IGetsStartActivitiesForCompositeActivityBlueprint startActivitiesProvider,
|
||||
ICompositeActivityBlueprint workflowBlueprint,
|
||||
Type activityType)
|
||||
=> startActivitiesProvider.GetStartActivities(workflowBlueprint, activityType.Name);
|
||||
|
||||
public static IEnumerable<IActivityBlueprint> GetStartActivities<T>(this IGetsStartActivitiesForCompositeActivityBlueprint startActivitiesProvider,
|
||||
ICompositeActivityBlueprint workflowBlueprint) where T : IActivity
|
||||
=> startActivitiesProvider.GetStartActivities(workflowBlueprint, typeof(T));
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,8 @@ namespace Elsa
|
|||
GetActivityById(this WorkflowDefinition workflowDefinition, string activityId) =>
|
||||
workflowDefinition.Activities.First(x => x.ActivityId == activityId);
|
||||
|
||||
// TODO: Consider adding this as an overload for IGetsStartActivitiesForCompositeActivityBlueprint
|
||||
// The intent is the same and the logic is near-identical.
|
||||
public static IEnumerable<ActivityDefinition> GetStartActivities(this WorkflowDefinition workflowDefinition)
|
||||
{
|
||||
var targetActivities = workflowDefinition.Connections
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Services.Models;
|
||||
|
||||
namespace Elsa.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// An object which can create an activity execution context for a specified activity blueprint.
|
||||
/// </summary>
|
||||
public interface ICreatesActivityExecutionContextForActivityBlueprint
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a activity execution context for the specified activity blueprint.
|
||||
/// </summary>
|
||||
/// <param name="activityBlueprint">An activity blueprint</param>
|
||||
/// <param name="workflowExecutionContext">A workflow execution context</param>
|
||||
/// <param name="cancellationToken">A cancellation token</param>
|
||||
/// <returns>An activity execution context</returns>
|
||||
ActivityExecutionContext CreateActivityExecutionContext(IActivityBlueprint activityBlueprint,
|
||||
WorkflowExecutionContext workflowExecutionContext,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Services.Models;
|
||||
|
||||
namespace Elsa.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// An object which can create a workflow execution context for a specified workflow blueprint.
|
||||
/// </summary>
|
||||
public interface ICreatesWorkflowExecutionContextForWorkflowBlueprint
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a workflow execution context for the specified workflow blueprint.
|
||||
/// </summary>
|
||||
/// <param name="workflowBlueprint">A workflow blueprint</param>
|
||||
/// <param name="cancellationToken">An optional cancellation token</param>
|
||||
/// <returns>A task for a workflow execution context</returns>
|
||||
Task<WorkflowExecutionContext> CreateWorkflowExecutionContextAsync(IWorkflowBlueprint workflowBlueprint,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
using System.Collections.Generic;
|
||||
using Elsa.Services.Models;
|
||||
|
||||
namespace Elsa.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// An object which gets the starting activities for a specified <see cref="ICompositeActivityBlueprint"/>.
|
||||
/// </summary>
|
||||
public interface IGetsStartActivitiesForCompositeActivityBlueprint
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a collection of the starting activities for the specified composite activity blueprint.
|
||||
/// </summary>
|
||||
/// <param name="compositeActivityBlueprint">A composite activity blueprint</param>
|
||||
/// <returns>A collection of the blueprint's starting activities</returns>
|
||||
IEnumerable<IActivityBlueprint> GetStartActivities(ICompositeActivityBlueprint compositeActivityBlueprint);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.ActivityProviders;
|
||||
using Elsa.Services.Models;
|
||||
|
||||
namespace Elsa.Triggers
|
||||
{
|
||||
/// <summary>
|
||||
/// An object which can get a collection of the <see cref="WorkflowTrigger"/> for a specified activity blueprint and workflow.
|
||||
/// </summary>
|
||||
public interface IGetsTriggersForActivityBlueprintAndWorkflow
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a collection of the workflow triggers for the specified activity blueprint.
|
||||
/// </summary>
|
||||
/// <param name="activityBlueprint">An activity blueprint</param>
|
||||
/// <param name="workflowExecutionContext">A workflow execution context</param>
|
||||
/// <param name="activityTypes">A dictionary of all of the activity types (by name)</param>
|
||||
/// <param name="cancellationToken">An optional cancellation token</param>
|
||||
/// <returns>A task exposing a collection of workflow triggers for the activity and workflow.</returns>
|
||||
Task<IEnumerable<WorkflowTrigger>> GetTriggersForActivityBlueprintAsync(IActivityBlueprint activityBlueprint,
|
||||
WorkflowExecutionContext workflowExecutionContext,
|
||||
IDictionary<string, ActivityType> activityTypes,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Services.Models;
|
||||
|
||||
namespace Elsa.Triggers
|
||||
{
|
||||
/// <summary>
|
||||
/// An object which can get all of the workflow triggers for a collection of workflow blueprints.
|
||||
/// </summary>
|
||||
public interface IGetsTriggersForWorkflowBlueprints
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the triggers for all of the specified workflow blueprints.
|
||||
/// </summary>
|
||||
/// <param name="workflowBlueprints">The workflow blueprints for which to get triggers.</param>
|
||||
/// <param name="cancellationToken">An optional cancellation token.</param>
|
||||
/// <returns>A task which exposes an enumerable collection of workflow triggers.</returns>
|
||||
Task<IEnumerable<WorkflowTrigger>> GetTriggersAsync(IEnumerable<IWorkflowBlueprint> workflowBlueprints,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
||||
|
|
@ -14,9 +14,12 @@ namespace Elsa.Builders
|
|||
public class CompositeActivityBuilder : ActivityBuilder, ICompositeActivityBuilder
|
||||
{
|
||||
private readonly Func<ICompositeActivityBuilder> _compositeActivityBuilderFactory;
|
||||
private readonly IGetsStartActivitiesForCompositeActivityBlueprint startingActivitiesProvider;
|
||||
|
||||
public CompositeActivityBuilder(IServiceProvider serviceProvider)
|
||||
public CompositeActivityBuilder(IServiceProvider serviceProvider, IGetsStartActivitiesForCompositeActivityBlueprint startingActivitiesProvider)
|
||||
{
|
||||
this.startingActivitiesProvider = startingActivitiesProvider ?? throw new ArgumentNullException(nameof(startingActivitiesProvider));
|
||||
|
||||
ServiceProvider = serviceProvider;
|
||||
ActivityBuilders = new List<IActivityBuilder>();
|
||||
ConnectionBuilders = new List<IConnectionBuilder>();
|
||||
|
|
@ -58,7 +61,7 @@ namespace Elsa.Builders
|
|||
|
||||
var valueProviders = propertyValuesBuilder.ValueProviders.ToDictionary(
|
||||
x => x.Key,
|
||||
x => (IActivityPropertyValueProvider) new DelegateActivityPropertyValueProvider(x.Value));
|
||||
x => (IActivityPropertyValueProvider)new DelegateActivityPropertyValueProvider(x.Value));
|
||||
|
||||
return New<T>(valueProviders, lineNumber, sourceFile);
|
||||
}
|
||||
|
|
@ -181,7 +184,7 @@ namespace Elsa.Builders
|
|||
using var scope = ServiceProvider.CreateScope();
|
||||
foreach (var activityBuilder in compositeActivityBuilders)
|
||||
{
|
||||
var compositeActivity = (CompositeActivity) ActivatorUtilities.CreateInstance(scope.ServiceProvider, activityBuilder.ActivityType);
|
||||
var compositeActivity = (CompositeActivity)ActivatorUtilities.CreateInstance(scope.ServiceProvider, activityBuilder.ActivityType);
|
||||
var compositeActivityBuilder = _compositeActivityBuilderFactory();
|
||||
compositeActivityBuilder.ActivityId = activityBuilder.ActivityId;
|
||||
compositeActivity.Build(compositeActivityBuilder);
|
||||
|
|
@ -198,7 +201,7 @@ namespace Elsa.Builders
|
|||
compositeActivityBlueprint.ActivityPropertyProviders = compositeActivityBlueprint.ActivityPropertyProviders;
|
||||
|
||||
// Connect the composite activity to its starting activities.
|
||||
var startActivities = compositeActivityBlueprint.GetStartActivities().ToList();
|
||||
var startActivities = startingActivitiesProvider.GetStartActivities(compositeActivityBlueprint).ToList();
|
||||
connections.AddRange(startActivities.Select(x => new Connection(compositeActivityBlueprint, x, CompositeActivity.Enter)));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ namespace Elsa.Builders
|
|||
{
|
||||
public class WorkflowBuilder : CompositeActivityBuilder, IWorkflowBuilder
|
||||
{
|
||||
public WorkflowBuilder(IIdGenerator idGenerator, IServiceProvider serviceProvider) : base(serviceProvider)
|
||||
public WorkflowBuilder(IIdGenerator idGenerator, IServiceProvider serviceProvider, IGetsStartActivitiesForCompositeActivityBlueprint startingActivitiesProvider) : base(serviceProvider, startingActivitiesProvider)
|
||||
{
|
||||
Version = 1;
|
||||
Variables = new Variables();
|
||||
|
|
|
|||
|
|
@ -109,6 +109,9 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
.AddTransient<IActivityTypeService, ActivityTypeService>()
|
||||
.AddActivityTypeProvider<TypeBasedActivityProvider>()
|
||||
.AddScoped<IWorkflowExecutionLog, WorkflowExecutionLog>()
|
||||
.AddTransient<ICreatesWorkflowExecutionContextForWorkflowBlueprint, WorkflowExecutionContextForWorkflowBlueprintFactory>()
|
||||
.AddTransient<ICreatesActivityExecutionContextForActivityBlueprint, ActivityExecutionContextForActivityBlueprintFactory>()
|
||||
.AddTransient<IGetsStartActivitiesForCompositeActivityBlueprint, StartActivitiesForCompositeActivityBlueprintProvider>()
|
||||
;
|
||||
|
||||
// Serialization.
|
||||
|
|
@ -143,6 +146,8 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
.AddScoped<IBookmarkIndexer, BookmarkIndexer>()
|
||||
.AddScoped<IBookmarkFinder, BookmarkFinder>()
|
||||
.AddScoped<ITriggerIndexer, TriggerIndexer>()
|
||||
.AddScoped<IGetsTriggersForWorkflowBlueprints, TriggersForBlueprintsProvider>()
|
||||
.AddTransient<IGetsTriggersForActivityBlueprintAndWorkflow, TriggersForActivityBlueprintAndWorkflowProvider>()
|
||||
.AddSingleton<ITriggerStore, TriggerStore>()
|
||||
.AddScoped<ITriggerFinder, TriggerFinder>()
|
||||
.AddBookmarkProvider<SignalReceivedBookmarkProvider>()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using Elsa.Services.Models;
|
||||
|
||||
namespace Elsa.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of <see cref="ICreatesActivityExecutionContextForActivityBlueprint"/>.
|
||||
/// </summary>
|
||||
public class ActivityExecutionContextForActivityBlueprintFactory : ICreatesActivityExecutionContextForActivityBlueprint
|
||||
{
|
||||
readonly IServiceProvider serviceProvider;
|
||||
|
||||
public ActivityExecutionContextForActivityBlueprintFactory(IServiceProvider serviceProvider)
|
||||
{
|
||||
this.serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a activity execution context for the specified activity blueprint.
|
||||
/// </summary>
|
||||
/// <param name="activityBlueprint">An activity blueprint</param>
|
||||
/// <param name="workflowExecutionContext">A workflow execution context</param>
|
||||
/// <param name="cancellationToken">A cancellation token</param>
|
||||
/// <returns>An activity execution context</returns>
|
||||
public ActivityExecutionContext CreateActivityExecutionContext(IActivityBlueprint activityBlueprint,
|
||||
WorkflowExecutionContext workflowExecutionContext,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return new ActivityExecutionContext(serviceProvider, workflowExecutionContext, activityBlueprint, null, false, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Elsa.Services.Models;
|
||||
|
||||
namespace Elsa.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of <see cref="IGetsStartActivitiesForCompositeActivityBlueprint"/>.
|
||||
/// </summary>
|
||||
public class StartActivitiesForCompositeActivityBlueprintProvider : IGetsStartActivitiesForCompositeActivityBlueprint
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a collection of the starting activities for the specified composite activity blueprint.
|
||||
/// </summary>
|
||||
/// <param name="compositeActivityBlueprint">A composite activity blueprint</param>
|
||||
/// <returns>A collection of the blueprint's starting activities</returns>
|
||||
public IEnumerable<IActivityBlueprint> GetStartActivities(ICompositeActivityBlueprint compositeActivityBlueprint)
|
||||
{
|
||||
var activityIdsThatAreNotStartingActivities = GetAllActivityIdsWhichHaveInboundConnections(compositeActivityBlueprint);
|
||||
|
||||
var query = from activity in compositeActivityBlueprint.Activities
|
||||
where !activityIdsThatAreNotStartingActivities.Contains(activity.Id)
|
||||
select activity;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method gets activities that have inbound connections.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// "Start activities" are those with no inbound connections; IE no workflow connection-target will point to a start activity.
|
||||
/// What this method returns is essentially a blacklist of activity IDs which are not starting activities.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="compositeActivityBlueprint">A composite activity blueprint</param>
|
||||
/// <returns>A lookup of activity IDs which are not starting activities</returns>
|
||||
ILookup<string?,string?> GetAllActivityIdsWhichHaveInboundConnections(ICompositeActivityBlueprint compositeActivityBlueprint)
|
||||
{
|
||||
return compositeActivityBlueprint.Connections
|
||||
.Select(x => x.Target.Activity?.Id)
|
||||
.Distinct()
|
||||
.ToLookup(x => x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,21 +14,25 @@ namespace Elsa.Services
|
|||
{
|
||||
private readonly IActivityTypeService _activityTypeService;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IGetsStartActivitiesForCompositeActivityBlueprint startingActivitiesProvider;
|
||||
|
||||
public WorkflowBlueprintMaterializer(IActivityTypeService activityTypeService, ILogger<WorkflowBlueprintMaterializer> logger)
|
||||
public WorkflowBlueprintMaterializer(IActivityTypeService activityTypeService,
|
||||
ILogger<WorkflowBlueprintMaterializer> logger,
|
||||
IGetsStartActivitiesForCompositeActivityBlueprint startingActivitiesProvider)
|
||||
{
|
||||
this.startingActivitiesProvider = startingActivitiesProvider ?? throw new System.ArgumentNullException(nameof(startingActivitiesProvider));
|
||||
_activityTypeService = activityTypeService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
||||
public async Task<IWorkflowBlueprint> CreateWorkflowBlueprintAsync(WorkflowDefinition workflowDefinition, CancellationToken cancellationToken)
|
||||
{
|
||||
var manyActivityBlueprints = await Task.WhenAll(workflowDefinition.Activities.Select(async x => await CreateBlueprintsAsync(x, cancellationToken)));
|
||||
var activityBlueprints = manyActivityBlueprints.SelectMany(x => x).Distinct().ToDictionary(x => x.Id);
|
||||
var compositeActivityBlueprints = activityBlueprints.Values.Where(x => x is ICompositeActivityBlueprint).Cast<ICompositeActivityBlueprint>().ToList();
|
||||
var compositeActivityBlueprints = activityBlueprints.Values.Where(x => x is ICompositeActivityBlueprint).Cast<ICompositeActivityBlueprint>().ToList();
|
||||
var connections = compositeActivityBlueprints.SelectMany(x => x.Connections).Distinct().ToList();
|
||||
var propertyProviders = compositeActivityBlueprints.SelectMany(x => x.ActivityPropertyProviders).ToList();
|
||||
|
||||
|
||||
connections.AddRange(workflowDefinition.Connections.Select(x => ResolveConnection(x, activityBlueprints)).Where(x => x != null).Select(x => x!));
|
||||
propertyProviders.AddRange(await CreatePropertyProviders(workflowDefinition, cancellationToken));
|
||||
|
||||
|
|
@ -63,7 +67,7 @@ namespace Elsa.Services
|
|||
var activityType = await _activityTypeService.GetActivityTypeAsync(activityDefinition.Type, cancellationToken);
|
||||
var type = activityType.Type;
|
||||
var props = type.GetProperties();
|
||||
|
||||
|
||||
foreach (var property in activityDefinition.Properties)
|
||||
{
|
||||
var prop = props.FirstOrDefault(x => x.Name == property.Name);
|
||||
|
|
@ -73,7 +77,7 @@ namespace Elsa.Services
|
|||
_logger.LogWarning("Could not find the specified property '{PropertyName}' for activity type {ActivityTypeName}. Was the activity property renamed/removed/refactored after the workflow definition was created?", property.Name, activityType.Type.Name);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
var provider = new ExpressionActivityPropertyValueProvider(property.Expression, property.Syntax, prop.PropertyType);
|
||||
propertyProviders.AddProvider(activityDefinition.ActivityId, property.Name, provider);
|
||||
}
|
||||
|
|
@ -101,12 +105,12 @@ namespace Elsa.Services
|
|||
private async Task<IEnumerable<IActivityBlueprint>> CreateBlueprintsAsync(ActivityDefinition activityDefinition, CancellationToken cancellationToken)
|
||||
{
|
||||
var list = new List<IActivityBlueprint>();
|
||||
|
||||
|
||||
if (activityDefinition is CompositeActivityDefinition compositeActivityDefinition)
|
||||
{
|
||||
var manyActivityBlueprints = await Task.WhenAll(compositeActivityDefinition.Activities.Select(async x => await CreateBlueprintsAsync(x, cancellationToken)));
|
||||
var activityBlueprints = manyActivityBlueprints.SelectMany(x => x).ToDictionary(x => x.Id);
|
||||
|
||||
|
||||
list.AddRange(activityBlueprints.Values);
|
||||
|
||||
var compositeActivityBlueprint = new CompositeActivityBlueprint
|
||||
|
|
@ -121,12 +125,12 @@ namespace Elsa.Services
|
|||
LoadWorkflowContext = activityDefinition.LoadWorkflowContext,
|
||||
SaveWorkflowContext = activityDefinition.SaveWorkflowContext,
|
||||
ActivityPropertyProviders = await CreatePropertyProviders(compositeActivityDefinition, cancellationToken)
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
list.Add(compositeActivityBlueprint);
|
||||
|
||||
|
||||
// Connect the composite activity to its starting activities.
|
||||
var startActivities = compositeActivityBlueprint.GetStartActivities().ToList();
|
||||
var startActivities = startingActivitiesProvider.GetStartActivities(compositeActivityBlueprint).ToList();
|
||||
compositeActivityBlueprint.Connections.AddRange(startActivities.Select(x => new Connection(compositeActivityBlueprint, x, CompositeActivity.Enter)));
|
||||
}
|
||||
else
|
||||
|
|
@ -140,7 +144,7 @@ namespace Elsa.Services
|
|||
PersistWorkflow = activityDefinition.PersistWorkflow,
|
||||
LoadWorkflowContext = activityDefinition.LoadWorkflowContext,
|
||||
SaveWorkflowContext = activityDefinition.SaveWorkflowContext,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return list;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Services.Models;
|
||||
|
||||
namespace Elsa.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of <see cref="ICreatesWorkflowExecutionContextForWorkflowBlueprint"/>.
|
||||
/// </summary>
|
||||
public class WorkflowExecutionContextForWorkflowBlueprintFactory : ICreatesWorkflowExecutionContextForWorkflowBlueprint
|
||||
{
|
||||
readonly IServiceProvider serviceProvider;
|
||||
readonly IWorkflowFactory workflowFactory;
|
||||
|
||||
public WorkflowExecutionContextForWorkflowBlueprintFactory(IServiceProvider serviceProvider, IWorkflowFactory workflowFactory)
|
||||
{
|
||||
this.serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
|
||||
this.workflowFactory = workflowFactory ?? throw new ArgumentNullException(nameof(workflowFactory));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a workflow execution context for the specified workflow blueprint.
|
||||
/// </summary>
|
||||
/// <param name="workflowBlueprint">A workflow blueprint</param>
|
||||
/// <param name="cancellationToken">An optional cancellation token</param>
|
||||
/// <returns>A task for a workflow execution context</returns>
|
||||
public async Task<WorkflowExecutionContext> CreateWorkflowExecutionContextAsync(IWorkflowBlueprint workflowBlueprint, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var workflowInstance = await workflowFactory.InstantiateAsync(workflowBlueprint, cancellationToken: cancellationToken);
|
||||
return new WorkflowExecutionContext(serviceProvider, workflowBlueprint, workflowInstance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,13 +14,19 @@ namespace Elsa.Services
|
|||
private readonly IWorkflowQueue _workflowQueue;
|
||||
private readonly IWorkflowRegistry _workflowRegistry;
|
||||
private readonly IWorkflowInstanceStore _workflowInstanceStore;
|
||||
private readonly IGetsStartActivitiesForCompositeActivityBlueprint startingActivitiesProvider;
|
||||
|
||||
public WorkflowReviver(IWorkflowRunner workflowRunner, IWorkflowQueue workflowQueue, IWorkflowRegistry workflowRegistry, IWorkflowInstanceStore workflowInstanceStore)
|
||||
public WorkflowReviver(IWorkflowRunner workflowRunner,
|
||||
IWorkflowQueue workflowQueue,
|
||||
IWorkflowRegistry workflowRegistry,
|
||||
IWorkflowInstanceStore workflowInstanceStore,
|
||||
IGetsStartActivitiesForCompositeActivityBlueprint startingActivitiesProvider)
|
||||
{
|
||||
_workflowRunner = workflowRunner;
|
||||
_workflowQueue = workflowQueue;
|
||||
_workflowRegistry = workflowRegistry;
|
||||
_workflowInstanceStore = workflowInstanceStore;
|
||||
this.startingActivitiesProvider = startingActivitiesProvider ?? throw new ArgumentNullException(nameof(startingActivitiesProvider));
|
||||
}
|
||||
|
||||
public async Task<WorkflowInstance> ReviveAsync(WorkflowInstance workflowInstance, CancellationToken cancellationToken)
|
||||
|
|
@ -81,7 +87,7 @@ namespace Elsa.Services
|
|||
if (workflowBlueprint == null)
|
||||
throw new WorkflowException($"Could not find associated workflow definition {workflowInstance.DefinitionId} with version {workflowInstance.Version}");
|
||||
|
||||
var startActivity = workflowBlueprint.GetStartActivities().FirstOrDefault();
|
||||
var startActivity = startingActivitiesProvider.GetStartActivities(workflowBlueprint).FirstOrDefault();
|
||||
|
||||
if (startActivity == null)
|
||||
throw new WorkflowException($"Cannot revive workflow {workflowInstance.Id} because it has no start activities");
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ namespace Elsa.Services
|
|||
private readonly IMediator _mediator;
|
||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IGetsStartActivitiesForCompositeActivityBlueprint startingActivitiesProvider;
|
||||
|
||||
public WorkflowRunner(
|
||||
IWorkflowRegistry workflowRegistry,
|
||||
|
|
@ -48,7 +49,8 @@ namespace Elsa.Services
|
|||
Func<IWorkflowBuilder> workflowBuilderFactory,
|
||||
IMediator mediator,
|
||||
IServiceScopeFactory serviceScopeFactory,
|
||||
ILogger<WorkflowRunner> logger)
|
||||
ILogger<WorkflowRunner> logger,
|
||||
IGetsStartActivitiesForCompositeActivityBlueprint startingActivitiesProvider)
|
||||
{
|
||||
_workflowRegistry = workflowRegistry;
|
||||
_workflowFactory = workflowFactory;
|
||||
|
|
@ -56,6 +58,7 @@ namespace Elsa.Services
|
|||
_mediator = mediator;
|
||||
_serviceScopeFactory = serviceScopeFactory;
|
||||
_logger = logger;
|
||||
this.startingActivitiesProvider = startingActivitiesProvider ?? throw new ArgumentNullException(nameof(startingActivitiesProvider));
|
||||
_workflowInstanceManager = workflowInstanceStore;
|
||||
_workflowContextManager = workflowContextManager;
|
||||
_bookmarkFinder = bookmarkFinder;
|
||||
|
|
@ -284,7 +287,7 @@ namespace Elsa.Services
|
|||
private async Task<bool> BeginWorkflow(WorkflowExecutionContext workflowExecutionContext, IActivityBlueprint? activity, object? input, CancellationToken cancellationToken)
|
||||
{
|
||||
if (activity == null)
|
||||
activity = workflowExecutionContext.WorkflowBlueprint.GetStartActivities().FirstOrDefault() ?? workflowExecutionContext.WorkflowBlueprint.Activities.First();
|
||||
activity = startingActivitiesProvider.GetStartActivities(workflowExecutionContext.WorkflowBlueprint).FirstOrDefault() ?? workflowExecutionContext.WorkflowBlueprint.Activities.First();
|
||||
|
||||
if (!await CanExecuteAsync(workflowExecutionContext, activity, input, false, cancellationToken))
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -17,36 +17,24 @@ namespace Elsa.Triggers
|
|||
public class TriggerIndexer : ITriggerIndexer
|
||||
{
|
||||
private readonly IWorkflowRegistry _workflowRegistry;
|
||||
private readonly IBookmarkHasher _bookmarkHasher;
|
||||
private readonly IEnumerable<IBookmarkProvider> _providers;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly IWorkflowFactory _workflowFactory;
|
||||
private readonly IActivityTypeService _activityTypeService;
|
||||
private readonly ITriggerStore _triggerStore;
|
||||
private readonly IMediator _mediator;
|
||||
private readonly ILogger _logger;
|
||||
private readonly Stopwatch _stopwatch = new();
|
||||
private readonly IGetsTriggersForWorkflowBlueprints _triggersForBookmarksProvider;
|
||||
|
||||
public TriggerIndexer(
|
||||
IWorkflowRegistry workflowRegistry,
|
||||
IBookmarkHasher bookmarkHasher,
|
||||
IEnumerable<IBookmarkProvider> providers,
|
||||
IServiceProvider serviceProvider,
|
||||
IWorkflowFactory workflowFactory,
|
||||
IActivityTypeService activityTypeService,
|
||||
ITriggerStore triggerStore,
|
||||
IMediator mediator,
|
||||
ILogger<TriggerIndexer> logger)
|
||||
ILogger<TriggerIndexer> logger,
|
||||
IGetsTriggersForWorkflowBlueprints triggersForBookmarksProvider)
|
||||
{
|
||||
_workflowRegistry = workflowRegistry;
|
||||
_bookmarkHasher = bookmarkHasher;
|
||||
_providers = providers;
|
||||
_serviceProvider = serviceProvider;
|
||||
_workflowFactory = workflowFactory;
|
||||
_activityTypeService = activityTypeService;
|
||||
_triggerStore = triggerStore;
|
||||
_mediator = mediator;
|
||||
_logger = logger;
|
||||
_triggersForBookmarksProvider = triggersForBookmarksProvider;
|
||||
}
|
||||
|
||||
public async Task IndexTriggersAsync(CancellationToken cancellationToken = default)
|
||||
|
|
@ -63,49 +51,12 @@ namespace Elsa.Triggers
|
|||
_logger.LogInformation("Indexing triggers");
|
||||
|
||||
var workflowBlueprintList = workflowBlueprints.ToList();
|
||||
var triggers = (await GetTriggersAsync(workflowBlueprintList, cancellationToken)).ToList();
|
||||
var triggers = (await _triggersForBookmarksProvider.GetTriggersAsync(workflowBlueprintList, cancellationToken)).ToList();
|
||||
|
||||
_stopwatch.Stop();
|
||||
_logger.LogInformation("Indexed {TriggerCount} triggers in {ElapsedTime}", triggers.Count, _stopwatch.Elapsed);
|
||||
|
||||
await _triggerStore.StoreAsync(triggers, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<WorkflowTrigger>> GetTriggersAsync(ICollection<IWorkflowBlueprint> workflowBlueprints, CancellationToken cancellationToken)
|
||||
{
|
||||
var allTriggers = new List<WorkflowTrigger>();
|
||||
var activityTypes = (await _activityTypeService.GetActivityTypesAsync(cancellationToken)).ToDictionary(x => x.TypeName);
|
||||
|
||||
foreach (var workflowBlueprint in workflowBlueprints)
|
||||
{
|
||||
var startActivities = workflowBlueprint.GetStartActivities();
|
||||
var workflowInstance = await _workflowFactory.InstantiateAsync(workflowBlueprint, cancellationToken: cancellationToken);
|
||||
var workflowExecutionContext = new WorkflowExecutionContext(_serviceProvider, workflowBlueprint, workflowInstance);
|
||||
|
||||
foreach (var activity in startActivities)
|
||||
{
|
||||
var activityExecutionContext = new ActivityExecutionContext(_serviceProvider, workflowExecutionContext, activity, null, false, cancellationToken);
|
||||
var activityType = activityTypes[activity.Type];
|
||||
var context = new BookmarkProviderContext(activityExecutionContext, activityType, BookmarkIndexingMode.WorkflowBlueprint);
|
||||
var providers = await FilterProvidersAsync(context).ToListAsync(cancellationToken);
|
||||
|
||||
foreach (var provider in providers)
|
||||
{
|
||||
var bookmarks = (await provider.GetBookmarksAsync(context, cancellationToken)).ToList();
|
||||
var triggers = bookmarks.Select(x => new WorkflowTrigger(workflowBlueprint, activity.Id, activity.Type, _bookmarkHasher.Hash(x), x)).ToList();
|
||||
allTriggers.AddRange(triggers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return allTriggers;
|
||||
}
|
||||
|
||||
private async IAsyncEnumerable<IBookmarkProvider> FilterProvidersAsync(BookmarkProviderContext context)
|
||||
{
|
||||
foreach (var provider in _providers)
|
||||
if (await provider.SupportsActivityAsync(context))
|
||||
yield return provider;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.ActivityProviders;
|
||||
using Elsa.Bookmarks;
|
||||
using Elsa.Services;
|
||||
using Elsa.Services.Models;
|
||||
|
||||
namespace Elsa.Triggers
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of <see cref="IGetsTriggersForActivityBlueprintAndWorkflow"/>.
|
||||
/// </summary>
|
||||
public class TriggersForActivityBlueprintAndWorkflowProvider : IGetsTriggersForActivityBlueprintAndWorkflow
|
||||
{
|
||||
readonly IBookmarkHasher bookmarkHasher;
|
||||
readonly IEnumerable<IBookmarkProvider> bookmarkProviders;
|
||||
readonly ICreatesActivityExecutionContextForActivityBlueprint activityExecutionContextFactory;
|
||||
|
||||
public TriggersForActivityBlueprintAndWorkflowProvider(IBookmarkHasher bookmarkHasher,
|
||||
IEnumerable<IBookmarkProvider> bookmarkProviders,
|
||||
ICreatesActivityExecutionContextForActivityBlueprint activityExecutionContextFactory)
|
||||
{
|
||||
this.bookmarkHasher = bookmarkHasher ?? throw new System.ArgumentNullException(nameof(bookmarkHasher));
|
||||
this.bookmarkProviders = bookmarkProviders ?? throw new System.ArgumentNullException(nameof(bookmarkProviders));
|
||||
this.activityExecutionContextFactory = activityExecutionContextFactory ?? throw new System.ArgumentNullException(nameof(activityExecutionContextFactory));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of the workflow triggers for the specified activity blueprint.
|
||||
/// </summary>
|
||||
/// <param name="activityBlueprint">An activity blueprint</param>
|
||||
/// <param name="workflowExecutionContext">A workflow execution context</param>
|
||||
/// <param name="activityTypes">A dictionary of all of the activity types (by name)</param>
|
||||
/// <param name="cancellationToken">An optional cancellation token</param>
|
||||
/// <returns>A task exposing a collection of workflow triggers for the activity and workflow.</returns>
|
||||
public async Task<IEnumerable<WorkflowTrigger>> GetTriggersForActivityBlueprintAsync(IActivityBlueprint activityBlueprint,
|
||||
WorkflowExecutionContext workflowExecutionContext,
|
||||
IDictionary<string, ActivityType> activityTypes,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var bookmarkProviderContext = GetBookmarkProviderContext(activityBlueprint, workflowExecutionContext, cancellationToken, activityTypes);
|
||||
var supportedBookmarkProviders = await GetSupportedBookmarkProvidersForContextAsync(bookmarkProviderContext)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var tasksOfListsOfTriggers = supportedBookmarkProviders
|
||||
.Select(bookmarkProvider => GetTriggersForBookmarkProvider(bookmarkProvider,
|
||||
bookmarkProviderContext,
|
||||
activityBlueprint,
|
||||
workflowExecutionContext.WorkflowBlueprint,
|
||||
cancellationToken));
|
||||
return (await Task.WhenAll(tasksOfListsOfTriggers))
|
||||
.SelectMany(x => x)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
BookmarkProviderContext GetBookmarkProviderContext(IActivityBlueprint activity,
|
||||
WorkflowExecutionContext workflowExecutionContext,
|
||||
CancellationToken cancellationToken,
|
||||
IDictionary<string,ActivityType> activityTypes)
|
||||
{
|
||||
var activityExecutionContext = activityExecutionContextFactory.CreateActivityExecutionContext(activity,
|
||||
workflowExecutionContext,
|
||||
cancellationToken);
|
||||
var activityType = activityTypes[activity.Type];
|
||||
return new BookmarkProviderContext(activityExecutionContext, activityType, BookmarkIndexingMode.WorkflowBlueprint);
|
||||
}
|
||||
|
||||
async IAsyncEnumerable<IBookmarkProvider> GetSupportedBookmarkProvidersForContextAsync(BookmarkProviderContext context)
|
||||
{
|
||||
foreach (var provider in bookmarkProviders)
|
||||
if (await provider.SupportsActivityAsync(context))
|
||||
yield return provider;
|
||||
}
|
||||
|
||||
async Task<IList<WorkflowTrigger>> GetTriggersForBookmarkProvider(IBookmarkProvider provider,
|
||||
BookmarkProviderContext context,
|
||||
IActivityBlueprint activityBlueprint,
|
||||
IWorkflowBlueprint workflowBlueprint,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var bookmarks = (await provider.GetBookmarksAsync(context, cancellationToken)).ToList();
|
||||
return bookmarks
|
||||
.Select(x => new WorkflowTrigger(workflowBlueprint, activityBlueprint.Id, activityBlueprint.Type, bookmarkHasher.Hash(x), x))
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
70
src/core/Elsa.Core/Triggers/TriggersForBlueprintsProvider.cs
Normal file
70
src/core/Elsa.Core/Triggers/TriggersForBlueprintsProvider.cs
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.ActivityProviders;
|
||||
using Elsa.Services;
|
||||
using Elsa.Services.Models;
|
||||
|
||||
namespace Elsa.Triggers
|
||||
{
|
||||
/// <summary>
|
||||
/// Default implementation of <see cref="IGetsTriggersForWorkflowBlueprints"/> which
|
||||
/// gets all of the workflow triggers for a collection of workflow blueprints.
|
||||
/// </summary>
|
||||
public class TriggersForBlueprintsProvider : IGetsTriggersForWorkflowBlueprints
|
||||
{
|
||||
readonly IActivityTypeService activityTypeService;
|
||||
readonly ICreatesWorkflowExecutionContextForWorkflowBlueprint workflowExecutionContextFactory;
|
||||
readonly IGetsTriggersForActivityBlueprintAndWorkflow triggerProvider;
|
||||
readonly IGetsStartActivitiesForCompositeActivityBlueprint startingActivitiesProvider;
|
||||
|
||||
public TriggersForBlueprintsProvider(IActivityTypeService activityTypeService,
|
||||
ICreatesWorkflowExecutionContextForWorkflowBlueprint workflowExecutionContextFactory,
|
||||
IGetsTriggersForActivityBlueprintAndWorkflow triggerProvider,
|
||||
IGetsStartActivitiesForCompositeActivityBlueprint startingActivitiesProvider)
|
||||
{
|
||||
this.activityTypeService = activityTypeService ?? throw new ArgumentNullException(nameof(activityTypeService));
|
||||
this.workflowExecutionContextFactory = workflowExecutionContextFactory ?? throw new ArgumentNullException(nameof(workflowExecutionContextFactory));
|
||||
this.triggerProvider = triggerProvider ?? throw new ArgumentNullException(nameof(triggerProvider));
|
||||
this.startingActivitiesProvider = startingActivitiesProvider ?? throw new ArgumentNullException(nameof(startingActivitiesProvider));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the triggers for all of the specified workflow blueprints.
|
||||
/// </summary>
|
||||
/// <param name="workflowBlueprints">The workflow blueprints for which to get triggers.</param>
|
||||
/// <param name="cancellationToken">An optional cancellation token.</param>
|
||||
/// <returns>A task which exposes an enumerable collection of workflow triggers.</returns>
|
||||
public async Task<IEnumerable<WorkflowTrigger>> GetTriggersAsync(IEnumerable<IWorkflowBlueprint> workflowBlueprints,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var activityTypes = (await activityTypeService.GetActivityTypesAsync(cancellationToken))
|
||||
.ToDictionary(x => x.TypeName);
|
||||
|
||||
var tasksOfListsOfTriggers = workflowBlueprints
|
||||
.Select(workflowBlueprint => GetWorkflowTriggersForWorkflowBlueprintAsync(workflowBlueprint, activityTypes, cancellationToken));
|
||||
|
||||
return (await Task.WhenAll(tasksOfListsOfTriggers))
|
||||
.SelectMany(x => x)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
async Task<IList<WorkflowTrigger>> GetWorkflowTriggersForWorkflowBlueprintAsync(IWorkflowBlueprint workflowBlueprint,
|
||||
IDictionary<string, ActivityType> activityTypes,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var startingActivityBlueprints = startingActivitiesProvider.GetStartActivities(workflowBlueprint);
|
||||
var workflowExecutionContext = await workflowExecutionContextFactory.CreateWorkflowExecutionContextAsync(workflowBlueprint, cancellationToken);
|
||||
var tasksOfCollectionsOfTriggers = startingActivityBlueprints
|
||||
.Select(async activityBlueprint => await triggerProvider.GetTriggersForActivityBlueprintAsync(activityBlueprint,
|
||||
workflowExecutionContext,
|
||||
activityTypes,
|
||||
cancellationToken));
|
||||
return (await Task.WhenAll(tasksOfCollectionsOfTriggers))
|
||||
.SelectMany(x => x)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ using Elsa.Persistence.EntityFramework.Sqlite;
|
|||
using Elsa.Persistence.YesSql;
|
||||
using YesSql.Provider.Sqlite;
|
||||
using System.Data;
|
||||
using Elsa.Testing.Shared.Helpers;
|
||||
|
||||
namespace Elsa.Core.IntegrationTests.Autofixture
|
||||
{
|
||||
|
|
@ -48,13 +49,15 @@ namespace Elsa.Core.IntegrationTests.Autofixture
|
|||
{
|
||||
public override ICustomization GetCustomization(ParameterInfo parameter)
|
||||
{
|
||||
var tempFolder = new TemporaryFolder();
|
||||
|
||||
return new HostBubilderUsingServicesCustomization(services => {
|
||||
services
|
||||
.AddElsa(elsa => {
|
||||
elsa
|
||||
.AddWorkflow<DuplicateActivitiesWorkflow>()
|
||||
.UseEntityFrameworkPersistence(opts => {
|
||||
opts.UseSqlite("Data Source=elsa.db;", db => db.MigrationsAssembly(typeof(SqliteElsaContextFactory).Assembly.GetName().Name));
|
||||
opts.UseSqlite($"Data Source={tempFolder.Folder}elsa.db;", db => db.MigrationsAssembly(typeof(SqliteElsaContextFactory).Assembly.GetName().Name));
|
||||
});
|
||||
});
|
||||
}, parameter);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection;
|
|||
using Elsa.Persistence.EntityFramework.Core.Extensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Elsa.Persistence.EntityFramework.Sqlite;
|
||||
using Elsa.Testing.Shared.Helpers;
|
||||
|
||||
namespace Elsa.Core.IntegrationTests.Autofixture
|
||||
{
|
||||
|
|
@ -13,12 +14,14 @@ namespace Elsa.Core.IntegrationTests.Autofixture
|
|||
{
|
||||
public override ICustomization GetCustomization(ParameterInfo parameter)
|
||||
{
|
||||
var tempFolder = new TemporaryFolder();
|
||||
|
||||
return new HostBubilderUsingServicesCustomization(services => {
|
||||
services
|
||||
.AddElsa(elsa => {
|
||||
elsa
|
||||
.UseEntityFrameworkPersistence(opts => {
|
||||
opts.UseSqlite("Data Source=elsa.db;", db => db.MigrationsAssembly(typeof(SqliteElsaContextFactory).Assembly.GetName().Name));
|
||||
opts.UseSqlite($"Data Source={tempFolder.Folder}elsa.db;", db => db.MigrationsAssembly(typeof(SqliteElsaContextFactory).Assembly.GetName().Name));
|
||||
})
|
||||
.AddPersistableWorkflow();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,203 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.ControlFlow;
|
||||
using Elsa.Activities.Primitives;
|
||||
using Elsa.Activities.Signaling;
|
||||
using Elsa.Activities.UserTask.Activities;
|
||||
using Elsa.Builders;
|
||||
using Elsa.Models;
|
||||
using Elsa.Persistence;
|
||||
using Elsa.Triggers;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Xunit;
|
||||
|
||||
namespace Elsa.Core.IntegrationTests.Triggers
|
||||
{
|
||||
public class TriggerIndexerIntegrationTests
|
||||
{
|
||||
[Fact(DisplayName = "The IndexTriggersAsync method should return a trigger for a blocking start activity")]
|
||||
public async Task IndexTriggersAsyncShouldIncludeBlockingStartActivities()
|
||||
{
|
||||
var allTriggers = await IndexThenGetAllTriggersAsync();
|
||||
Assert.True(allTriggers.Any(x => x.ActivityType == nameof(SignalReceived)
|
||||
&& x.WorkflowBlueprint.Name == nameof(WorkflowWithBlockingStartActivity)),
|
||||
"A trigger exists for the expected blocking activity");
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "The IndexTriggersAsync method should not return a trigger for a non-blocking start activity")]
|
||||
public async Task IndexTriggersAsyncShouldNotIncludeNonBlockingStartActivities()
|
||||
{
|
||||
var allTriggers = await IndexThenGetAllTriggersAsync();
|
||||
Assert.False(allTriggers.Any(x => x.WorkflowBlueprint.Name == nameof(WorkflowWithNonBlockingStartActivity)),
|
||||
"No triggers exist for the workflow which starts with a non-blocking activity");
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "The IndexTriggersAsync method should return a trigger for a blocking start activity within a composite activity",
|
||||
Skip = "This test is the repro case for #738, which is not yet implemented")]
|
||||
public async Task IndexTriggersAsyncShouldIncludeBlockingCompositeStartActivities()
|
||||
{
|
||||
var allTriggers = await IndexThenGetAllTriggersAsync();
|
||||
Assert.True(allTriggers.Any(x => x.WorkflowBlueprint.Name == "WorkflowWithBlockingCompositeStartActivity"),
|
||||
"A trigger exists for the expected blocking composite activity");
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "The IndexTriggersAsync method should not return a trigger for a non-blocking start activity within a composite activity")]
|
||||
public async Task IndexTriggersAsyncShouldNotIncludeNonBlockingCompositeStartActivities()
|
||||
{
|
||||
var allTriggers = await IndexThenGetAllTriggersAsync();
|
||||
Assert.False(allTriggers.Any(x => x.WorkflowBlueprint.Name == "WorkflowWithNonBlockingCompositeStartActivity"),
|
||||
"No triggers exist for the workflow which starts with a non-blocking composite activity");
|
||||
}
|
||||
|
||||
async Task<IEnumerable<WorkflowTrigger>> IndexThenGetAllTriggersAsync()
|
||||
{
|
||||
var serviceProvider = await GetServiceProvider();
|
||||
|
||||
var sut = serviceProvider.GetRequiredService<ITriggerIndexer>();
|
||||
await sut.IndexTriggersAsync();
|
||||
|
||||
var triggerStore = serviceProvider.GetRequiredService<ITriggerStore>();
|
||||
return await triggerStore.GetAsync();
|
||||
}
|
||||
|
||||
async Task<IServiceProvider> GetServiceProvider()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddElsa(elsa => {
|
||||
elsa
|
||||
.AddWorkflow<WorkflowWithBlockingStartActivity>()
|
||||
.AddWorkflow<WorkflowWithNonBlockingStartActivity>()
|
||||
.AddActivity<SignalReceived>()
|
||||
.AddActivity<SetVariable>()
|
||||
;
|
||||
});
|
||||
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
var definitionStore = serviceProvider.GetRequiredService<IWorkflowDefinitionStore>();
|
||||
await definitionStore.AddAsync(new WorkflowDefinition
|
||||
{
|
||||
Id = "1",
|
||||
DefinitionId = "WorkflowWithNonBlockingCompositeStartActivity",
|
||||
Name = "WorkflowWithNonBlockingCompositeStartActivity",
|
||||
Version = 1,
|
||||
IsPublished = true,
|
||||
IsLatest = true,
|
||||
PersistenceBehavior = WorkflowPersistenceBehavior.Suspended,
|
||||
Activities = new[]
|
||||
{
|
||||
GetNonBlockingCompositeActivityDefinition("nonBlockingComposite1"),
|
||||
GetNonBlockingActivityDefinition("nonBlocking1"),
|
||||
},
|
||||
Connections = new[]
|
||||
{
|
||||
new ConnectionDefinition("nonBlockingComposite1", "nonBlocking1", OutcomeNames.Done),
|
||||
}
|
||||
});
|
||||
await definitionStore.AddAsync(new WorkflowDefinition
|
||||
{
|
||||
Id = "2",
|
||||
DefinitionId = "WorkflowWithBlockingCompositeStartActivity",
|
||||
Name = "WorkflowWithBlockingCompositeStartActivity",
|
||||
Version = 1,
|
||||
IsPublished = true,
|
||||
IsLatest = true,
|
||||
PersistenceBehavior = WorkflowPersistenceBehavior.Suspended,
|
||||
Activities = new[]
|
||||
{
|
||||
GetBlockingCompositeActivityDefinition("blockingComposite1"),
|
||||
GetNonBlockingActivityDefinition("nonBlocking2"),
|
||||
},
|
||||
Connections = new[]
|
||||
{
|
||||
new ConnectionDefinition("blockingComposite1", "nonBlocking2", OutcomeNames.Done),
|
||||
}
|
||||
});
|
||||
|
||||
return serviceProvider;
|
||||
}
|
||||
|
||||
class WorkflowWithBlockingStartActivity : IWorkflow
|
||||
{
|
||||
public void Build(IWorkflowBuilder builder)
|
||||
{
|
||||
builder
|
||||
.StartWith<SignalReceived>(a => a.Set(x => x.Signal, "MySignal").Set(x => x.Id, "SignalReceived1"))
|
||||
.Then<Finish>(a => a.Set(x => x.Id, "Finish1"));
|
||||
}
|
||||
}
|
||||
|
||||
class WorkflowWithNonBlockingStartActivity : IWorkflow
|
||||
{
|
||||
public void Build(IWorkflowBuilder builder)
|
||||
{
|
||||
builder
|
||||
.StartWith<SetVariable>(t => t.Set(x => x.VariableName, "Unused").Set(x => x.Value, "Unused").Set(x => x.Id, "SetVariable1"))
|
||||
.Then<Finish>(a => a.Set(x => x.Id, "Finish2"));
|
||||
}
|
||||
}
|
||||
|
||||
ActivityDefinition GetBlockingActivityDefinition(string id)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
ActivityId = id,
|
||||
Type = nameof(SignalReceived),
|
||||
Properties = new []
|
||||
{
|
||||
ActivityDefinitionProperty.Literal(nameof(SignalReceived.Signal), "MySignal"),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ActivityDefinition GetNonBlockingActivityDefinition(string id)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
ActivityId = id,
|
||||
Type = nameof(SetVariable),
|
||||
Properties = new []
|
||||
{
|
||||
ActivityDefinitionProperty.Literal(nameof(SetVariable.VariableName), "Unused"),
|
||||
ActivityDefinitionProperty.Literal(nameof(SetVariable.Value), "Unused"),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ActivityDefinition GetBlockingCompositeActivityDefinition(string id)
|
||||
{
|
||||
return new CompositeActivityDefinition
|
||||
{
|
||||
ActivityId = id,
|
||||
Activities = new []
|
||||
{
|
||||
GetBlockingActivityDefinition("SignalReceived2"),
|
||||
GetNonBlockingActivityDefinition("SetVariable2"),
|
||||
},
|
||||
Connections = new []
|
||||
{
|
||||
new ConnectionDefinition("UserTask2", "SetVariable2", OutcomeNames.Done),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ActivityDefinition GetNonBlockingCompositeActivityDefinition(string id)
|
||||
{
|
||||
return new CompositeActivityDefinition
|
||||
{
|
||||
ActivityId = id,
|
||||
Activities = new []
|
||||
{
|
||||
GetNonBlockingActivityDefinition("SetVariable3"),
|
||||
GetNonBlockingActivityDefinition("SetVariable4"),
|
||||
},
|
||||
Connections = new []
|
||||
{
|
||||
new ConnectionDefinition("SetVariable3", "SetVariable4", OutcomeNames.Done),
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
using System.Reflection;
|
||||
using AutoFixture;
|
||||
using AutoFixture.Xunit2;
|
||||
using Elsa.Testing.Shared.AutoFixture.Customizations;
|
||||
|
||||
namespace Elsa.Testing.Shared.AutoFixture.Attributes
|
||||
{
|
||||
public class MockBookmarkProvidersAttribute : CustomizeAttribute
|
||||
{
|
||||
readonly int howMany;
|
||||
|
||||
public override ICustomization GetCustomization(ParameterInfo parameter)
|
||||
=> new MockBookmarkProvidersCustomization(parameter, howMany);
|
||||
|
||||
public MockBookmarkProvidersAttribute(int howMany = 3)
|
||||
{
|
||||
this.howMany = howMany;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
using System.Reflection;
|
||||
using AutoFixture.Kernel;
|
||||
using Elsa.Testing.Shared.AutoFixture.SpecimenBuilders;
|
||||
|
||||
namespace Elsa.Testing.Shared.AutoFixture.Customizations
|
||||
{
|
||||
public class MockBookmarkProvidersCustomization : SpecimenBuilderForParameterCustomization
|
||||
{
|
||||
readonly int howMany;
|
||||
|
||||
protected override ISpecimenBuilder GetUnfilteredSpecimenBuilder()
|
||||
=> new MockBookmarkProvidersSpecimenBuilder(howMany);
|
||||
|
||||
public MockBookmarkProvidersCustomization(ParameterInfo parameter, int howMany) : base(parameter)
|
||||
{
|
||||
this.howMany = howMany;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AutoFixture.Kernel;
|
||||
using Elsa.Bookmarks;
|
||||
using Moq;
|
||||
|
||||
namespace Elsa.Testing.Shared.AutoFixture.SpecimenBuilders
|
||||
{
|
||||
public class MockBookmarkProvidersSpecimenBuilder : ISpecimenBuilder
|
||||
{
|
||||
readonly int howMany;
|
||||
|
||||
public object Create(object request, ISpecimenContext context)
|
||||
{
|
||||
if (!request.IsAnAutofixtureRequestForType<IEnumerable<IBookmarkProvider>>())
|
||||
return new NoSpecimen();
|
||||
|
||||
return Enumerable.Range(0, howMany)
|
||||
.Select(x => Mock.Of<IBookmarkProvider>())
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public MockBookmarkProvidersSpecimenBuilder(int howMany)
|
||||
{
|
||||
this.howMany = howMany;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using Elsa.Services.Models;
|
||||
using Elsa.Testing.Shared.AutoFixture.Attributes;
|
||||
using Xunit;
|
||||
|
||||
namespace Elsa.Services
|
||||
{
|
||||
public class ActivityExecutionContextForActivityBlueprintFactoryTests
|
||||
{
|
||||
[Theory(DisplayName = "The CreateActivityExecutionContext method should create a context using the activity blueprint, the workflow execution context, cancellation token and injected service provider."), AutoMoqData]
|
||||
public void CreateActivityExecutionContextCreatesContextUsingBlueprintExecutionContextCancellationTokenAndServiceProvider([AutofixtureServiceProvider] IServiceProvider serviceProvider,
|
||||
IActivityBlueprint activityBlueprint,
|
||||
[OmitOnRecursion] WorkflowExecutionContext workflowExecutionContext,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var sut = new ActivityExecutionContextForActivityBlueprintFactory(serviceProvider);
|
||||
|
||||
var result = sut.CreateActivityExecutionContext(activityBlueprint, workflowExecutionContext, cancellationToken);
|
||||
|
||||
Assert.True(ReferenceEquals(activityBlueprint, result.ActivityBlueprint), "The activity blueprint should be the same");
|
||||
Assert.True(ReferenceEquals(workflowExecutionContext, result.WorkflowExecutionContext), "The workflow execution context should be the same");
|
||||
Assert.True(Equals(cancellationToken, result.CancellationToken), "The cancellation token should be equal");
|
||||
Assert.True(ReferenceEquals(serviceProvider, result.ServiceProvider), "The service provider should be the same");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Elsa.Services.Models;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Elsa.Services
|
||||
{
|
||||
public class StartActivitiesForCompositeActivityBlueprintProviderTests
|
||||
{
|
||||
[Theory(DisplayName = "The GetStartActivities returns only activities that have no inbound workflow connections"), AutoMoqData]
|
||||
public void GetStartActivitiesReturnsAllActivitiesWhichHaveNoInboundConnections(StartActivitiesForCompositeActivityBlueprintProvider sut,
|
||||
IWorkflowBlueprint workflowBlueprint,
|
||||
IActivityBlueprint activityBlueprint1,
|
||||
IActivityBlueprint activityBlueprint2,
|
||||
IActivityBlueprint activityBlueprint3,
|
||||
IActivityBlueprint activityBlueprint4,
|
||||
string activityBlueprintId1,
|
||||
string activityBlueprintId2,
|
||||
string activityBlueprintId3,
|
||||
string activityBlueprintId4,
|
||||
IConnection connection1,
|
||||
IConnection connection2,
|
||||
ITargetEndpoint endpoint1,
|
||||
ITargetEndpoint endpoint2)
|
||||
{
|
||||
SetupActivitiesWithIds(new () {
|
||||
{activityBlueprintId1, activityBlueprint1},
|
||||
{activityBlueprintId2, activityBlueprint2},
|
||||
{activityBlueprintId3, activityBlueprint3},
|
||||
{activityBlueprintId4, activityBlueprint4},
|
||||
});
|
||||
Mock.Get(workflowBlueprint).SetupGet(x => x.Connections).Returns(new [] { connection1, connection2 });
|
||||
Mock.Get(workflowBlueprint).SetupGet(x => x.Activities).Returns(new [] { activityBlueprint1, activityBlueprint2, activityBlueprint3, activityBlueprint4 });
|
||||
Mock.Get(connection1).SetupGet(x => x.Target).Returns(endpoint1);
|
||||
Mock.Get(connection2).SetupGet(x => x.Target).Returns(endpoint2);
|
||||
Mock.Get(endpoint1).SetupGet(x => x.Activity).Returns(activityBlueprint2);
|
||||
Mock.Get(endpoint2).SetupGet(x => x.Activity).Returns(activityBlueprint4);
|
||||
|
||||
var result = sut.GetStartActivities(workflowBlueprint).ToArray();
|
||||
|
||||
Assert.Equal(new [] { activityBlueprint1, activityBlueprint3 }, result);
|
||||
}
|
||||
|
||||
void SetupActivitiesWithIds(Dictionary<string,IActivityBlueprint> idsToBlueprints)
|
||||
{
|
||||
foreach(var kvp in idsToBlueprints)
|
||||
Mock.Get(kvp.Value).SetupGet(x => x.Id).Returns(kvp.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Models;
|
||||
using Elsa.Services.Models;
|
||||
using Elsa.Testing.Shared.AutoFixture.Attributes;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Elsa.Services
|
||||
{
|
||||
public class WorkflowExecutionContextForWorkflowBlueprintFactoryTests
|
||||
{
|
||||
[Theory(DisplayName = "The CreateWorkflowExecutionContextAsync method returns an execution context using the blueprint, an instance and the service provider"), AutoMoqData]
|
||||
public async Task CreateWorkflowExecutionContextAsyncReturnsContextWithBlueprintInstanceAndServiceProvider([AutofixtureServiceProvider] IServiceProvider serviceProvider,
|
||||
IWorkflowFactory workflowFactory,
|
||||
IWorkflowBlueprint workflowBlueprint,
|
||||
[OmitOnRecursion] WorkflowInstance instance)
|
||||
{
|
||||
var sut = new WorkflowExecutionContextForWorkflowBlueprintFactory(serviceProvider, workflowFactory);
|
||||
Mock.Get(workflowFactory)
|
||||
.Setup(x => x.InstantiateAsync(workflowBlueprint, default, default, default))
|
||||
.Returns(() => Task.FromResult(instance));
|
||||
|
||||
var result = await sut.CreateWorkflowExecutionContextAsync(workflowBlueprint);
|
||||
|
||||
Assert.Same(serviceProvider, result.ServiceProvider);
|
||||
Assert.Same(workflowBlueprint, result.WorkflowBlueprint);
|
||||
Assert.Same(instance, result.WorkflowInstance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.ActivityProviders;
|
||||
using Elsa.Bookmarks;
|
||||
using Elsa.Models;
|
||||
using Elsa.Services;
|
||||
using Elsa.Services.Models;
|
||||
using Elsa.Testing.Shared.AutoFixture.Attributes;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Elsa.Triggers
|
||||
{
|
||||
public class TriggersForActivityBlueprintAndWorkflowProviderTests
|
||||
{
|
||||
[Theory(DisplayName = "The GetTriggersForActivityBlueprintAsync returns a trigger for every bookmark in the bookmark providers that support the activity"), AutoMoqData]
|
||||
public async Task GetTriggersForActivityBlueprintAsyncReturnsTriggersForEachBookmarkInSupportedBookmarkProviders(IBookmarkHasher bookmarkHasher,
|
||||
IBookmarkProvider bookmarkProvider1,
|
||||
IBookmarkProvider unsupportedBookmarkProvider,
|
||||
IBookmarkProvider bookmarkProvider2,
|
||||
ICreatesActivityExecutionContextForActivityBlueprint activityExecutionContextFactory,
|
||||
IActivityBlueprint activityBlueprint,
|
||||
[AutofixtureServiceProvider] IServiceProvider serviceProvider,
|
||||
IWorkflowBlueprint workflowBlueprint,
|
||||
[OmitOnRecursion] WorkflowInstance workflowInstance,
|
||||
ActivityType activityType,
|
||||
IBookmark bookmark1,
|
||||
IBookmark bookmark2,
|
||||
IBookmark bookmark3,
|
||||
IBookmark bookmark4)
|
||||
{
|
||||
var sut = new TriggersForActivityBlueprintAndWorkflowProvider(bookmarkHasher,
|
||||
new[] { bookmarkProvider1, unsupportedBookmarkProvider, bookmarkProvider2 },
|
||||
activityExecutionContextFactory);
|
||||
var workflowExecutionContext = new WorkflowExecutionContext(serviceProvider, workflowBlueprint, workflowInstance);
|
||||
var activityExecutionContext = new ActivityExecutionContext(serviceProvider, workflowExecutionContext, activityBlueprint, default, default, default);
|
||||
|
||||
Mock.Get(activityExecutionContextFactory)
|
||||
.Setup(x => x.CreateActivityExecutionContext(activityBlueprint, workflowExecutionContext, default))
|
||||
.Returns(activityExecutionContext);
|
||||
Mock.Get(activityBlueprint).SetupGet(x => x.Type).Returns(activityType.TypeName);
|
||||
Mock.Get(bookmarkProvider1)
|
||||
.Setup(x => x.SupportsActivityAsync(It.Is<BookmarkProviderContext>(c => c.ActivityType == activityType), default))
|
||||
.Returns(() => ValueTask.FromResult(true));
|
||||
Mock.Get(unsupportedBookmarkProvider)
|
||||
.Setup(x => x.SupportsActivityAsync(It.Is<BookmarkProviderContext>(c => c.ActivityType == activityType), default))
|
||||
.Returns(() => ValueTask.FromResult(false));
|
||||
Mock.Get(bookmarkProvider2)
|
||||
.Setup(x => x.SupportsActivityAsync(It.Is<BookmarkProviderContext>(c => c.ActivityType == activityType), default))
|
||||
.Returns(() => ValueTask.FromResult(true));
|
||||
Mock.Get(bookmarkProvider1)
|
||||
.Setup(x => x.GetBookmarksAsync(It.IsAny<BookmarkProviderContext>(), default))
|
||||
.Returns(() => ValueTask.FromResult<IEnumerable<IBookmark>>(new [] { bookmark1, bookmark2 }));
|
||||
Mock.Get(bookmarkProvider2)
|
||||
.Setup(x => x.GetBookmarksAsync(It.IsAny<BookmarkProviderContext>(), default))
|
||||
.Returns(() => ValueTask.FromResult<IEnumerable<IBookmark>>(new [] { bookmark3, bookmark4 }));
|
||||
|
||||
var result = await sut.GetTriggersForActivityBlueprintAsync(activityBlueprint,
|
||||
workflowExecutionContext,
|
||||
new Dictionary<string, ActivityType> { { activityType.TypeName, activityType } });
|
||||
|
||||
Assert.True(result.Any(x => x.Bookmark == bookmark1), "Result contains a trigger for bookmark 1");
|
||||
Assert.True(result.Any(x => x.Bookmark == bookmark2), "Result contains a trigger for bookmark 2");
|
||||
Assert.True(result.Any(x => x.Bookmark == bookmark3), "Result contains a trigger for bookmark 3");
|
||||
Assert.True(result.Any(x => x.Bookmark == bookmark4), "Result contains a trigger for bookmark 4");
|
||||
Assert.True(result.Count() == 4, "Result has 4 items");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AutoFixture.Xunit2;
|
||||
using Elsa.ActivityProviders;
|
||||
using Elsa.Bookmarks;
|
||||
using Elsa.Models;
|
||||
using Elsa.Services;
|
||||
using Elsa.Services.Models;
|
||||
using Elsa.Testing.Shared.AutoFixture.Attributes;
|
||||
using Elsa.Triggers;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Elsa.UnitTests.Triggers
|
||||
{
|
||||
public class TriggersForBlueprintsProviderTests
|
||||
{
|
||||
[Theory(DisplayName = "The GetTriggersAsync method should return all triggers for start activities of the workflow blueprints"), AutoMoqData]
|
||||
public async Task GetTriggersAsyncGetsAllTriggersForAllBlueprintsStartActivitiesAndCompatibleBookmarks([Frozen] IActivityTypeService activityTypeService,
|
||||
[Frozen] ICreatesWorkflowExecutionContextForWorkflowBlueprint workflowExecutionContextFactory,
|
||||
[Frozen] IGetsTriggersForActivityBlueprintAndWorkflow triggerProvider,
|
||||
[Frozen] IGetsStartActivitiesForCompositeActivityBlueprint startingActivitiesProvider,
|
||||
TriggersForBlueprintsProvider sut,
|
||||
IWorkflowBlueprint workflowBlueprint1,
|
||||
IWorkflowBlueprint workflowBlueprint2,
|
||||
ActivityType activityType1,
|
||||
ActivityType activityType2,
|
||||
ActivityType activityType3,
|
||||
IActivityBlueprint activityBlueprint1,
|
||||
IActivityBlueprint activityBlueprint2,
|
||||
IActivityBlueprint activityBlueprint3,
|
||||
WorkflowTrigger trigger1,
|
||||
WorkflowTrigger trigger2,
|
||||
WorkflowTrigger trigger3,
|
||||
WorkflowTrigger trigger4,
|
||||
WorkflowTrigger trigger5,
|
||||
WorkflowTrigger trigger6,
|
||||
[AutofixtureServiceProvider] IServiceProvider serviceProvider,
|
||||
[NoAutoProperties] WorkflowInstance workflowInstance)
|
||||
{
|
||||
Mock.Get(activityTypeService)
|
||||
.Setup(x => x.GetActivityTypesAsync(default))
|
||||
.Returns(ValueTask.FromResult<IEnumerable<ActivityType>>(new [] { activityType1, activityType2, activityType3 }));
|
||||
Mock.Get(startingActivitiesProvider)
|
||||
.Setup(x => x.GetStartActivities(workflowBlueprint1))
|
||||
.Returns(() => new [] { activityBlueprint1, activityBlueprint2 });
|
||||
Mock.Get(startingActivitiesProvider)
|
||||
.Setup(x => x.GetStartActivities(workflowBlueprint2))
|
||||
.Returns(() => new [] { activityBlueprint3 });
|
||||
Mock.Get(workflowExecutionContextFactory)
|
||||
.Setup(x => x.CreateWorkflowExecutionContextAsync(It.IsAny<IWorkflowBlueprint>(), default))
|
||||
.Returns((IWorkflowBlueprint bp, CancellationToken c) => Task.FromResult(new WorkflowExecutionContext(serviceProvider, bp, workflowInstance, default)));
|
||||
Mock.Get(triggerProvider)
|
||||
.Setup(x => x.GetTriggersForActivityBlueprintAsync(activityBlueprint1, It.IsAny<WorkflowExecutionContext>(), It.IsAny<IDictionary<string,ActivityType>>(), default))
|
||||
.Returns(() => Task.FromResult<IEnumerable<WorkflowTrigger>>(new [] { trigger1, trigger2 }));
|
||||
Mock.Get(triggerProvider)
|
||||
.Setup(x => x.GetTriggersForActivityBlueprintAsync(activityBlueprint2, It.IsAny<WorkflowExecutionContext>(), It.IsAny<IDictionary<string,ActivityType>>(), default))
|
||||
.Returns(() => Task.FromResult<IEnumerable<WorkflowTrigger>>(new [] { trigger3, trigger4 }));
|
||||
Mock.Get(triggerProvider)
|
||||
.Setup(x => x.GetTriggersForActivityBlueprintAsync(activityBlueprint3, It.IsAny<WorkflowExecutionContext>(), It.IsAny<IDictionary<string,ActivityType>>(), default))
|
||||
.Returns(() => Task.FromResult<IEnumerable<WorkflowTrigger>>(new [] { trigger5, trigger6 }));
|
||||
|
||||
var results = await sut.GetTriggersAsync(new [] { workflowBlueprint1, workflowBlueprint2 });
|
||||
|
||||
Assert.Equal(new [] { trigger1, trigger2, trigger3, trigger4, trigger5, trigger6 },
|
||||
results);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue