Import publish execute integration test (#3872)
* Import publish execute integration test * Update test * Extracted import logic into WorkflowDefinitionImporter * Removed the WorkflowDefinitionRequest model and done a model rename --------- Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
This commit is contained in:
parent
ef1ec1ed08
commit
45f27f91f5
3
src/modules/Elsa.Workflows.Api/AssemblyInfo.cs
Normal file
3
src/modules/Elsa.Workflows.Api/AssemblyInfo.cs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Elsa.IntegrationTests")]
|
||||
|
|
@ -1,11 +1,6 @@
|
|||
using System.Text.Json;
|
||||
using Elsa.Abstractions;
|
||||
using Elsa.Common.Models;
|
||||
using Elsa.Workflows.Api.Models;
|
||||
using Elsa.Workflows.Core.Serialization;
|
||||
using Elsa.Workflows.Management.Contracts;
|
||||
using Elsa.Workflows.Management.Mappers;
|
||||
using Elsa.Workflows.Management.Materializers;
|
||||
using Elsa.Workflows.Management.Models;
|
||||
using Elsa.Workflows.Runtime.Contracts;
|
||||
|
||||
|
|
@ -14,24 +9,18 @@ namespace Elsa.Workflows.Api.Endpoints.WorkflowDefinitions.Import;
|
|||
/// <summary>
|
||||
/// Imports a JSON file containing a workflow definition.
|
||||
/// </summary>
|
||||
internal class Import : ElsaEndpoint<WorkflowDefinitionRequest, WorkflowDefinitionResponse>
|
||||
internal class Import : ElsaEndpoint<SaveWorkflowDefinitionRequest, WorkflowDefinitionResponse>
|
||||
{
|
||||
private readonly SerializerOptionsProvider _serializerOptionsProvider;
|
||||
private readonly IWorkflowDefinitionPublisher _workflowDefinitionPublisher;
|
||||
private readonly IWorkflowDefinitionService _workflowDefinitionService;
|
||||
private readonly VariableDefinitionMapper _variableDefinitionMapper;
|
||||
private readonly IWorkflowDefinitionImporter _workflowDefinitionImporter;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Import(
|
||||
SerializerOptionsProvider serializerOptionsProvider,
|
||||
IWorkflowDefinitionPublisher workflowDefinitionPublisher,
|
||||
IWorkflowDefinitionService workflowDefinitionService,
|
||||
VariableDefinitionMapper variableDefinitionMapper)
|
||||
IWorkflowDefinitionImporter workflowDefinitionImporter)
|
||||
{
|
||||
_serializerOptionsProvider = serializerOptionsProvider;
|
||||
_workflowDefinitionPublisher = workflowDefinitionPublisher;
|
||||
_workflowDefinitionService = workflowDefinitionService;
|
||||
_variableDefinitionMapper = variableDefinitionMapper;
|
||||
_workflowDefinitionImporter = workflowDefinitionImporter;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -43,44 +32,13 @@ internal class Import : ElsaEndpoint<WorkflowDefinitionRequest, WorkflowDefiniti
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task HandleAsync(WorkflowDefinitionRequest request, CancellationToken cancellationToken)
|
||||
public override async Task HandleAsync(SaveWorkflowDefinitionRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
var definitionId = request.DefinitionId;
|
||||
var isNew = string.IsNullOrWhiteSpace(definitionId);
|
||||
|
||||
// Get a workflow draft version.
|
||||
var draft = !string.IsNullOrWhiteSpace(definitionId)
|
||||
? await _workflowDefinitionPublisher.GetDraftAsync(definitionId, VersionOptions.Latest, cancellationToken)
|
||||
: default;
|
||||
|
||||
var isNew = draft == null;
|
||||
|
||||
// Create a new workflow in case no existing definition was found.
|
||||
if (isNew)
|
||||
{
|
||||
draft = _workflowDefinitionPublisher.New();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(definitionId))
|
||||
draft.DefinitionId = definitionId;
|
||||
}
|
||||
|
||||
// Update the draft with the received model.
|
||||
var root = request.Root;
|
||||
var serializerOptions = _serializerOptionsProvider.CreateApiOptions();
|
||||
var stringData = JsonSerializer.Serialize(root, serializerOptions);
|
||||
var variables = _variableDefinitionMapper.Map(request.Variables).ToList();
|
||||
|
||||
draft!.StringData = stringData;
|
||||
draft.MaterializerName = JsonWorkflowMaterializer.MaterializerName;
|
||||
draft.Name = request.Name?.Trim();
|
||||
draft.Description = request.Description?.Trim();
|
||||
draft.CustomProperties = request.CustomProperties ?? new Dictionary<string, object>();
|
||||
draft.Variables = variables;
|
||||
draft.Inputs = request.Inputs ?? new List<InputDefinition>();
|
||||
draft.Outputs = request.Outputs ?? new List<OutputDefinition>();
|
||||
draft.Outcomes = request.Outcomes ?? new List<string>();
|
||||
draft.Options = request.Options;
|
||||
draft.UsableAsActivity = request.UsableAsActivity;
|
||||
draft = request.Publish ? await _workflowDefinitionPublisher.PublishAsync(draft, cancellationToken) : await _workflowDefinitionPublisher.SaveDraftAsync(draft, cancellationToken);
|
||||
// Import workflow
|
||||
var draft = await _workflowDefinitionImporter.ImportAsync(request, cancellationToken);
|
||||
|
||||
// Materialize the workflow definition for serialization.
|
||||
var workflow = await _workflowDefinitionService.MaterializeWorkflowAsync(draft, cancellationToken);
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ using Microsoft.AspNetCore.Http;
|
|||
namespace Elsa.Workflows.Api.Endpoints.WorkflowDefinitions.Post;
|
||||
|
||||
[PublicAPI]
|
||||
internal class Post : ElsaEndpoint<WorkflowDefinitionRequest, WorkflowDefinitionResponse, WorkflowDefinitionMapper>
|
||||
internal class Post : ElsaEndpoint<SaveWorkflowDefinitionRequest, WorkflowDefinitionResponse, WorkflowDefinitionMapper>
|
||||
{
|
||||
private readonly SerializerOptionsProvider _serializerOptionsProvider;
|
||||
private readonly IWorkflowDefinitionPublisher _workflowDefinitionPublisher;
|
||||
|
|
@ -42,7 +42,7 @@ internal class Post : ElsaEndpoint<WorkflowDefinitionRequest, WorkflowDefinition
|
|||
ConfigurePermissions("write:workflow-definitions");
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(WorkflowDefinitionRequest request, CancellationToken cancellationToken)
|
||||
public override async Task HandleAsync(SaveWorkflowDefinitionRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
var definitionId = request.DefinitionId;
|
||||
var resourceName = $"{GetType().FullName}:{(!string.IsNullOrWhiteSpace(definitionId) ? definitionId : Guid.NewGuid().ToString())}";
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
using Elsa.Workflows.Core.Contracts;
|
||||
using Elsa.Workflows.Core.Models;
|
||||
using Elsa.Workflows.Management.Models;
|
||||
|
||||
namespace Elsa.Workflows.Api.Models;
|
||||
|
||||
internal class WorkflowDefinitionRequest
|
||||
{
|
||||
public string? DefinitionId { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public ICollection<VariableDefinition>? Variables { get; set; }
|
||||
public ICollection<InputDefinition>? Inputs { get; set; }
|
||||
public ICollection<OutputDefinition>? Outputs { get; set; }
|
||||
public ICollection<string>? Outcomes { get; set; }
|
||||
public IDictionary<string, object>? CustomProperties { get; set; }
|
||||
public IActivity? Root { get; set; }
|
||||
public WorkflowOptions? Options { get; set; }
|
||||
public bool Publish { get; set; }
|
||||
public bool? UsableAsActivity { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
using Elsa.Workflows.Management.Entities;
|
||||
using Elsa.Workflows.Management.Models;
|
||||
|
||||
namespace Elsa.Workflows.Management.Contracts
|
||||
{
|
||||
public interface IWorkflowDefinitionImporter
|
||||
{
|
||||
Task<WorkflowDefinition> ImportAsync(SaveWorkflowDefinitionRequest request, CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
||||
|
|
@ -103,7 +103,7 @@ public class WorkflowManagementFeature : FeatureBase
|
|||
/// Adds the specified variable type to the system.
|
||||
/// </summary>
|
||||
public WorkflowManagementFeature AddVariableType<T>(string category) => AddVariableType(typeof(T), category);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified variable type to the system.
|
||||
/// </summary>
|
||||
|
|
@ -138,6 +138,7 @@ public class WorkflowManagementFeature : FeatureBase
|
|||
.AddMemoryStore<WorkflowInstance, MemoryWorkflowInstanceStore>()
|
||||
.AddActivityProvider<TypedActivityProvider>()
|
||||
.AddSingleton<IWorkflowDefinitionPublisher, WorkflowDefinitionPublisher>()
|
||||
.AddSingleton<IWorkflowDefinitionImporter, WorkflowDefinitionImporter>()
|
||||
.AddSingleton<IWorkflowDefinitionManager, WorkflowDefinitionManager>()
|
||||
.AddSingleton<IActivityRegistryPopulator, ActivityRegistryPopulator>()
|
||||
.AddSingleton<IExpressionSyntaxRegistry, ExpressionSyntaxRegistry>()
|
||||
|
|
@ -151,7 +152,7 @@ public class WorkflowManagementFeature : FeatureBase
|
|||
.AddSingleton<SerializerOptionsProvider>()
|
||||
.AddSingleton<VariableDefinitionMapper>()
|
||||
;
|
||||
|
||||
|
||||
Services.AddNotificationHandlersFrom(GetType());
|
||||
|
||||
Services.Configure<ManagementOptions>(options =>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
using Elsa.Workflows.Core.Contracts;
|
||||
using Elsa.Workflows.Core.Models;
|
||||
|
||||
namespace Elsa.Workflows.Management.Models
|
||||
{
|
||||
public class SaveWorkflowDefinitionRequest
|
||||
{
|
||||
public string? DefinitionId { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public ICollection<VariableDefinition>? Variables { get; set; }
|
||||
public ICollection<InputDefinition>? Inputs { get; set; }
|
||||
public ICollection<OutputDefinition>? Outputs { get; set; }
|
||||
public ICollection<string>? Outcomes { get; set; }
|
||||
public IDictionary<string, object>? CustomProperties { get; set; }
|
||||
public IActivity? Root { get; set; }
|
||||
public WorkflowOptions? Options { get; set; }
|
||||
public bool Publish { get; set; }
|
||||
public bool? UsableAsActivity { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
using Elsa.Common.Models;
|
||||
using Elsa.Workflows.Core.Serialization;
|
||||
using Elsa.Workflows.Management.Contracts;
|
||||
using Elsa.Workflows.Management.Entities;
|
||||
using Elsa.Workflows.Management.Mappers;
|
||||
using Elsa.Workflows.Management.Materializers;
|
||||
using Elsa.Workflows.Management.Models;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Elsa.Workflows.Management.Services
|
||||
{
|
||||
public class WorkflowDefinitionImporter : IWorkflowDefinitionImporter
|
||||
{
|
||||
private readonly SerializerOptionsProvider _serializerOptionsProvider;
|
||||
private readonly IWorkflowDefinitionPublisher _workflowDefinitionPublisher;
|
||||
private readonly VariableDefinitionMapper _variableDefinitionMapper;
|
||||
|
||||
public WorkflowDefinitionImporter(
|
||||
SerializerOptionsProvider serializerOptionsProvider,
|
||||
IWorkflowDefinitionPublisher workflowDefinitionPublisher,
|
||||
VariableDefinitionMapper variableDefinitionMapper)
|
||||
{
|
||||
_serializerOptionsProvider = serializerOptionsProvider;
|
||||
_workflowDefinitionPublisher = workflowDefinitionPublisher;
|
||||
_variableDefinitionMapper = variableDefinitionMapper;
|
||||
}
|
||||
|
||||
public async Task<WorkflowDefinition> ImportAsync(SaveWorkflowDefinitionRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var definitionId = request.DefinitionId;
|
||||
|
||||
// Get a workflow draft version.
|
||||
var draft = !string.IsNullOrWhiteSpace(definitionId)
|
||||
? await _workflowDefinitionPublisher.GetDraftAsync(definitionId, VersionOptions.Latest, cancellationToken)
|
||||
: default;
|
||||
|
||||
var isNew = draft == null;
|
||||
|
||||
// Create a new workflow in case no existing definition was found.
|
||||
if (isNew)
|
||||
{
|
||||
draft = _workflowDefinitionPublisher.New();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(definitionId))
|
||||
draft.DefinitionId = definitionId;
|
||||
}
|
||||
|
||||
// Update the draft with the received model.
|
||||
var root = request.Root;
|
||||
var serializerOptions = _serializerOptionsProvider.CreateApiOptions();
|
||||
var stringData = JsonSerializer.Serialize(root, serializerOptions);
|
||||
var variables = _variableDefinitionMapper.Map(request.Variables).ToList();
|
||||
|
||||
draft!.StringData = stringData;
|
||||
draft.MaterializerName = JsonWorkflowMaterializer.MaterializerName;
|
||||
draft.Name = request.Name?.Trim();
|
||||
draft.Description = request.Description?.Trim();
|
||||
draft.CustomProperties = request.CustomProperties ?? new Dictionary<string, object>();
|
||||
draft.Variables = variables;
|
||||
draft.Inputs = request.Inputs ?? new List<InputDefinition>();
|
||||
draft.Outputs = request.Outputs ?? new List<OutputDefinition>();
|
||||
draft.Outcomes = request.Outcomes ?? new List<string>();
|
||||
draft.Options = request.Options;
|
||||
draft.UsableAsActivity = request.UsableAsActivity;
|
||||
draft = request.Publish ? await _workflowDefinitionPublisher.PublishAsync(draft, cancellationToken) : await _workflowDefinitionPublisher.SaveDraftAsync(draft, cancellationToken);
|
||||
|
||||
return draft;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -23,7 +23,14 @@
|
|||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\common\Elsa.Testing.Shared\Elsa.Testing.Shared.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\modules\Elsa.JavaScript\Elsa.JavaScript.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\modules\Elsa.Workflows.Api\Elsa.Workflows.Api.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\modules\Elsa.Workflows.Runtime\Elsa.Workflows.Runtime.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Scenarios\ImportAndExecute\workflow.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
using Elsa.Common.Models;
|
||||
using Elsa.Expressions.Contracts;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Testing.Shared;
|
||||
using Elsa.Workflows.Api.Models;
|
||||
using Elsa.Workflows.Core.Activities;
|
||||
using Elsa.Workflows.Core.Activities.Flowchart.Activities;
|
||||
using Elsa.Workflows.Core.Contracts;
|
||||
using Elsa.Workflows.Core.Models;
|
||||
using Elsa.Workflows.Core.Serialization;
|
||||
using Elsa.Workflows.Management.Contracts;
|
||||
using Elsa.Workflows.Management.Models;
|
||||
using Elsa.Workflows.Runtime.Contracts;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Elsa.IntegrationTests.Scenarios.ImportAndExecute;
|
||||
|
||||
public class Tests
|
||||
{
|
||||
private readonly CapturingTextWriter _capturingTextWriter = new();
|
||||
private readonly IWorkflowDefinitionImporter _workflowDefinitionImporter;
|
||||
private readonly SerializerOptionsProvider _serializerOptionsProvider;
|
||||
private readonly IWorkflowRuntime _workflowRuntime;
|
||||
private readonly IActivityRegistry _activityRegistry;
|
||||
private readonly IExpressionSyntaxRegistry _expressionSyntaxRegistry;
|
||||
private readonly IEnumerable<IExpressionSyntaxProvider> _expressionSyntaxProviders;
|
||||
|
||||
public Tests(ITestOutputHelper testOutputHelper)
|
||||
{
|
||||
var services = new TestApplicationBuilder(testOutputHelper)
|
||||
.WithCapturingTextWriter(_capturingTextWriter)
|
||||
.ConfigureElsa(elsa => elsa.UseWorkflowsApi())
|
||||
.Build();
|
||||
_serializerOptionsProvider = services.GetRequiredService<SerializerOptionsProvider>();
|
||||
_workflowRuntime = services.GetRequiredService<IWorkflowRuntime>();
|
||||
_activityRegistry = services.GetRequiredService<IActivityRegistry>();
|
||||
_expressionSyntaxRegistry = services.GetRequiredService<IExpressionSyntaxRegistry>();
|
||||
_expressionSyntaxProviders = services.GetServices<IExpressionSyntaxProvider>();
|
||||
_workflowDefinitionImporter = services.GetRequiredService<IWorkflowDefinitionImporter>();
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "Workflow imported from file should execute successfully.")]
|
||||
public async Task Test1()
|
||||
{
|
||||
// Register activities.
|
||||
await _activityRegistry.RegisterAsync(typeof(Workflow));
|
||||
await _activityRegistry.RegisterAsync(typeof(Flowchart));
|
||||
await _activityRegistry.RegisterAsync(typeof(WriteLine));
|
||||
|
||||
// Register expression syntaxes.
|
||||
foreach (var syntaxProvider in _expressionSyntaxProviders)
|
||||
{
|
||||
var syntaxes = await syntaxProvider.GetDescriptorsAsync();
|
||||
_expressionSyntaxRegistry.AddMany(syntaxes);
|
||||
}
|
||||
|
||||
// Import and publish workflow.
|
||||
var fileName = @"Scenarios/ImportAndExecute/workflow.json";
|
||||
await using var openStream = File.OpenRead(fileName);
|
||||
var options = _serializerOptionsProvider.CreateApiOptions();
|
||||
var workflowDefinitionRequest = (await JsonSerializer.DeserializeAsync<SaveWorkflowDefinitionRequest>(openStream, options))!;
|
||||
|
||||
workflowDefinitionRequest.Publish = true;
|
||||
var workflowDefinition = await _workflowDefinitionImporter.ImportAsync(workflowDefinitionRequest);
|
||||
|
||||
// Execute.
|
||||
var startWorkflowOptions = new StartWorkflowRuntimeOptions(null, new Dictionary<string, object>(), VersionOptions.Published);
|
||||
await _workflowRuntime.StartWorkflowAsync(workflowDefinition.DefinitionId, startWorkflowOptions);
|
||||
|
||||
// Assert.
|
||||
var lines = _capturingTextWriter.Lines.ToList();
|
||||
|
||||
Assert.Equal(new[] { "Dummy Text" }, lines);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
{
|
||||
"id": "3a793167cf6948c899d2f40c3c05cf0b",
|
||||
"definitionId": "11e2b350dae8485498c16d1ce3843829",
|
||||
"name": "Dummy workflow",
|
||||
"createdAt": "2023-04-04T08:35:22.841043+00:00",
|
||||
"version": 1,
|
||||
"variables": [],
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"outcomes": [],
|
||||
"metadata": {},
|
||||
"isLatest": true,
|
||||
"isPublished": true,
|
||||
"root": {
|
||||
"type": "Elsa.Flowchart",
|
||||
"version": 1,
|
||||
"id": "Flowchart1",
|
||||
"metadata": {},
|
||||
"applicationProperties": {
|
||||
"NotFoundConnectionsKey": []
|
||||
},
|
||||
"start": "WriteLine1",
|
||||
"activities": [
|
||||
{
|
||||
"text": {
|
||||
"typeName": "String",
|
||||
"expression": {
|
||||
"type": "Literal",
|
||||
"value": "Dummy Text"
|
||||
},
|
||||
"memoryReference": {
|
||||
"id": "WriteLine1:input-1"
|
||||
}
|
||||
},
|
||||
"id": "WriteLine1",
|
||||
"type": "Elsa.WriteLine",
|
||||
"version": 1,
|
||||
"canStartWorkflow": false,
|
||||
"runAsynchronously": false,
|
||||
"customProperties": {},
|
||||
"source": null,
|
||||
"line": null,
|
||||
"metadata": {
|
||||
"designer": {
|
||||
"position": {
|
||||
"x": 2256,
|
||||
"y": 2462
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": []
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue