1+ using Elsa . Common . Models ;
2+ using Elsa . Common . Services ;
13using Elsa . Workflows . Runtime . Entities ;
2- using Elsa . Workflows . Runtime . Filters ;
3- using Moq ;
4+ using Elsa . Workflows . Runtime . Stores ;
45
56namespace Elsa . Workflows . Runtime . UnitTests . Extensions ;
67
78public class ActivityExecutionStoreExtensionsTests
89{
9- private readonly Mock < IActivityExecutionStore > _storeMock = new ( ) ;
10-
1110 [ Fact ( DisplayName = "GetExecutionChainAsync returns empty page when record not found" ) ]
1211 public async Task GetExecutionChainAsync_ReturnsEmptyPage_WhenRecordNotFound ( )
1312 {
1413 // Arrange
15- _storeMock
16- . Setup ( x => x . FindAsync ( It . IsAny < ActivityExecutionRecordFilter > ( ) , It . IsAny < CancellationToken > ( ) ) )
17- . ReturnsAsync ( ( ActivityExecutionRecord ? ) null ) ;
14+ var store = CreateStore ( ) ;
1815
1916 // Act
20- var result = await _storeMock . Object . GetExecutionChainAsync ( "non-existent-id" ) ;
17+ var result = await store . GetExecutionChainAsync ( "non-existent-id" ) ;
2118
2219 // Assert
2320 Assert . Empty ( result . Items ) ;
@@ -28,20 +25,20 @@ public async Task GetExecutionChainAsync_ReturnsEmptyPage_WhenRecordNotFound()
2825 public async Task GetExecutionChainAsync_ReturnsSingleRecord_WhenNoParent ( )
2926 {
3027 // Arrange
31- SetupStore ( CreateChain ( 1 ) ) ;
28+ var store = CreateStore ( CreateChain ( 1 ) ) ;
3229
3330 // Act & Assert
34- await AssertChainAsync ( "record-1" , expectedIds : [ "record-1" ] ) ;
31+ await AssertChainAsync ( store , "record-1" , expectedIds : [ "record-1" ] ) ;
3532 }
3633
3734 [ Fact ( DisplayName = "GetExecutionChainAsync traverses chain correctly with multiple levels" ) ]
3835 public async Task GetExecutionChainAsync_TraversesChainCorrectly_WithMultipleLevels ( )
3936 {
4037 // Arrange
41- SetupStore ( CreateChain ( 3 ) ) ;
38+ var store = CreateStore ( CreateChain ( 3 ) ) ;
4239
4340 // Act & Assert
44- await AssertChainAsync ( "record-3" , expectedIds : [ "record-1" , "record-2" , "record-3" ] ) ;
41+ await AssertChainAsync ( store , "record-3" , expectedIds : [ "record-1" , "record-2" , "record-3" ] ) ;
4542 }
4643
4744 [ Theory ( DisplayName = "GetExecutionChainAsync respects workflow boundary based on includeCrossWorkflowChain" ) ]
@@ -53,10 +50,10 @@ public async Task GetExecutionChainAsync_RespectsWorkflowBoundary(bool includeCr
5350 var parentRecord = CreateRecord ( "parent-record" , "parent-workflow" , null ) ;
5451 var childRoot = CreateRecord ( "child-root" , "child-workflow" , "parent-record" , "parent-workflow" ) ;
5552 var childLeaf = CreateRecord ( "child-leaf" , "child-workflow" , "child-root" ) ;
56- SetupStore ( parentRecord , childRoot , childLeaf ) ;
53+ var store = CreateStore ( parentRecord , childRoot , childLeaf ) ;
5754
5855 // Act & Assert
59- await AssertChainAsync ( "child-leaf" , expectedIds , includeCrossWorkflowChain : includeCrossWorkflowChain ) ;
56+ await AssertChainAsync ( store , "child-leaf" , expectedIds , includeCrossWorkflowChain : includeCrossWorkflowChain ) ;
6057 }
6158
6259 [ Theory ( DisplayName = "GetExecutionChainAsync applies pagination correctly" ) ]
@@ -67,10 +64,10 @@ public async Task GetExecutionChainAsync_RespectsWorkflowBoundary(bool includeCr
6764 public async Task GetExecutionChainAsync_AppliesPagination ( int skip , int ? take , int expectedTotal , string [ ] expectedIds )
6865 {
6966 // Arrange
70- SetupStore ( CreateChain ( 4 ) ) ;
67+ var store = CreateStore ( CreateChain ( 4 ) ) ;
7168
7269 // Act
73- var result = await _storeMock . Object . GetExecutionChainAsync ( "record-4" , skip : skip , take : take ) ;
70+ var result = await store . GetExecutionChainAsync ( "record-4" , skip : skip , take : take ) ;
7471 var items = result . Items . ToList ( ) ;
7572
7673 // Assert
@@ -84,25 +81,33 @@ public async Task GetExecutionChainAsync_HandlesCircularReference_Gracefully()
8481 // Arrange: Create a circular reference (which shouldn't happen in practice, but should be handled)
8582 var record1 = CreateRecord ( "record-1" , "workflow-1" , "record-2" ) ;
8683 var record2 = CreateRecord ( "record-2" , "workflow-1" , "record-1" ) ;
87- SetupStore ( record1 , record2 ) ;
84+ var store = CreateStore ( record1 , record2 ) ;
8885
8986 // Act
90- var result = await _storeMock . Object . GetExecutionChainAsync ( "record-1" ) ;
87+ var result = await store . GetExecutionChainAsync ( "record-1" ) ;
9188
9289 // Assert: Should have visited both records (once each) and stopped
9390 Assert . Equal ( 2 , result . Items . Count ) ;
9491 }
9592
9693 #region Helpers
9794
98- private async Task AssertChainAsync (
95+ private static MemoryActivityExecutionStore CreateStore ( params ActivityExecutionRecord [ ] records )
96+ {
97+ var memoryStore = new MemoryStore < ActivityExecutionRecord > ( ) ;
98+ memoryStore . SaveMany ( records , r => r . Id ) ;
99+ return new MemoryActivityExecutionStore ( memoryStore ) ;
100+ }
101+
102+ private static async Task AssertChainAsync (
103+ IActivityExecutionStore store ,
99104 string startRecordId ,
100105 string [ ] expectedIds ,
101106 bool includeCrossWorkflowChain = true ,
102107 int skip = 0 ,
103108 int ? take = null )
104109 {
105- var result = await _storeMock . Object . GetExecutionChainAsync ( startRecordId , includeCrossWorkflowChain , skip , take ) ;
110+ var result = await store . GetExecutionChainAsync ( startRecordId , includeCrossWorkflowChain , skip , take ) ;
106111 var items = result . Items . ToList ( ) ;
107112
108113 Assert . Equal ( expectedIds . Length , items . Count ) ;
@@ -141,24 +146,5 @@ private static ActivityExecutionRecord CreateRecord(
141146 SchedulingWorkflowInstanceId = schedulingWorkflowInstanceId
142147 } ;
143148
144- private void SetupStore ( params ActivityExecutionRecord [ ] records )
145- {
146- var recordsDict = records . ToDictionary ( r => r . Id ) ;
147- var recordsByWorkflow = records . GroupBy ( r => r . WorkflowInstanceId )
148- . ToDictionary ( g => g . Key , g => g . ToList ( ) ) ;
149-
150- _storeMock
151- . Setup ( x => x . FindAsync ( It . IsAny < ActivityExecutionRecordFilter > ( ) , It . IsAny < CancellationToken > ( ) ) )
152- . ReturnsAsync ( ( ActivityExecutionRecordFilter filter , CancellationToken _ ) =>
153- filter . Id != null && recordsDict . TryGetValue ( filter . Id , out var record ) ? record : null ) ;
154-
155- _storeMock
156- . Setup ( x => x . FindManyAsync ( It . IsAny < ActivityExecutionRecordFilter > ( ) , It . IsAny < CancellationToken > ( ) ) )
157- . ReturnsAsync ( ( ActivityExecutionRecordFilter filter , CancellationToken _ ) =>
158- filter . WorkflowInstanceId != null && recordsByWorkflow . TryGetValue ( filter . WorkflowInstanceId , out var workflowRecords )
159- ? workflowRecords . AsEnumerable ( )
160- : Enumerable . Empty < ActivityExecutionRecord > ( ) ) ;
161- }
162-
163149 #endregion
164150}
0 commit comments