diff --git a/Elsa.sln b/Elsa.sln
index 0d68b277f..35db33303 100644
--- a/Elsa.sln
+++ b/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}
diff --git a/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowDefinitionStorePopulator.cs b/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowDefinitionStorePopulator.cs
index 193b7a4e7..3dc97a6be 100644
--- a/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowDefinitionStorePopulator.cs
+++ b/src/modules/Elsa.Workflows.Runtime/Services/DefaultWorkflowDefinitionStorePopulator.cs
@@ -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);
diff --git a/test/unit/Elsa.Workflows.Runtime.UnitTests/Elsa.Workflows.Runtime.UnitTests.csproj b/test/unit/Elsa.Workflows.Runtime.UnitTests/Elsa.Workflows.Runtime.UnitTests.csproj
new file mode 100644
index 000000000..4a25fd633
--- /dev/null
+++ b/test/unit/Elsa.Workflows.Runtime.UnitTests/Elsa.Workflows.Runtime.UnitTests.csproj
@@ -0,0 +1,15 @@
+
+
+
+ net8.0
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/unit/Elsa.Workflows.Runtime.UnitTests/Services/DefaultWorkflowDefinitionStorePopulatorTests.cs b/test/unit/Elsa.Workflows.Runtime.UnitTests/Services/DefaultWorkflowDefinitionStorePopulatorTests.cs
new file mode 100644
index 000000000..e943aa154
--- /dev/null
+++ b/test/unit/Elsa.Workflows.Runtime.UnitTests/Services/DefaultWorkflowDefinitionStorePopulatorTests.cs
@@ -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 _workflowDefinitionsInStore = new();
+
+ public DefaultWorkflowDefinitionStorePopulatorTests()
+ {
+ _storeMock = Substitute.For();
+ _storeMock.FindManyAsync(Arg.Any(), Arg.Any())
+ .Returns(_workflowDefinitionsInStore);
+ _populator = new DefaultWorkflowDefinitionStorePopulator(() => new List(),
+ Substitute.For(),
+ _storeMock,
+ Substitute.For(),
+ Substitute.For(),
+ Substitute.For(),
+ Substitute.For(),
+ Substitute.For>());
+ }
+
+ [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
+ {
+ new()
+ {
+ Name = "InputA"
+ }
+ }
+ });
+
+ var workflow = new MaterializedWorkflow(new Workflow
+ {
+ Identity = new WorkflowIdentity("a", 1, "1"),
+ Inputs = new List
+ {
+ new()
+ {
+ Name = "A"
+ }
+ }
+ }, "Test", "Test");
+
+ await _populator.AddAsync(workflow);
+
+ await _storeMock.DidNotReceive().SaveManyAsync(Arg.Any>(), Arg.Any());
+ }
+
+
+ [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>(), Arg.Any());
+ }
+
+ [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>(l =>
+ l.Any(wd => wd.Id == "2")), Arg.Any());
+
+ 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(), Arg.Any())
+ .Returns(new List
+ {
+ 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>(l =>
+ l.Any(wd => wd.Id == "2")), Arg.Any());
+ await _storeMock.Received().SaveManyAsync(Arg.Is>(l =>
+ l.Any(wd => wd.Id == "1" && wd.IsPublished == false)), Arg.Any());
+ }
+
+ private async Task CheckIfWorkflowWasSaved(WorkflowIdentity identity, Func predicate)
+ {
+ await _storeMock.Received(1).SaveManyAsync(Arg.Is>(l =>
+ l.Where(wd => wd.Id == identity.Id
+ && wd.DefinitionId == identity.DefinitionId
+ && wd.Version == identity.Version).Any(predicate)), Arg.Any());
+ }
+
+ private async Task CheckIfWorkflowWasSaved(Func predicate)
+ {
+ await _storeMock.Received(1).SaveManyAsync(Arg.Is>(l =>
+ l.Any(predicate)), Arg.Any());
+ }
+
+ private async Task CheckAmountOfWorkflowsSaved(int count)
+ {
+ await _storeMock.Received(count)
+ .SaveManyAsync(Arg.Any>(), Arg.Any());
+ }
+}
\ No newline at end of file
diff --git a/test/unit/Elsa.Workflows.Runtime.UnitTests/Usings.cs b/test/unit/Elsa.Workflows.Runtime.UnitTests/Usings.cs
new file mode 100644
index 000000000..8c927eb74
--- /dev/null
+++ b/test/unit/Elsa.Workflows.Runtime.UnitTests/Usings.cs
@@ -0,0 +1 @@
+global using Xunit;
\ No newline at end of file
diff --git a/test/unit/Elsa.Workflows.Runtime.UnitTests1/Services/DefaultWorkflowDefinitionStorePopulatorTests.cs b/test/unit/Elsa.Workflows.Runtime.UnitTests1/Services/DefaultWorkflowDefinitionStorePopulatorTests.cs
new file mode 100644
index 000000000..e943aa154
--- /dev/null
+++ b/test/unit/Elsa.Workflows.Runtime.UnitTests1/Services/DefaultWorkflowDefinitionStorePopulatorTests.cs
@@ -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 _workflowDefinitionsInStore = new();
+
+ public DefaultWorkflowDefinitionStorePopulatorTests()
+ {
+ _storeMock = Substitute.For();
+ _storeMock.FindManyAsync(Arg.Any(), Arg.Any())
+ .Returns(_workflowDefinitionsInStore);
+ _populator = new DefaultWorkflowDefinitionStorePopulator(() => new List(),
+ Substitute.For(),
+ _storeMock,
+ Substitute.For(),
+ Substitute.For(),
+ Substitute.For(),
+ Substitute.For(),
+ Substitute.For>());
+ }
+
+ [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
+ {
+ new()
+ {
+ Name = "InputA"
+ }
+ }
+ });
+
+ var workflow = new MaterializedWorkflow(new Workflow
+ {
+ Identity = new WorkflowIdentity("a", 1, "1"),
+ Inputs = new List
+ {
+ new()
+ {
+ Name = "A"
+ }
+ }
+ }, "Test", "Test");
+
+ await _populator.AddAsync(workflow);
+
+ await _storeMock.DidNotReceive().SaveManyAsync(Arg.Any>(), Arg.Any());
+ }
+
+
+ [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>(), Arg.Any());
+ }
+
+ [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>(l =>
+ l.Any(wd => wd.Id == "2")), Arg.Any());
+
+ 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(), Arg.Any())
+ .Returns(new List
+ {
+ 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>(l =>
+ l.Any(wd => wd.Id == "2")), Arg.Any());
+ await _storeMock.Received().SaveManyAsync(Arg.Is>(l =>
+ l.Any(wd => wd.Id == "1" && wd.IsPublished == false)), Arg.Any());
+ }
+
+ private async Task CheckIfWorkflowWasSaved(WorkflowIdentity identity, Func predicate)
+ {
+ await _storeMock.Received(1).SaveManyAsync(Arg.Is>(l =>
+ l.Where(wd => wd.Id == identity.Id
+ && wd.DefinitionId == identity.DefinitionId
+ && wd.Version == identity.Version).Any(predicate)), Arg.Any());
+ }
+
+ private async Task CheckIfWorkflowWasSaved(Func predicate)
+ {
+ await _storeMock.Received(1).SaveManyAsync(Arg.Is>(l =>
+ l.Any(predicate)), Arg.Any());
+ }
+
+ private async Task CheckAmountOfWorkflowsSaved(int count)
+ {
+ await _storeMock.Received(count)
+ .SaveManyAsync(Arg.Any>(), Arg.Any());
+ }
+}
\ No newline at end of file