Made loading workflows more robust
This commit is contained in:
parent
b8cb2caf78
commit
c1d0e7a0a2
7
Elsa.sln
7
Elsa.sln
|
|
@ -322,6 +322,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Hosting.Management", "
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.KeyValues", "src\modules\Elsa.KeyValues\Elsa.KeyValues.csproj", "{73BBB1E3-AEEC-44E8-83D7-6DD9CD687FE3}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Workflows.Runtime.UnitTests", "test\unit\Elsa.Workflows.Runtime.UnitTests\Elsa.Workflows.Runtime.UnitTests.csproj", "{B0946844-DE0E-4C59-9391-C0EB649DBF4E}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -786,6 +788,10 @@ Global
|
|||
{086A7E2A-6CE7-41DA-927B-C033638060B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{086A7E2A-6CE7-41DA-927B-C033638060B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{086A7E2A-6CE7-41DA-927B-C033638060B1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B0946844-DE0E-4C59-9391-C0EB649DBF4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B0946844-DE0E-4C59-9391-C0EB649DBF4E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B0946844-DE0E-4C59-9391-C0EB649DBF4E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B0946844-DE0E-4C59-9391-C0EB649DBF4E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -925,6 +931,7 @@ Global
|
|||
{BBCE36D1-6767-4ED1-B3E8-84D2567A962A} = {A516931E-EDBB-4FC3-BB94-1BB824D5BC61}
|
||||
{73BBB1E3-AEEC-44E8-83D7-6DD9CD687FE3} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
|
||||
{086A7E2A-6CE7-41DA-927B-C033638060B1} = {56C2FFB8-EA54-45B5-A095-4A78142EB4B5}
|
||||
{B0946844-DE0E-4C59-9391-C0EB649DBF4E} = {18453B51-25EB-4317-A4B3-B10518252E92}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {D4B5CEAA-7D70-4FCB-A68E-B03FBE5E0E5E}
|
||||
|
|
|
|||
|
|
@ -92,6 +92,9 @@ public class DefaultWorkflowDefinitionStorePopulator : IWorkflowDefinitionStoreP
|
|||
var workflow = materializedWorkflow.Workflow;
|
||||
var definitionId = workflow.Identity.DefinitionId;
|
||||
|
||||
var existingWorkflowLatest = false;
|
||||
var existingWorkflowPublished = false;
|
||||
|
||||
// Serialize materializer context.
|
||||
var materializerContext = materializedWorkflow.MaterializerContext;
|
||||
var materializerContextJson = materializerContext != null ? _payloadSerializer.Serialize(materializerContext) : default;
|
||||
|
|
@ -114,47 +117,8 @@ public class DefaultWorkflowDefinitionStorePopulator : IWorkflowDefinitionStoreP
|
|||
if (existingDefinitionVersion != null)
|
||||
workflowDefinitionsToSave.Add(existingDefinitionVersion);
|
||||
|
||||
// If the workflow being added is configured to be the latest version, then we need to reset the current latest version.
|
||||
if (workflow.Publication.IsLatest)
|
||||
{
|
||||
// Reset current latest definitions.
|
||||
var filter = new WorkflowDefinitionFilter
|
||||
{
|
||||
DefinitionId = definitionId,
|
||||
VersionOptions = VersionOptions.Latest
|
||||
};
|
||||
var latestWorkflowDefinitions = (await _workflowDefinitionStore.FindManyAsync(filter, cancellationToken)).ToList();
|
||||
|
||||
// If the latest definitions contains definitions with the same ID then we need to replace them with the latest workflow definitions.
|
||||
SyncExistingCopies(latestWorkflowDefinitions, workflowDefinitionsToSave);
|
||||
|
||||
foreach (var latestWorkflowDefinition in latestWorkflowDefinitions)
|
||||
{
|
||||
latestWorkflowDefinition.IsLatest = false;
|
||||
workflowDefinitionsToSave.Add(latestWorkflowDefinition);
|
||||
}
|
||||
}
|
||||
|
||||
// If the workflow being added is configured to be the published version, then we need to reset the current published version.
|
||||
if (workflow.Publication.IsPublished)
|
||||
{
|
||||
// Reset current published definitions.
|
||||
var filter = new WorkflowDefinitionFilter
|
||||
{
|
||||
DefinitionId = definitionId,
|
||||
VersionOptions = VersionOptions.Published
|
||||
};
|
||||
var publishedWorkflowDefinitions = (await _workflowDefinitionStore.FindManyAsync(filter, cancellationToken)).ToList();
|
||||
|
||||
// If the published workflow definitions contains definitions with the same ID as definitions in the latest workflow definitions, then we need to replace them with the latest workflow definitions.
|
||||
SyncExistingCopies(publishedWorkflowDefinitions, workflowDefinitionsToSave);
|
||||
|
||||
foreach (var publishedWorkflowDefinition in publishedWorkflowDefinitions)
|
||||
{
|
||||
publishedWorkflowDefinition.IsPublished = false;
|
||||
workflowDefinitionsToSave.Add(publishedWorkflowDefinition);
|
||||
}
|
||||
}
|
||||
await UpdateIsLatest();
|
||||
await UpdateIsPublished();
|
||||
|
||||
var workflowDefinition = existingDefinitionVersion ?? new WorkflowDefinition
|
||||
{
|
||||
|
|
@ -166,8 +130,8 @@ public class DefaultWorkflowDefinitionStorePopulator : IWorkflowDefinitionStoreP
|
|||
workflowDefinition.Description = workflow.WorkflowMetadata.Description;
|
||||
workflowDefinition.Name = workflow.WorkflowMetadata.Name;
|
||||
workflowDefinition.ToolVersion = workflow.WorkflowMetadata.ToolVersion;
|
||||
workflowDefinition.IsLatest = workflow.Publication.IsLatest;
|
||||
workflowDefinition.IsPublished = workflow.Publication.IsPublished;
|
||||
workflowDefinition.IsLatest = !existingWorkflowLatest;
|
||||
workflowDefinition.IsPublished = !existingWorkflowPublished && workflow.Publication.IsPublished;
|
||||
workflowDefinition.IsReadonly = workflow.IsReadonly;
|
||||
workflowDefinition.IsSystem = workflow.IsSystem;
|
||||
workflowDefinition.CustomProperties = workflow.CustomProperties;
|
||||
|
|
@ -182,15 +146,15 @@ public class DefaultWorkflowDefinitionStorePopulator : IWorkflowDefinitionStoreP
|
|||
workflowDefinition.MaterializerContext = materializerContextJson;
|
||||
workflowDefinition.MaterializerName = materializedWorkflow.MaterializerName;
|
||||
|
||||
// Temporary measure to try and find the root cause of https://github.com/elsa-workflows/elsa-core/issues/5033
|
||||
if (existingDefinitionVersion is null
|
||||
&& workflowDefinitionsToSave.Any(w => w.Id == workflowDefinition.Id))
|
||||
_logger.LogError("Trying to create duplicate workflows with id {WorkflowDefinitionId}", workflowDefinition.Id);
|
||||
else
|
||||
{
|
||||
workflowDefinitionsToSave.Add(workflowDefinition);
|
||||
_logger.LogError("Trying to create a new workflow with existing id {workflowId}", workflowDefinition.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
workflowDefinitionsToSave.Add(workflowDefinition);
|
||||
|
||||
var duplicates = workflowDefinitionsToSave.GroupBy(wd => wd.Id)
|
||||
.Where(g => g.Count() > 1)
|
||||
.Select(g => g.Key)
|
||||
|
|
@ -202,6 +166,67 @@ public class DefaultWorkflowDefinitionStorePopulator : IWorkflowDefinitionStoreP
|
|||
}
|
||||
|
||||
await _workflowDefinitionStore.SaveManyAsync(workflowDefinitionsToSave, cancellationToken);
|
||||
return;
|
||||
|
||||
async Task UpdateIsLatest()
|
||||
{
|
||||
// Always try to update the IsLatest property based on the VersionNumber
|
||||
|
||||
// Reset current latest definitions.
|
||||
var filter = new WorkflowDefinitionFilter
|
||||
{
|
||||
DefinitionId = definitionId,
|
||||
VersionOptions = VersionOptions.Latest
|
||||
};
|
||||
var latestWorkflowDefinitions = (await _workflowDefinitionStore.FindManyAsync(filter, cancellationToken)).ToList();
|
||||
|
||||
// If the latest definitions contains definitions with the same ID then we need to replace them with the latest workflow definitions.
|
||||
SyncExistingCopies(latestWorkflowDefinitions, workflowDefinitionsToSave);
|
||||
|
||||
foreach (var latestWorkflowDefinition in latestWorkflowDefinitions)
|
||||
{
|
||||
if (latestWorkflowDefinition.Version > workflow.Identity.Version)
|
||||
{
|
||||
_logger.LogWarning("A more recent version of the workflow has been found, overwriting the IsLatest property on the workflow");
|
||||
existingWorkflowLatest = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
latestWorkflowDefinition.IsLatest = false;
|
||||
workflowDefinitionsToSave.Add(latestWorkflowDefinition);
|
||||
}
|
||||
}
|
||||
|
||||
async Task UpdateIsPublished()
|
||||
{
|
||||
// If the workflow being added is configured to be the published version, then we need to reset the current published version.
|
||||
if (workflow.Publication.IsPublished)
|
||||
{
|
||||
// Reset current published definitions.
|
||||
var filter = new WorkflowDefinitionFilter
|
||||
{
|
||||
DefinitionId = definitionId,
|
||||
VersionOptions = VersionOptions.Published
|
||||
};
|
||||
var publishedWorkflowDefinitions = (await _workflowDefinitionStore.FindManyAsync(filter, cancellationToken)).ToList();
|
||||
|
||||
// If the published workflow definitions contains definitions with the same ID as definitions in the latest workflow definitions, then we need to replace them with the latest workflow definitions.
|
||||
SyncExistingCopies(publishedWorkflowDefinitions, workflowDefinitionsToSave);
|
||||
|
||||
foreach (var publishedWorkflowDefinition in publishedWorkflowDefinitions)
|
||||
{
|
||||
if (publishedWorkflowDefinition.Version > workflow.Identity.Version)
|
||||
{
|
||||
_logger.LogWarning("A more recent version of the workflow has been found to be published, overwriting the IsPublished property on the workflow");
|
||||
existingWorkflowPublished = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
publishedWorkflowDefinition.IsPublished = false;
|
||||
workflowDefinitionsToSave.Add(publishedWorkflowDefinition);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task IndexTriggersAsync(MaterializedWorkflow workflow, CancellationToken cancellationToken) => await _triggerIndexer.IndexTriggersAsync(workflow.Workflow, cancellationToken);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\modules\Elsa.Workflows.Runtime\Elsa.Workflows.Runtime.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NSubstitute" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,234 @@
|
|||
using Elsa.Common.Contracts;
|
||||
using Elsa.Workflows.Activities;
|
||||
using Elsa.Workflows.Contracts;
|
||||
using Elsa.Workflows.Management.Contracts;
|
||||
using Elsa.Workflows.Management.Entities;
|
||||
using Elsa.Workflows.Management.Filters;
|
||||
using Elsa.Workflows.Models;
|
||||
using Elsa.Workflows.Runtime.Contracts;
|
||||
using Elsa.Workflows.Runtime.Models;
|
||||
using Elsa.Workflows.Runtime.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
|
||||
namespace Elsa.Workflows.Runtime.UnitTests.Services;
|
||||
|
||||
public class DefaultWorkflowDefinitionStorePopulatorTests
|
||||
{
|
||||
private readonly IWorkflowDefinitionStore _storeMock;
|
||||
private readonly DefaultWorkflowDefinitionStorePopulator _populator;
|
||||
private readonly List<WorkflowDefinition> _workflowDefinitionsInStore = new();
|
||||
|
||||
public DefaultWorkflowDefinitionStorePopulatorTests()
|
||||
{
|
||||
_storeMock = Substitute.For<IWorkflowDefinitionStore>();
|
||||
_storeMock.FindManyAsync(Arg.Any<WorkflowDefinitionFilter>(), Arg.Any<CancellationToken>())
|
||||
.Returns(_workflowDefinitionsInStore);
|
||||
_populator = new DefaultWorkflowDefinitionStorePopulator(() => new List<IWorkflowProvider>(),
|
||||
Substitute.For<ITriggerIndexer>(),
|
||||
_storeMock,
|
||||
Substitute.For<IActivitySerializer>(),
|
||||
Substitute.For<IPayloadSerializer>(),
|
||||
Substitute.For<ISystemClock>(),
|
||||
Substitute.For<IIdentityGraphService>(),
|
||||
Substitute.For<ILogger<DefaultWorkflowDefinitionStorePopulator>>());
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "When adding a new workflow it needs to be saved")]
|
||||
public async Task AddOrUpdateCoreAsync_NewWorkflowDefinition_AddsWorkflowDefinition()
|
||||
{
|
||||
var workflow = new MaterializedWorkflow(new Workflow
|
||||
{
|
||||
Identity = new WorkflowIdentity("a", 7, "1"),
|
||||
Publication = new WorkflowPublication(true, true)
|
||||
}, "Test", "Test");
|
||||
|
||||
await _populator.AddAsync(workflow);
|
||||
|
||||
await CheckAmountOfWorkflowsSaved(1);
|
||||
await CheckIfWorkflowWasSaved(workflow.Workflow.Identity, wd => wd.IsLatest);
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "When adding a workflow with the same version, ignore it")]
|
||||
public async Task AddOrUpdateCoreAsync_ExistingWorkflowDefinition_KeepsExistingWorkflow()
|
||||
{
|
||||
_workflowDefinitionsInStore.Add(new()
|
||||
{
|
||||
Id = "1",
|
||||
DefinitionId = "a",
|
||||
Version = 1,
|
||||
Inputs = new List<InputDefinition>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Name = "InputA"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var workflow = new MaterializedWorkflow(new Workflow
|
||||
{
|
||||
Identity = new WorkflowIdentity("a", 1, "1"),
|
||||
Inputs = new List<InputDefinition>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Name = "A"
|
||||
}
|
||||
}
|
||||
}, "Test", "Test");
|
||||
|
||||
await _populator.AddAsync(workflow);
|
||||
|
||||
await _storeMock.DidNotReceive().SaveManyAsync(Arg.Any<IEnumerable<WorkflowDefinition>>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
|
||||
[Theory(DisplayName = "When adding a newer workflow version, add it as latest and update the publication settings of older workflows")]
|
||||
[InlineData(false, false, false, true)]
|
||||
[InlineData(false, true, true, false)]
|
||||
[InlineData(true, true, true, false)]
|
||||
public async Task AddOrUpdateCoreAsync_NewWorkflowDefinitionVersion_AddsWorkflowDefinition(bool workflowAddedIsLatest, bool workflowAddedIsPublished, bool expectedPublishedStateAdded, bool expectedPublishedStateExisting)
|
||||
{
|
||||
_workflowDefinitionsInStore.Add(new()
|
||||
{
|
||||
Id = "1",
|
||||
DefinitionId = "a",
|
||||
Version = 1,
|
||||
IsLatest = true,
|
||||
IsPublished = true
|
||||
});
|
||||
var workflow = new MaterializedWorkflow(new Workflow
|
||||
{
|
||||
Identity = new WorkflowIdentity("a", 2, "2"),
|
||||
Publication = new WorkflowPublication(workflowAddedIsLatest, workflowAddedIsPublished)
|
||||
}, "Test", "Test");
|
||||
|
||||
await _populator.AddAsync(workflow);
|
||||
|
||||
await CheckIfWorkflowWasSaved(wd => wd is { Id: "1", IsLatest: false }
|
||||
&& wd.IsPublished == expectedPublishedStateExisting);
|
||||
await CheckIfWorkflowWasSaved(workflow.Workflow.Identity, wd => wd.IsLatest && wd.IsPublished == expectedPublishedStateAdded);
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "When adding a newer workflow version, ignore the root Version number")]
|
||||
public async Task AddOrUpdateCoreAsync_NewWorkflowDefinitionVersion_IgnoreRootVersion()
|
||||
{
|
||||
_workflowDefinitionsInStore.Add(new()
|
||||
{
|
||||
Id = "2",
|
||||
DefinitionId = "a",
|
||||
Version = 2,
|
||||
IsLatest = true,
|
||||
IsPublished = true
|
||||
});
|
||||
|
||||
var workflow = new MaterializedWorkflow(new Workflow
|
||||
{
|
||||
Identity = new WorkflowIdentity("a", 3, "1"),
|
||||
Version = 1,
|
||||
Publication = new WorkflowPublication(true, true)
|
||||
}, "Test", "Test");
|
||||
|
||||
await _populator.AddAsync(workflow);
|
||||
|
||||
await CheckIfWorkflowWasSaved(wd => wd is { Id: "2", IsLatest: false, IsPublished: false });
|
||||
await CheckIfWorkflowWasSaved(workflow.Workflow.Identity, wd => wd is { IsLatest: true, IsPublished: true });
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "When adding a workflow version with the same ID, ignore it")]
|
||||
public async Task AddOrUpdateCoreAsync_NewWorkflowDefinitionVersionWithSameId_IsIgnored()
|
||||
{
|
||||
_workflowDefinitionsInStore.Add(
|
||||
new()
|
||||
{
|
||||
Id = "1",
|
||||
DefinitionId = "a",
|
||||
Version = 1,
|
||||
});
|
||||
|
||||
var workflow = new MaterializedWorkflow(new Workflow
|
||||
{
|
||||
Identity = new WorkflowIdentity("a", 2, "1")
|
||||
}, "Test", "Test");
|
||||
|
||||
await _populator.AddAsync(workflow);
|
||||
|
||||
await _storeMock.DidNotReceive()
|
||||
.SaveManyAsync(Arg.Any<IEnumerable<WorkflowDefinition>>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "When adding a older workflow version, keep the current latest as latest")]
|
||||
public async Task AddOrUpdateCoreAsync_OlderVersionAsLatest_ExistingVersionShouldRemainLatest()
|
||||
{
|
||||
_workflowDefinitionsInStore.Add(
|
||||
new()
|
||||
{
|
||||
Id = "2",
|
||||
DefinitionId = "a",
|
||||
Version = 2,
|
||||
IsLatest = true
|
||||
});
|
||||
|
||||
var workflow = new MaterializedWorkflow(new Workflow
|
||||
{
|
||||
Identity = new WorkflowIdentity("a", 1, "1"),
|
||||
Publication = new WorkflowPublication(true, true)
|
||||
}, "Test", "Test");
|
||||
|
||||
await _populator.AddAsync(workflow);
|
||||
|
||||
await _storeMock.DidNotReceive().SaveManyAsync(Arg.Is<IEnumerable<WorkflowDefinition>>(l =>
|
||||
l.Any(wd => wd.Id == "2")), Arg.Any<CancellationToken>());
|
||||
|
||||
await CheckIfWorkflowWasSaved(workflow.Workflow.Identity, wd => wd.IsLatest == false);
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "When adding a newer workflow version, keep the current published version published")]
|
||||
public async Task AddOrUpdateCoreAsync_OlderVersionAsPublished_ShouldNotBeUpdated()
|
||||
{
|
||||
_storeMock.FindManyAsync(Arg.Any<WorkflowDefinitionFilter>(), Arg.Any<CancellationToken>())
|
||||
.Returns(new List<WorkflowDefinition>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Id = "2",
|
||||
DefinitionId = "a",
|
||||
Version = 2,
|
||||
IsPublished = true
|
||||
}
|
||||
});
|
||||
|
||||
var workflow = new MaterializedWorkflow(new Workflow
|
||||
{
|
||||
Identity = new WorkflowIdentity("a", 1, "1"),
|
||||
Publication = new WorkflowPublication(true, true)
|
||||
}, "Test", "Test");
|
||||
|
||||
await _populator.AddAsync(workflow);
|
||||
await _storeMock.DidNotReceive().SaveManyAsync(Arg.Is<IEnumerable<WorkflowDefinition>>(l =>
|
||||
l.Any(wd => wd.Id == "2")), Arg.Any<CancellationToken>());
|
||||
await _storeMock.Received().SaveManyAsync(Arg.Is<IEnumerable<WorkflowDefinition>>(l =>
|
||||
l.Any(wd => wd.Id == "1" && wd.IsPublished == false)), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
private async Task CheckIfWorkflowWasSaved(WorkflowIdentity identity, Func<WorkflowDefinition, bool> predicate)
|
||||
{
|
||||
await _storeMock.Received(1).SaveManyAsync(Arg.Is<IEnumerable<WorkflowDefinition>>(l =>
|
||||
l.Where(wd => wd.Id == identity.Id
|
||||
&& wd.DefinitionId == identity.DefinitionId
|
||||
&& wd.Version == identity.Version).Any(predicate)), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
private async Task CheckIfWorkflowWasSaved(Func<WorkflowDefinition, bool> predicate)
|
||||
{
|
||||
await _storeMock.Received(1).SaveManyAsync(Arg.Is<IEnumerable<WorkflowDefinition>>(l =>
|
||||
l.Any(predicate)), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
private async Task CheckAmountOfWorkflowsSaved(int count)
|
||||
{
|
||||
await _storeMock.Received(count)
|
||||
.SaveManyAsync(Arg.Any<IEnumerable<WorkflowDefinition>>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
}
|
||||
1
test/unit/Elsa.Workflows.Runtime.UnitTests/Usings.cs
Normal file
1
test/unit/Elsa.Workflows.Runtime.UnitTests/Usings.cs
Normal file
|
|
@ -0,0 +1 @@
|
|||
global using Xunit;
|
||||
|
|
@ -0,0 +1,234 @@
|
|||
using Elsa.Common.Contracts;
|
||||
using Elsa.Workflows.Activities;
|
||||
using Elsa.Workflows.Contracts;
|
||||
using Elsa.Workflows.Management.Contracts;
|
||||
using Elsa.Workflows.Management.Entities;
|
||||
using Elsa.Workflows.Management.Filters;
|
||||
using Elsa.Workflows.Models;
|
||||
using Elsa.Workflows.Runtime.Contracts;
|
||||
using Elsa.Workflows.Runtime.Models;
|
||||
using Elsa.Workflows.Runtime.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
|
||||
namespace Elsa.Workflows.Runtime.UnitTests.Services;
|
||||
|
||||
public class DefaultWorkflowDefinitionStorePopulatorTests
|
||||
{
|
||||
private readonly IWorkflowDefinitionStore _storeMock;
|
||||
private readonly DefaultWorkflowDefinitionStorePopulator _populator;
|
||||
private readonly List<WorkflowDefinition> _workflowDefinitionsInStore = new();
|
||||
|
||||
public DefaultWorkflowDefinitionStorePopulatorTests()
|
||||
{
|
||||
_storeMock = Substitute.For<IWorkflowDefinitionStore>();
|
||||
_storeMock.FindManyAsync(Arg.Any<WorkflowDefinitionFilter>(), Arg.Any<CancellationToken>())
|
||||
.Returns(_workflowDefinitionsInStore);
|
||||
_populator = new DefaultWorkflowDefinitionStorePopulator(() => new List<IWorkflowProvider>(),
|
||||
Substitute.For<ITriggerIndexer>(),
|
||||
_storeMock,
|
||||
Substitute.For<IActivitySerializer>(),
|
||||
Substitute.For<IPayloadSerializer>(),
|
||||
Substitute.For<ISystemClock>(),
|
||||
Substitute.For<IIdentityGraphService>(),
|
||||
Substitute.For<ILogger<DefaultWorkflowDefinitionStorePopulator>>());
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "When adding a new workflow it needs to be saved")]
|
||||
public async Task AddOrUpdateCoreAsync_NewWorkflowDefinition_AddsWorkflowDefinition()
|
||||
{
|
||||
var workflow = new MaterializedWorkflow(new Workflow
|
||||
{
|
||||
Identity = new WorkflowIdentity("a", 7, "1"),
|
||||
Publication = new WorkflowPublication(true, true)
|
||||
}, "Test", "Test");
|
||||
|
||||
await _populator.AddAsync(workflow);
|
||||
|
||||
await CheckAmountOfWorkflowsSaved(1);
|
||||
await CheckIfWorkflowWasSaved(workflow.Workflow.Identity, wd => wd.IsLatest);
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "When adding a workflow with the same version, ignore it")]
|
||||
public async Task AddOrUpdateCoreAsync_ExistingWorkflowDefinition_KeepsExistingWorkflow()
|
||||
{
|
||||
_workflowDefinitionsInStore.Add(new()
|
||||
{
|
||||
Id = "1",
|
||||
DefinitionId = "a",
|
||||
Version = 1,
|
||||
Inputs = new List<InputDefinition>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Name = "InputA"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var workflow = new MaterializedWorkflow(new Workflow
|
||||
{
|
||||
Identity = new WorkflowIdentity("a", 1, "1"),
|
||||
Inputs = new List<InputDefinition>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Name = "A"
|
||||
}
|
||||
}
|
||||
}, "Test", "Test");
|
||||
|
||||
await _populator.AddAsync(workflow);
|
||||
|
||||
await _storeMock.DidNotReceive().SaveManyAsync(Arg.Any<IEnumerable<WorkflowDefinition>>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
|
||||
[Theory(DisplayName = "When adding a newer workflow version, add it as latest and update the publication settings of older workflows")]
|
||||
[InlineData(false, false, false, true)]
|
||||
[InlineData(false, true, true, false)]
|
||||
[InlineData(true, true, true, false)]
|
||||
public async Task AddOrUpdateCoreAsync_NewWorkflowDefinitionVersion_AddsWorkflowDefinition(bool workflowAddedIsLatest, bool workflowAddedIsPublished, bool expectedPublishedStateAdded, bool expectedPublishedStateExisting)
|
||||
{
|
||||
_workflowDefinitionsInStore.Add(new()
|
||||
{
|
||||
Id = "1",
|
||||
DefinitionId = "a",
|
||||
Version = 1,
|
||||
IsLatest = true,
|
||||
IsPublished = true
|
||||
});
|
||||
var workflow = new MaterializedWorkflow(new Workflow
|
||||
{
|
||||
Identity = new WorkflowIdentity("a", 2, "2"),
|
||||
Publication = new WorkflowPublication(workflowAddedIsLatest, workflowAddedIsPublished)
|
||||
}, "Test", "Test");
|
||||
|
||||
await _populator.AddAsync(workflow);
|
||||
|
||||
await CheckIfWorkflowWasSaved(wd => wd is { Id: "1", IsLatest: false }
|
||||
&& wd.IsPublished == expectedPublishedStateExisting);
|
||||
await CheckIfWorkflowWasSaved(workflow.Workflow.Identity, wd => wd.IsLatest && wd.IsPublished == expectedPublishedStateAdded);
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "When adding a newer workflow version, ignore the root Version number")]
|
||||
public async Task AddOrUpdateCoreAsync_NewWorkflowDefinitionVersion_IgnoreRootVersion()
|
||||
{
|
||||
_workflowDefinitionsInStore.Add(new()
|
||||
{
|
||||
Id = "2",
|
||||
DefinitionId = "a",
|
||||
Version = 2,
|
||||
IsLatest = true,
|
||||
IsPublished = true
|
||||
});
|
||||
|
||||
var workflow = new MaterializedWorkflow(new Workflow
|
||||
{
|
||||
Identity = new WorkflowIdentity("a", 3, "1"),
|
||||
Version = 1,
|
||||
Publication = new WorkflowPublication(true, true)
|
||||
}, "Test", "Test");
|
||||
|
||||
await _populator.AddAsync(workflow);
|
||||
|
||||
await CheckIfWorkflowWasSaved(wd => wd is { Id: "2", IsLatest: false, IsPublished: false });
|
||||
await CheckIfWorkflowWasSaved(workflow.Workflow.Identity, wd => wd is { IsLatest: true, IsPublished: true });
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "When adding a workflow version with the same ID, ignore it")]
|
||||
public async Task AddOrUpdateCoreAsync_NewWorkflowDefinitionVersionWithSameId_IsIgnored()
|
||||
{
|
||||
_workflowDefinitionsInStore.Add(
|
||||
new()
|
||||
{
|
||||
Id = "1",
|
||||
DefinitionId = "a",
|
||||
Version = 1,
|
||||
});
|
||||
|
||||
var workflow = new MaterializedWorkflow(new Workflow
|
||||
{
|
||||
Identity = new WorkflowIdentity("a", 2, "1")
|
||||
}, "Test", "Test");
|
||||
|
||||
await _populator.AddAsync(workflow);
|
||||
|
||||
await _storeMock.DidNotReceive()
|
||||
.SaveManyAsync(Arg.Any<IEnumerable<WorkflowDefinition>>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "When adding a older workflow version, keep the current latest as latest")]
|
||||
public async Task AddOrUpdateCoreAsync_OlderVersionAsLatest_ExistingVersionShouldRemainLatest()
|
||||
{
|
||||
_workflowDefinitionsInStore.Add(
|
||||
new()
|
||||
{
|
||||
Id = "2",
|
||||
DefinitionId = "a",
|
||||
Version = 2,
|
||||
IsLatest = true
|
||||
});
|
||||
|
||||
var workflow = new MaterializedWorkflow(new Workflow
|
||||
{
|
||||
Identity = new WorkflowIdentity("a", 1, "1"),
|
||||
Publication = new WorkflowPublication(true, true)
|
||||
}, "Test", "Test");
|
||||
|
||||
await _populator.AddAsync(workflow);
|
||||
|
||||
await _storeMock.DidNotReceive().SaveManyAsync(Arg.Is<IEnumerable<WorkflowDefinition>>(l =>
|
||||
l.Any(wd => wd.Id == "2")), Arg.Any<CancellationToken>());
|
||||
|
||||
await CheckIfWorkflowWasSaved(workflow.Workflow.Identity, wd => wd.IsLatest == false);
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "When adding a newer workflow version, keep the current published version published")]
|
||||
public async Task AddOrUpdateCoreAsync_OlderVersionAsPublished_ShouldNotBeUpdated()
|
||||
{
|
||||
_storeMock.FindManyAsync(Arg.Any<WorkflowDefinitionFilter>(), Arg.Any<CancellationToken>())
|
||||
.Returns(new List<WorkflowDefinition>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Id = "2",
|
||||
DefinitionId = "a",
|
||||
Version = 2,
|
||||
IsPublished = true
|
||||
}
|
||||
});
|
||||
|
||||
var workflow = new MaterializedWorkflow(new Workflow
|
||||
{
|
||||
Identity = new WorkflowIdentity("a", 1, "1"),
|
||||
Publication = new WorkflowPublication(true, true)
|
||||
}, "Test", "Test");
|
||||
|
||||
await _populator.AddAsync(workflow);
|
||||
await _storeMock.DidNotReceive().SaveManyAsync(Arg.Is<IEnumerable<WorkflowDefinition>>(l =>
|
||||
l.Any(wd => wd.Id == "2")), Arg.Any<CancellationToken>());
|
||||
await _storeMock.Received().SaveManyAsync(Arg.Is<IEnumerable<WorkflowDefinition>>(l =>
|
||||
l.Any(wd => wd.Id == "1" && wd.IsPublished == false)), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
private async Task CheckIfWorkflowWasSaved(WorkflowIdentity identity, Func<WorkflowDefinition, bool> predicate)
|
||||
{
|
||||
await _storeMock.Received(1).SaveManyAsync(Arg.Is<IEnumerable<WorkflowDefinition>>(l =>
|
||||
l.Where(wd => wd.Id == identity.Id
|
||||
&& wd.DefinitionId == identity.DefinitionId
|
||||
&& wd.Version == identity.Version).Any(predicate)), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
private async Task CheckIfWorkflowWasSaved(Func<WorkflowDefinition, bool> predicate)
|
||||
{
|
||||
await _storeMock.Received(1).SaveManyAsync(Arg.Is<IEnumerable<WorkflowDefinition>>(l =>
|
||||
l.Any(predicate)), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
private async Task CheckAmountOfWorkflowsSaved(int count)
|
||||
{
|
||||
await _storeMock.Received(count)
|
||||
.SaveManyAsync(Arg.Any<IEnumerable<WorkflowDefinition>>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue