Add project files.
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\modules\Elsa.Common\Elsa.Common.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,29 @@
|
||||
using Elsa.Extensions;
|
||||
using Xunit;
|
||||
|
||||
namespace Elsa.Common.Core;
|
||||
|
||||
public class EnumerableExtensionsTests
|
||||
{
|
||||
[Fact]
|
||||
public void ToBatches_ReturnsItemsInListOfPages()
|
||||
{
|
||||
var items = new List<int>
|
||||
{
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5
|
||||
};
|
||||
var batchSize = 2;
|
||||
var batches = items.ToBatches(batchSize).ToList();
|
||||
|
||||
Assert.Equal(3, batches.Count);
|
||||
Assert.Equal(2, batches.ElementAt(0).Count());
|
||||
Assert.Equal(2, batches.ElementAt(1).Count());
|
||||
Assert.Equal(1, batches.ElementAt(2).Count());
|
||||
Assert.Equal(3, batches.ElementAt(1).ElementAt(0));
|
||||
Assert.Equal(4, batches.ElementAt(1).ElementAt(1));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\modules\Elsa.MongoDb\Elsa.MongoDb.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,38 @@
|
||||
using Elsa.Common.Multitenancy;
|
||||
using Elsa.KeyValues.Entities;
|
||||
using Elsa.MongoDb.Common;
|
||||
using Elsa.MongoDb.Modules.Runtime;
|
||||
using MongoDB.Driver;
|
||||
using NSubstitute;
|
||||
|
||||
namespace Elsa.MongoDb.UnitTests;
|
||||
|
||||
public class MongoKeyValueStoreTests
|
||||
{
|
||||
private readonly MongoDbStore<SerializedKeyValuePair> _mongoDbStore;
|
||||
|
||||
public MongoKeyValueStoreTests()
|
||||
{
|
||||
var mongoCollectionMock = Substitute.For<IMongoCollection<SerializedKeyValuePair>>();
|
||||
var tenantAccessorMock = Substitute.For<ITenantAccessor>();
|
||||
mongoCollectionMock.FindOneAndReplaceAsync(
|
||||
Arg.Any<FilterDefinition<SerializedKeyValuePair>>(),
|
||||
Arg.Any<SerializedKeyValuePair>(),
|
||||
Arg.Any<FindOneAndReplaceOptions<SerializedKeyValuePair>>()
|
||||
)
|
||||
.Returns(new SerializedKeyValuePair());
|
||||
_mongoDbStore = new MongoDbStore<SerializedKeyValuePair>(mongoCollectionMock, tenantAccessorMock);
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "When saving a SerializedKeyValuePair document, don't throw an exception of missing ID property")]
|
||||
public async Task SaveAsync_WithSerializedKeyValuePairDocument_DoesNotThrowException()
|
||||
{
|
||||
var mongoKeyValueStore = new MongoKeyValueStore(_mongoDbStore);
|
||||
var keyValuePair = new SerializedKeyValuePair();
|
||||
|
||||
var exception = await Record.ExceptionAsync(
|
||||
async () => await mongoKeyValueStore.SaveAsync(keyValuePair, default));
|
||||
|
||||
Assert.Null(exception);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
global using Xunit;
|
||||
@@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\modules\Elsa.Workflows.Core\Elsa.Workflows.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,107 @@
|
||||
using Elsa.Expressions.Models;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Workflows.Memory;
|
||||
|
||||
namespace Elsa.Workflows.Core.UnitTests;
|
||||
|
||||
public class ExpressionExecutionContextExtensionsTests
|
||||
{
|
||||
[Fact]
|
||||
public void GetVariable_ReturnsVariable_WhenVariableExists()
|
||||
{
|
||||
// Arrange
|
||||
var variable = new Variable("test", 5);
|
||||
var memoryRegister = new MemoryRegister(new Dictionary<string, MemoryBlock>
|
||||
{
|
||||
{ variable.Id, new MemoryBlock(variable.Value, new VariableBlockMetadata(variable, typeof(object), true)) }
|
||||
});
|
||||
|
||||
var context = new ExpressionExecutionContext(null!, memoryRegister);
|
||||
|
||||
// Act
|
||||
var result = context.GetVariable<int>("test");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(5, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetVariable_ReturnsNull_WhenVariableDoesNotExist()
|
||||
{
|
||||
// Arrange
|
||||
var memoryRegister = new MemoryRegister(new Dictionary<string, MemoryBlock>());
|
||||
var context = new ExpressionExecutionContext(null!, memoryRegister);
|
||||
|
||||
// Act
|
||||
var result = context.GetVariable<string>("nonexistent");
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateVariable_ThrowsException_WhenVariableExists()
|
||||
{
|
||||
// Arrange
|
||||
var variable = new Variable("test", 5);
|
||||
var memoryRegister = new MemoryRegister(new Dictionary<string, MemoryBlock>
|
||||
{
|
||||
{ variable.Id, new MemoryBlock(variable.Value, new VariableBlockMetadata(variable, typeof(object), true)) }
|
||||
});
|
||||
|
||||
var context = new ExpressionExecutionContext(null!, memoryRegister);
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<Exception>(() => context.CreateVariable("test", 10));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateVariable_CreatesVariable_WhenVariableDoesNotExist()
|
||||
{
|
||||
// Arrange
|
||||
var memoryRegister = new MemoryRegister(new Dictionary<string, MemoryBlock>());
|
||||
var context = new ExpressionExecutionContext(null!, memoryRegister);
|
||||
|
||||
// Act
|
||||
context.CreateVariable("newVariable", 10);
|
||||
|
||||
// Assert
|
||||
var variable = context.GetVariable<int>("newVariable");
|
||||
Assert.Equal(10, variable);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetVariable_CreatesVariable_WhenVariableDoesNotExist()
|
||||
{
|
||||
// Arrange
|
||||
var memoryRegister = new MemoryRegister(new Dictionary<string, MemoryBlock>());
|
||||
var context = new ExpressionExecutionContext(null!, memoryRegister);
|
||||
|
||||
// Act
|
||||
context.CreateVariable("newVariable", 10);
|
||||
|
||||
// Assert
|
||||
var variable = context.GetVariable<int>("newVariable");
|
||||
Assert.Equal(10, variable);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetVariable_SetsValue_WhenVariableExists()
|
||||
{
|
||||
// Arrange
|
||||
var variable = new Variable("test", 5);
|
||||
var memoryRegister = new MemoryRegister(new Dictionary<string, MemoryBlock>
|
||||
{
|
||||
{ variable.Id, new MemoryBlock(variable.Value, new VariableBlockMetadata(variable, typeof(object), true)) }
|
||||
});
|
||||
|
||||
var context = new ExpressionExecutionContext(null!, memoryRegister);
|
||||
|
||||
// Act
|
||||
context.SetVariable("test", 10);
|
||||
|
||||
// Assert
|
||||
var updatedVariable = context.GetVariable<int>("test");
|
||||
Assert.Equal(10, updatedVariable);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using Elsa.Workflows.Activities.Flowchart.Models;
|
||||
|
||||
namespace Elsa.Workflows.Core.UnitTests.Flowchart;
|
||||
|
||||
public static class FlowGraphExtensions
|
||||
{
|
||||
public static void ValidateOutboundConnections(this FlowGraph flowGraph, List<string> expected, IActivity activity)
|
||||
{
|
||||
Assert.Equal(expected, flowGraph.GetOutboundConnections(activity).Select(c => c.ToString()));
|
||||
}
|
||||
|
||||
public static void ValidateForwardInboundConnections(this FlowGraph flowGraph, List<string> expected, IActivity activity)
|
||||
{
|
||||
Assert.Equal(expected, flowGraph.GetForwardInboundConnections(activity).Select(c => c.ToString()));
|
||||
}
|
||||
public static void ValidateBackwardConnection(this FlowGraph flowGraph, bool expectedBackward, bool expectedValid, Connection connection)
|
||||
{
|
||||
var actualBackward = flowGraph.IsBackwardConnection(connection, out var actualValid);
|
||||
Assert.Equal(expectedBackward, actualBackward);
|
||||
Assert.Equal(expectedValid, actualValid);
|
||||
}
|
||||
|
||||
public static void ValidateDanglingActivity(this FlowGraph flowGraph, bool expected, Activity activity)
|
||||
{
|
||||
Assert.Equal(expected, flowGraph.IsDanglingActivity(activity));
|
||||
}
|
||||
|
||||
public static void ValidateAncestorActivities(this FlowGraph flowGraph, List<Activity> expected, Activity activity)
|
||||
{
|
||||
Assert.Equal(expected, flowGraph.GetAncestorActivities(activity));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,386 @@
|
||||
using Elsa.Workflows.Activities;
|
||||
using Elsa.Workflows.Activities.Flowchart.Models;
|
||||
|
||||
namespace Elsa.Workflows.Core.UnitTests.Flowchart;
|
||||
public class FlowGraphTests
|
||||
{
|
||||
// Start
|
||||
// ↓
|
||||
// A ← W Y
|
||||
// ↙ ↘ ↙
|
||||
// B C ← X ← Z
|
||||
// ↘ ↙
|
||||
// D
|
||||
// ↓
|
||||
// End
|
||||
[Fact]
|
||||
public void InvalidDanglingActivitiesTest()
|
||||
{
|
||||
var start = new TestActivity("start");
|
||||
var a = new TestActivity("a");
|
||||
var b = new TestActivity("b");
|
||||
var c = new TestActivity("c");
|
||||
var d = new TestActivity("d");
|
||||
var end = new TestActivity("end");
|
||||
var w = new TestActivity("w");
|
||||
var x = new TestActivity("x");
|
||||
var y = new TestActivity("y");
|
||||
var z = new TestActivity("z");
|
||||
|
||||
var connections = new List<Connection>
|
||||
{
|
||||
new(start, a),
|
||||
new(a, b),
|
||||
new(a, c),
|
||||
new(b, d),
|
||||
new(c, d),
|
||||
new(d, end),
|
||||
new(w, a), // invalid dangling connector
|
||||
new(x, c), // invalid dangling connector
|
||||
new(y, x), // invalid dangling connector
|
||||
new(z, x), // invalid dangling connector
|
||||
};
|
||||
|
||||
var flowGraph = new FlowGraph(connections, start);
|
||||
|
||||
// FlowGraph.GetForwardInboundConnections
|
||||
flowGraph.ValidateForwardInboundConnections(["start->a"], a);
|
||||
flowGraph.ValidateForwardInboundConnections(["a->b"], b);
|
||||
flowGraph.ValidateForwardInboundConnections(["a->c"], c);
|
||||
flowGraph.ValidateForwardInboundConnections(["b->d", "c->d"], d);
|
||||
flowGraph.ValidateForwardInboundConnections(["d->end"], end);
|
||||
flowGraph.ValidateForwardInboundConnections([], x);
|
||||
|
||||
// FlowGraph.GetOutboundConnections
|
||||
flowGraph.ValidateOutboundConnections(["start->a"], start);
|
||||
flowGraph.ValidateOutboundConnections(["a->b", "a->c"], a);
|
||||
flowGraph.ValidateOutboundConnections(["b->d"], b);
|
||||
flowGraph.ValidateOutboundConnections(["c->d"], c);
|
||||
flowGraph.ValidateOutboundConnections(["d->end"], d);
|
||||
flowGraph.ValidateOutboundConnections([], end);
|
||||
flowGraph.ValidateOutboundConnections(["x->c"], x);
|
||||
|
||||
// FlowGraph.GetAncestorActivities
|
||||
flowGraph.ValidateAncestorActivities([], start);
|
||||
flowGraph.ValidateAncestorActivities([start], a);
|
||||
flowGraph.ValidateAncestorActivities([a, start], b);
|
||||
flowGraph.ValidateAncestorActivities([a, start], c);
|
||||
flowGraph.ValidateAncestorActivities([b, c, a, start], d);
|
||||
flowGraph.ValidateAncestorActivities([], x);
|
||||
|
||||
// FlowGraph IsDanglingActivity
|
||||
flowGraph.ValidateDanglingActivity(false, start);
|
||||
flowGraph.ValidateDanglingActivity(false, a);
|
||||
flowGraph.ValidateDanglingActivity(false, b);
|
||||
flowGraph.ValidateDanglingActivity(false, c);
|
||||
flowGraph.ValidateDanglingActivity(false, d);
|
||||
flowGraph.ValidateDanglingActivity(false, end);
|
||||
flowGraph.ValidateDanglingActivity(true, w);
|
||||
flowGraph.ValidateDanglingActivity(true, x);
|
||||
flowGraph.ValidateDanglingActivity(true, y);
|
||||
flowGraph.ValidateDanglingActivity(true, z);
|
||||
}
|
||||
|
||||
// Start
|
||||
// ↓
|
||||
// A ← ←
|
||||
// ↙ ↘ ↖
|
||||
// B C ↑
|
||||
// ↘ ↙ ↗
|
||||
// D → →
|
||||
[Fact]
|
||||
public void ValidBackwardConnectionTest()
|
||||
{
|
||||
var start = new TestActivity("start");
|
||||
var a = new TestActivity("a");
|
||||
var b = new TestActivity("b");
|
||||
var c = new TestActivity("c");
|
||||
var d = new TestActivity("d");
|
||||
|
||||
var connections = new List<Connection>
|
||||
{
|
||||
new(start, a),
|
||||
new(a, b),
|
||||
new(a, c),
|
||||
new(b, d),
|
||||
new(c, d),
|
||||
new(d, a), // loopback
|
||||
};
|
||||
|
||||
var flowGraph = new FlowGraph(connections, start);
|
||||
|
||||
// FlowGraph.GetForwardInboundConnections
|
||||
flowGraph.ValidateForwardInboundConnections([], start);
|
||||
flowGraph.ValidateForwardInboundConnections(["start->a"], a);
|
||||
flowGraph.ValidateForwardInboundConnections(["a->b"], b);
|
||||
flowGraph.ValidateForwardInboundConnections(["a->c"], c);
|
||||
flowGraph.ValidateForwardInboundConnections(["b->d", "c->d"], d);
|
||||
|
||||
// FlowGraph.GetOutboundConnections
|
||||
flowGraph.ValidateOutboundConnections(["start->a"], start);
|
||||
flowGraph.ValidateOutboundConnections(["a->b", "a->c"], a);
|
||||
flowGraph.ValidateOutboundConnections(["b->d"], b);
|
||||
flowGraph.ValidateOutboundConnections(["c->d"], c);
|
||||
flowGraph.ValidateOutboundConnections(["d->a"], d);
|
||||
|
||||
// FlowGraph.GetAncestorActivities
|
||||
flowGraph.ValidateAncestorActivities([], start);
|
||||
flowGraph.ValidateAncestorActivities([start], a);
|
||||
flowGraph.ValidateAncestorActivities([a, start], b);
|
||||
flowGraph.ValidateAncestorActivities([a, start], c);
|
||||
flowGraph.ValidateAncestorActivities([b, c, a, start], d);
|
||||
|
||||
// FlowGraph.IsBackwardConnection
|
||||
flowGraph.ValidateBackwardConnection(false, false, new(a, c));
|
||||
flowGraph.ValidateBackwardConnection(true, true, new(d, a));
|
||||
}
|
||||
|
||||
|
||||
// Start
|
||||
// ↙ ↘
|
||||
// A B ← ←
|
||||
// ↘ ↙ ↘ ↖
|
||||
// C D ↰ ↑
|
||||
// ↘ ↙ ↗ ↗
|
||||
// E → →
|
||||
[Fact]
|
||||
public void InvalidLoopbackTest()
|
||||
{
|
||||
var start = new TestActivity("start");
|
||||
var a = new TestActivity("a");
|
||||
var b = new TestActivity("b");
|
||||
var c = new TestActivity("c");
|
||||
var d = new TestActivity("d");
|
||||
var e = new TestActivity("e");
|
||||
|
||||
var connections = new List<Connection>
|
||||
{
|
||||
new(start, a),
|
||||
new(start, b),
|
||||
new(a, c),
|
||||
new(b, c),
|
||||
new(b, d),
|
||||
new(d, e),
|
||||
new(c, e),
|
||||
new(e, b), // invalid loopback
|
||||
new(e, d), // invalid loopback
|
||||
};
|
||||
|
||||
var flowGraph = new FlowGraph(connections, start);
|
||||
|
||||
// FlowGraph.GetForwardInboundConnections
|
||||
flowGraph.ValidateForwardInboundConnections([], start);
|
||||
flowGraph.ValidateForwardInboundConnections(["start->a"], a);
|
||||
flowGraph.ValidateForwardInboundConnections(["start->b"], b);
|
||||
flowGraph.ValidateForwardInboundConnections(["a->c", "b->c"], c);
|
||||
flowGraph.ValidateForwardInboundConnections(["b->d"], d);
|
||||
flowGraph.ValidateForwardInboundConnections(["c->e", "d->e"], e);
|
||||
|
||||
// FlowGraph.GetOutboundConnections
|
||||
flowGraph.ValidateOutboundConnections(["start->a", "start->b"], start);
|
||||
flowGraph.ValidateOutboundConnections(["a->c"], a);
|
||||
flowGraph.ValidateOutboundConnections(["b->c", "b->d"], b);
|
||||
flowGraph.ValidateOutboundConnections(["c->e"], c);
|
||||
flowGraph.ValidateOutboundConnections(["d->e"], d);
|
||||
flowGraph.ValidateOutboundConnections(["e->b", "e->d"], e);
|
||||
|
||||
// FlowGraph.GetAncestorActivities
|
||||
flowGraph.ValidateAncestorActivities([], start);
|
||||
flowGraph.ValidateAncestorActivities([start], a);
|
||||
flowGraph.ValidateAncestorActivities([start], b);
|
||||
flowGraph.ValidateAncestorActivities([a, b, start], c);
|
||||
flowGraph.ValidateAncestorActivities([b, start], d);
|
||||
flowGraph.ValidateAncestorActivities([c, d, a, b, start], e);
|
||||
|
||||
// FlowGraph.IsBackwardConnection
|
||||
flowGraph.ValidateBackwardConnection(true, false, new(e, b));
|
||||
flowGraph.ValidateBackwardConnection(true, false, new(e, d));
|
||||
}
|
||||
|
||||
// Start
|
||||
// ↙ ↘
|
||||
// ↓ A
|
||||
// ↓ ↓
|
||||
// ↓ B
|
||||
// ↓ ↙ ↓
|
||||
// C D
|
||||
// ↘ ↙
|
||||
// End
|
||||
[Fact]
|
||||
public void LongLegTest()
|
||||
{
|
||||
var start = new TestActivity("start");
|
||||
var a = new TestActivity("a");
|
||||
var b = new TestActivity("b");
|
||||
var c = new TestActivity("c");
|
||||
var d = new TestActivity("d");
|
||||
var end = new TestActivity("end");
|
||||
|
||||
var connections = new List<Connection>
|
||||
{
|
||||
new(start, a),
|
||||
new(start, c),
|
||||
new(a, b),
|
||||
new(b, c),
|
||||
new(b, d),
|
||||
new(c, end),
|
||||
new(d, end),
|
||||
};
|
||||
|
||||
var flowGraph = new FlowGraph(connections, start);
|
||||
|
||||
// FlowGraph.GetForwardInboundConnections
|
||||
flowGraph.ValidateForwardInboundConnections([], start);
|
||||
flowGraph.ValidateForwardInboundConnections(["start->a"], a);
|
||||
flowGraph.ValidateForwardInboundConnections(["a->b"], b);
|
||||
flowGraph.ValidateForwardInboundConnections(["start->c", "b->c"], c);
|
||||
flowGraph.ValidateForwardInboundConnections(["b->d"], d);
|
||||
flowGraph.ValidateForwardInboundConnections(["c->end", "d->end"], end);
|
||||
|
||||
// FlowGraph.GetOutboundConnections
|
||||
flowGraph.ValidateOutboundConnections(["start->a", "start->c"], start);
|
||||
flowGraph.ValidateOutboundConnections(["a->b"], a);
|
||||
flowGraph.ValidateOutboundConnections(["b->c", "b->d"], b);
|
||||
flowGraph.ValidateOutboundConnections(["c->end"], c);
|
||||
flowGraph.ValidateOutboundConnections(["d->end"], d);
|
||||
flowGraph.ValidateOutboundConnections([], end);
|
||||
|
||||
// FlowGraph.GetAncestorActivities
|
||||
flowGraph.ValidateAncestorActivities([], start);
|
||||
flowGraph.ValidateAncestorActivities([start], a);
|
||||
flowGraph.ValidateAncestorActivities([a, start], b);
|
||||
flowGraph.ValidateAncestorActivities([start, b, a], c);
|
||||
flowGraph.ValidateAncestorActivities([b, a, start], d);
|
||||
flowGraph.ValidateAncestorActivities([c, d, start, b, a], end);
|
||||
}
|
||||
|
||||
// Start
|
||||
// ↙ ↘
|
||||
// A B
|
||||
// ↓↘ ↙ ↘
|
||||
// ↳→C D
|
||||
// ↓↘ ↙
|
||||
// ↳→E
|
||||
[Fact]
|
||||
public void SameEdgeDuplicateTest()
|
||||
{
|
||||
var start = new TestActivity("start");
|
||||
var a = new TestActivity("a");
|
||||
var b = new TestActivity("b");
|
||||
var c = new TestActivity("c");
|
||||
var d = new TestActivity("d");
|
||||
var e = new TestActivity("e");
|
||||
|
||||
var connections = new List<Connection>
|
||||
{
|
||||
new(start, a),
|
||||
new(start, b),
|
||||
new(a, c),
|
||||
new(a, c), // duplicate
|
||||
new(b, c),
|
||||
new(b, d),
|
||||
new(d, e),
|
||||
new(c, e),
|
||||
new(c, e), // duplicate
|
||||
};
|
||||
|
||||
var flowGraph = new FlowGraph(connections, start);
|
||||
|
||||
// FlowGraph.GetForwardInboundConnections
|
||||
flowGraph.ValidateForwardInboundConnections([], start);
|
||||
flowGraph.ValidateForwardInboundConnections(["start->a"], a);
|
||||
flowGraph.ValidateForwardInboundConnections(["start->b"], b);
|
||||
flowGraph.ValidateForwardInboundConnections(["a->c", "b->c"], c);
|
||||
flowGraph.ValidateForwardInboundConnections(["b->d"], d);
|
||||
flowGraph.ValidateForwardInboundConnections(["c->e", "d->e"], e);
|
||||
|
||||
// FlowGraph.GetOutboundConnections
|
||||
flowGraph.ValidateOutboundConnections(["start->a", "start->b"], start);
|
||||
flowGraph.ValidateOutboundConnections(["a->c"], a);
|
||||
flowGraph.ValidateOutboundConnections(["b->c", "b->d"], b);
|
||||
flowGraph.ValidateOutboundConnections(["c->e"], c);
|
||||
flowGraph.ValidateOutboundConnections(["d->e"], d);
|
||||
flowGraph.ValidateOutboundConnections([], e);
|
||||
|
||||
// FlowGraph.GetAncestorActivities
|
||||
flowGraph.ValidateAncestorActivities([], start);
|
||||
flowGraph.ValidateAncestorActivities([start], a);
|
||||
flowGraph.ValidateAncestorActivities([start], b);
|
||||
flowGraph.ValidateAncestorActivities([a, b, start], c);
|
||||
flowGraph.ValidateAncestorActivities([b, start], d);
|
||||
flowGraph.ValidateAncestorActivities([c, d, a, b, start], e);
|
||||
}
|
||||
|
||||
// Start
|
||||
// ↙ ↘
|
||||
// A B
|
||||
// ↓↘ ↙ ↘
|
||||
// ↳→C D
|
||||
// ↓↘ ↙
|
||||
// ↳→E
|
||||
[Fact]
|
||||
public void SameEdgeDifferentPortDuplicateTest()
|
||||
{
|
||||
var start = new TestActivity("start");
|
||||
var a = new TestActivity("a");
|
||||
var b = new TestActivity("b");
|
||||
var c = new TestActivity("c");
|
||||
var d = new TestActivity("d");
|
||||
var e = new TestActivity("e");
|
||||
|
||||
var connections = new List<Connection>
|
||||
{
|
||||
new(start, a),
|
||||
new(start, b),
|
||||
new(start, b), // duplicate
|
||||
new(a, c),
|
||||
new(new Endpoint(a, "Yes"), new Endpoint(c)),
|
||||
new(new Endpoint(a, "Yes"), new Endpoint(c)), // duplicate
|
||||
new(b, c),
|
||||
new(b, d),
|
||||
new(c, e),
|
||||
new(new Endpoint(c, "Yes"), new Endpoint(e)),
|
||||
new(d, e),
|
||||
};
|
||||
|
||||
var flowGraph = new FlowGraph(connections, start);
|
||||
|
||||
// FlowGraph.GetForwardInboundConnections
|
||||
flowGraph.ValidateForwardInboundConnections([], start);
|
||||
flowGraph.ValidateForwardInboundConnections(["start->a"], a);
|
||||
flowGraph.ValidateForwardInboundConnections(["start->b"], b);
|
||||
flowGraph.ValidateForwardInboundConnections(["a->c", "a:Yes->c", "b->c"], c);
|
||||
flowGraph.ValidateForwardInboundConnections(["b->d"], d);
|
||||
flowGraph.ValidateForwardInboundConnections(["c->e", "c:Yes->e", "d->e"], e);
|
||||
|
||||
// FlowGraph.GetOutboundConnections
|
||||
flowGraph.ValidateOutboundConnections(["start->a", "start->b"], start);
|
||||
flowGraph.ValidateOutboundConnections(["a->c", "a:Yes->c"], a);
|
||||
flowGraph.ValidateOutboundConnections(["b->c", "b->d"], b);
|
||||
flowGraph.ValidateOutboundConnections(["c->e", "c:Yes->e"], c);
|
||||
flowGraph.ValidateOutboundConnections(["d->e"], d);
|
||||
flowGraph.ValidateOutboundConnections([], e);
|
||||
|
||||
// FlowGraph.GetAncestorActivities
|
||||
flowGraph.ValidateAncestorActivities([], start);
|
||||
flowGraph.ValidateAncestorActivities([start], a);
|
||||
flowGraph.ValidateAncestorActivities([start], b);
|
||||
flowGraph.ValidateAncestorActivities([a, b, start], c);
|
||||
flowGraph.ValidateAncestorActivities([b, start], d);
|
||||
flowGraph.ValidateAncestorActivities([c, d, a, b, start], e);
|
||||
}
|
||||
|
||||
class TestActivity : Activity
|
||||
{
|
||||
public TestActivity(string id)
|
||||
{
|
||||
Id = id;
|
||||
Name = id;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Elsa.Workflows.Core.UnitTests.ObjectConversion
|
||||
{
|
||||
public class Person
|
||||
{
|
||||
public double? Age { get; set; }
|
||||
|
||||
public string? Name { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
using System.Dynamic;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using Elsa.Expressions.Exceptions;
|
||||
using Elsa.Expressions.Helpers;
|
||||
|
||||
namespace Elsa.Workflows.Core.UnitTests.ObjectConversion;
|
||||
|
||||
public class Tests
|
||||
{
|
||||
private readonly ObjectConverterOptions _objectConverterOptions = new(StrictMode: true);
|
||||
|
||||
[Fact]
|
||||
public void TryConvertTo_SameType_ReturnsSuccess()
|
||||
{
|
||||
// Arrange
|
||||
var value = 42;
|
||||
|
||||
// Act
|
||||
var result = value.TryConvertTo<int>(_objectConverterOptions);
|
||||
|
||||
// Assert
|
||||
Assert.True(result.Success);
|
||||
Assert.Equal(42, result.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryConvertTo_DifferentType_ReturnsConvertedValue()
|
||||
{
|
||||
// Arrange
|
||||
var value = "42";
|
||||
|
||||
// Act
|
||||
var result = value.TryConvertTo<int>(_objectConverterOptions);
|
||||
|
||||
// Assert
|
||||
Assert.True(result.Success);
|
||||
Assert.Equal(42, result.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryConvertTo_InvalidConversion_ReturnsFailure()
|
||||
{
|
||||
// Arrange
|
||||
var value = "invalid";
|
||||
|
||||
// Act
|
||||
var result = value.TryConvertTo<int>(_objectConverterOptions);
|
||||
|
||||
// Assert
|
||||
Assert.False(result.Success);
|
||||
Assert.NotNull(result.Exception);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryConvertTo_InvalidJsonString_ReturnsFailure()
|
||||
{
|
||||
// Arrange
|
||||
var value = "{ invalid json }";
|
||||
|
||||
// Act
|
||||
var result = value.TryConvertTo<Dictionary<string, object>>(_objectConverterOptions);
|
||||
|
||||
// Assert
|
||||
Assert.False(result.Success);
|
||||
Assert.NotNull(result.Exception);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConvertTo_NullValue_ReturnsDefault()
|
||||
{
|
||||
// Arrange
|
||||
object? value = null;
|
||||
|
||||
// Act
|
||||
var result = value.ConvertTo<int>(_objectConverterOptions);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConvertTo_JsonElementNumberToString_ReturnsString()
|
||||
{
|
||||
// Arrange
|
||||
var jsonElement = JsonNode.Parse("42")!.AsValue();
|
||||
|
||||
// Act
|
||||
var result = jsonElement.ConvertTo<string>();
|
||||
|
||||
// Assert
|
||||
Assert.Equal("42", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConvertTo_JsonNodeToExpandoObject_ReturnsExpandoObject()
|
||||
{
|
||||
// Arrange
|
||||
var jsonNode = JsonNode.Parse("{ \"key\": \"value\" }");
|
||||
|
||||
// Act
|
||||
var result = jsonNode.ConvertTo<ExpandoObject>(_objectConverterOptions);
|
||||
|
||||
// Assert
|
||||
dynamic expando = Assert.IsType<ExpandoObject>(result);
|
||||
object value = expando.key;
|
||||
|
||||
// This is not the result I expect, I would have expected the JsonNode to have been recursively converted
|
||||
|
||||
Assert.True(value is JsonElement);
|
||||
Assert.Equal(JsonValueKind.String, ((JsonElement)value).ValueKind);
|
||||
Assert.Equal("value", ((JsonElement)value).GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConvertTo_StringToDateTime_ReturnsDateTime()
|
||||
{
|
||||
// Arrange
|
||||
var value = "2023-01-01T00:00:00";
|
||||
|
||||
// Act
|
||||
var result = value.ConvertTo<DateTime>(_objectConverterOptions);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new(2023, 1, 1, 0, 0, 0), result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConvertTo_StringToEnum_ReturnsEnum()
|
||||
{
|
||||
// Arrange
|
||||
var value = "Monday";
|
||||
|
||||
// Act
|
||||
var result = value.ConvertTo<DayOfWeek>(_objectConverterOptions);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(DayOfWeek.Monday, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConvertTo_StringToByteArray_ReturnsByteArray()
|
||||
{
|
||||
// Arrange
|
||||
var value = Convert.ToBase64String(new byte[]
|
||||
{
|
||||
1, 2, 3
|
||||
});
|
||||
|
||||
// Act
|
||||
var result = value.ConvertTo<byte[]>(_objectConverterOptions);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new byte[]
|
||||
{
|
||||
1, 2, 3
|
||||
}, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConvertTo_InvalidJsonString_ThrowsException()
|
||||
{
|
||||
// Arrange
|
||||
var value = "{ invalid json }";
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<TypeConversionException>(() => value.ConvertTo<Dictionary<string, object>>(_objectConverterOptions));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConvertTo_EnumerableToList_ReturnsConvertedList()
|
||||
{
|
||||
// Arrange
|
||||
var value = new[]
|
||||
{
|
||||
"1", "2", "3"
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = value.ConvertTo<List<int>>(_objectConverterOptions);
|
||||
|
||||
// Assert
|
||||
Assert.Equal([
|
||||
1,
|
||||
2,
|
||||
3
|
||||
], result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConvertTo_DateTimeToDateOnly_ReturnsDateOnly()
|
||||
{
|
||||
// Arrange
|
||||
var value = new DateTime(2023, 1, 1);
|
||||
|
||||
// Act
|
||||
var result = value.ConvertTo<DateOnly>(_objectConverterOptions);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new(2023, 1, 1), result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConvertTo_DateOnlyToDateTime_ReturnsDateTime()
|
||||
{
|
||||
// Arrange
|
||||
var value = new DateOnly(2023, 1, 1);
|
||||
|
||||
// Act
|
||||
var result = value.ConvertTo<DateTime>(_objectConverterOptions);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new(2023, 1, 1, 0, 0, 0), result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConvertTo_UnknownConversion_ThrowsInvalidCastException()
|
||||
{
|
||||
// Arrange
|
||||
var value = new object();
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<TypeConversionException>(() => value.ConvertTo<int>(_objectConverterOptions));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConvertTo_JsonArrayToListOfObject_ReturnsListOfJsonObject()
|
||||
{
|
||||
// Arrange
|
||||
var jsonArrayString = "[{\"name\":\"Alice\",\"age\":30},{\"name\":\"Bob\",\"age\":25}]";
|
||||
var jsonArray = JsonNode.Parse(jsonArrayString);
|
||||
|
||||
// Act
|
||||
var result = jsonArray.ConvertTo<List<object>>(_objectConverterOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.IsType<JsonObject>(result[0]);
|
||||
Assert.IsType<JsonObject>(result[1]);
|
||||
|
||||
var firstElement = result[0] as JsonObject;
|
||||
var secondElement = result[1] as JsonObject;
|
||||
|
||||
Assert.NotNull(firstElement);
|
||||
Assert.Equal("Alice", firstElement["name"]?.ToString());
|
||||
Assert.Equal("30", firstElement["age"]?.ToString());
|
||||
|
||||
Assert.NotNull(secondElement);
|
||||
Assert.Equal("Bob", secondElement["name"]?.ToString());
|
||||
Assert.Equal("25", secondElement["age"]?.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConvertTo_JsonArrayToICollectionOfObject_ReturnsCollectionOfJsonObject()
|
||||
{
|
||||
// Arrange
|
||||
var jsonArrayString = "[{\"name\":\"Alice\",\"age\":30},{\"name\":\"Bob\",\"age\":25}]";
|
||||
var jsonArray = JsonNode.Parse(jsonArrayString);
|
||||
|
||||
// Act
|
||||
var result = jsonArray.ConvertTo<ICollection<object>>(_objectConverterOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.All(result, item => Assert.IsType<JsonObject>(item));
|
||||
|
||||
var firstElement = result.First() as JsonObject;
|
||||
var secondElement = result.Last() as JsonObject;
|
||||
|
||||
Assert.NotNull(firstElement);
|
||||
Assert.Equal("Alice", firstElement["name"]?.ToString());
|
||||
Assert.Equal("30", firstElement["age"]?.ToString());
|
||||
|
||||
Assert.NotNull(secondElement);
|
||||
Assert.Equal("Bob", secondElement["name"]?.ToString());
|
||||
Assert.Equal("25", secondElement["age"]?.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConvertFrom_JsonArrayToArrayOfComplexType_ReturnsArrayOfComplexType()
|
||||
{
|
||||
// Arrange
|
||||
var jsonArrayString = "[{\"name\":\"Alice\",\"age\":30},{\"name\":\"Bob\",\"age\":25}]";
|
||||
|
||||
// Act
|
||||
var result = jsonArrayString.ConvertTo<Person[]>(_objectConverterOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(2, result.Length);
|
||||
Assert.Equal("Alice", result[0].Name);
|
||||
Assert.Equal("Bob", result[1].Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConvertFrom_ObjectArrayOfDoubleToArrayOfDouble_ReturnsArrayOfDouble()
|
||||
{
|
||||
// Arrange
|
||||
object[] objectArray = [1d, 2d, 3d];
|
||||
|
||||
// Act
|
||||
var result = objectArray.ConvertTo<double[]>(_objectConverterOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("foo", false, typeof(bool))]
|
||||
[InlineData("true", true, typeof(bool))]
|
||||
[InlineData("false", false, typeof(bool))]
|
||||
[InlineData("bar", 0, typeof(int))]
|
||||
[InlineData("123", 123, typeof(int))]
|
||||
[InlineData("notadate", null!, typeof(DateTime?))]
|
||||
[InlineData("2023-01-01T00:00:00", "2023-01-01T00:00:00", typeof(DateTime))]
|
||||
public void ConvertTo_StrictModeDisabled_ReturnsDefaultOrConverted(string input, object? expected, Type targetType)
|
||||
{
|
||||
var options = new ObjectConverterOptions(StrictMode: false);
|
||||
var result = input.ConvertTo(targetType, options);
|
||||
|
||||
if (expected is null)
|
||||
{
|
||||
Assert.Null(result);
|
||||
return;
|
||||
}
|
||||
|
||||
// Special handling for DateTime
|
||||
if (targetType == typeof(DateTime) && expected is string expectedString)
|
||||
{
|
||||
var expectedDateTime = DateTime.Parse(expectedString, null, System.Globalization.DateTimeStyles.RoundtripKind);
|
||||
Assert.Equal(expectedDateTime, result);
|
||||
return;
|
||||
}
|
||||
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("foo", true, typeof(bool))]
|
||||
[InlineData("true", false, typeof(bool))]
|
||||
[InlineData("false", false, typeof(bool))]
|
||||
[InlineData("bar", true, typeof(int))]
|
||||
[InlineData("123", false, typeof(int))]
|
||||
[InlineData("notadate", true, typeof(DateTime?))]
|
||||
[InlineData("2023-01-01T00:00:00", false, typeof(DateTime))]
|
||||
public void ConvertTo_StrictModeEnabled_Throws(string input, bool shouldThrow, Type targetType)
|
||||
{
|
||||
var options = new ObjectConverterOptions(StrictMode: true);
|
||||
|
||||
if(shouldThrow)
|
||||
Assert.Throws<TypeConversionException>(() => input.ConvertTo(targetType, options));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
global using Xunit;
|
||||
@@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\modules\Elsa.Workflows.Runtime\Elsa.Workflows.Runtime.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
using Elsa.Common;
|
||||
using Elsa.Workflows.Activities;
|
||||
using Elsa.Workflows.Management;
|
||||
using Elsa.Workflows.Management.Entities;
|
||||
using Elsa.Workflows.Management.Filters;
|
||||
using Elsa.Workflows.Models;
|
||||
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<IWorkflowsProvider>(),
|
||||
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>());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
global using Xunit;
|
||||
Reference in New Issue
Block a user