@@ -59,16 +59,7 @@ public async Task AcquireLockWithRetry_AcquisitionFailures_BehavesAsExpected(int
5959 public async Task RunInstanceAsync_TransientLockFailures_RetriesCorrectly ( int failureCount , bool shouldThrow )
6060 {
6161 // Arrange
62- var workflowRuntime = Scope . ServiceProvider . GetRequiredService < IWorkflowRuntime > ( ) ;
63-
64- // First, create the workflow instance to get its ID
65- var createRequest = new CreateWorkflowInstanceRequest
66- {
67- WorkflowDefinitionHandle = WorkflowDefinitionHandle . ByDefinitionId ( SimpleWorkflow . DefinitionId , VersionOptions . Latest )
68- } ;
69-
70- var workflowClient = await workflowRuntime . CreateClientAsync ( ) ;
71- await workflowClient . CreateInstanceAsync ( createRequest ) ;
62+ var workflowClient = await CreateWorkflowClientAsync ( ) ;
7263 var workflowInstanceId = workflowClient . WorkflowInstanceId ;
7364
7465 // Reset and configure failures for this specific workflow instance's lock
@@ -82,98 +73,75 @@ public async Task RunInstanceAsync_TransientLockFailures_RetriesCorrectly(int fa
8273 // Act & Assert
8374 if ( shouldThrow )
8475 {
85- // Should throw after exhausting retries
8676 await Assert . ThrowsAsync < TimeoutException > ( async ( ) =>
8777 await workflowClient . RunInstanceAsync ( runRequest ) ) ;
8878 }
8979 else
9080 {
91- // Should succeed after retries
9281 var response = await workflowClient . RunInstanceAsync ( runRequest ) ;
9382 Assert . NotNull ( response ) ;
9483 }
9584
9685 // Verify retries occurred - check the delta from before the operation to account for background noise
97- var actualAttempts = MockProvider . AcquisitionAttemptCount - attemptCountBefore ;
9886 var expectedAttempts = failureCount + 1 ; // failures + 1 success (or final failure for shouldThrow case)
99- Assert . True ( actualAttempts >= expectedAttempts ,
100- $ "Expected at least { expectedAttempts } acquisition attempts, but got { actualAttempts } ") ;
101- }
102-
103- [ Fact ]
104- public async Task RunInstanceAsync_TransientLockFailureOnSecondRun_RetriesCorrectly ( )
105- {
106- // Arrange
107- var workflowRuntime = Scope . ServiceProvider . GetRequiredService < IWorkflowRuntime > ( ) ;
108-
109- // First run - reset and don't inject failures
110- MockProvider . Reset ( ) ;
111- var workflowClient = await workflowRuntime . CreateClientAsync ( ) ;
112-
113- var createRequest = new CreateAndRunWorkflowInstanceRequest
114- {
115- WorkflowDefinitionHandle = WorkflowDefinitionHandle . ByDefinitionId ( SimpleWorkflow . DefinitionId , VersionOptions . Latest )
116- } ;
117-
118- var firstResponse = await workflowClient . CreateAndRunInstanceAsync ( createRequest ) ;
119- Assert . NotNull ( firstResponse . WorkflowInstanceId ) ;
120-
121- // Verify first run succeeded (at least 1 attempt, possibly more from background workers)
122- Assert . True ( MockProvider . AcquisitionAttemptCount >= 1 ) ;
123-
124- // Setup second workflow with failure - reset right before to minimize background noise
125- MockProvider . Reset ( ) ;
126- MockProvider . FailAcquisitionTimes ( 2 ) ; // Fail twice, succeed on third
127-
128- var secondClient = await workflowRuntime . CreateClientAsync ( ) ;
129- var secondResponse = await secondClient . CreateAndRunInstanceAsync ( createRequest ) ;
130-
131- // Act & Assert - Second run should succeed after retries
132- Assert . NotNull ( secondResponse ) ;
133- Assert . NotNull ( secondResponse . WorkflowInstanceId ) ;
134- Assert . NotEqual ( firstResponse . WorkflowInstanceId , secondResponse . WorkflowInstanceId ) ;
135-
136- // Verify retries occurred (at least 3 attempts: 2 failures + 1 success, possibly more from background)
137- Assert . True ( MockProvider . AcquisitionAttemptCount >= 3 ,
138- $ "Expected at least 3 acquisition attempts, but got { MockProvider . AcquisitionAttemptCount } ") ;
87+ AssertMinimumAttempts ( MockProvider . AcquisitionAttemptCount - attemptCountBefore , expectedAttempts , "acquisition" ) ;
13988 }
14089
14190 [ Fact ]
14291 public async Task RunInstanceAsync_TransientReleaseFailure_ShouldLogButNotThrow ( )
14392 {
14493 // Arrange
14594 MockProvider . Reset ( ) ;
146-
147- var workflowRuntime = Scope . ServiceProvider . GetRequiredService < IWorkflowRuntime > ( ) ;
148- var workflowClient = await workflowRuntime . CreateClientAsync ( ) ;
149-
150- var createRequest = new CreateAndRunWorkflowInstanceRequest
151- {
152- WorkflowDefinitionHandle = WorkflowDefinitionHandle . ByDefinitionId ( SimpleWorkflow . DefinitionId , VersionOptions . Latest )
153- } ;
95+ var workflowClient = await CreateWorkflowClientAsync ( createInstance : false ) ;
15496
15597 // Configure failure after client creation to minimize background interference
15698 MockProvider . FailReleaseOnce ( ) ;
15799 var releaseCountBefore = MockProvider . ReleaseAttemptCount ;
158100
159101 // Act - Release failure should be caught and logged, not thrown
160- var response = await workflowClient . CreateAndRunInstanceAsync ( createRequest ) ;
102+ var response = await workflowClient . CreateAndRunInstanceAsync ( CreateAndRunRequest ( ) ) ;
161103
162104 // Assert
163105 Assert . NotNull ( response ) ;
164106 Assert . NotNull ( response . WorkflowInstanceId ) ;
165-
166- // Verify at least one release occurred (allow for background noise)
167- var actualReleaseAttempts = MockProvider . ReleaseAttemptCount - releaseCountBefore ;
168- Assert . True ( actualReleaseAttempts >= 1 ,
169- $ "Expected at least 1 release attempt, but got { actualReleaseAttempts } ") ;
107+ AssertMinimumAttempts ( MockProvider . ReleaseAttemptCount - releaseCountBefore , 1 , "release" ) ;
170108 }
171109
172110 private async Task < IDistributedSynchronizationHandle ? > AcquireLockWithRetryAsync ( string lockName ) =>
173111 await RetryPipeline . ExecuteAsync ( async ct =>
174112 await MockProvider . AcquireLockAsync ( lockName , LockOptions . LockAcquisitionTimeout , ct ) ,
175113 CancellationToken . None ) ;
176114
115+ /// <summary>
116+ /// Creates a workflow client with an optional workflow instance already created.
117+ /// </summary>
118+ private async Task < IWorkflowClient > CreateWorkflowClientAsync ( bool createInstance = true )
119+ {
120+ var workflowRuntime = Scope . ServiceProvider . GetRequiredService < IWorkflowRuntime > ( ) ;
121+ var workflowClient = await workflowRuntime . CreateClientAsync ( ) ;
122+
123+ if ( createInstance )
124+ {
125+ var createRequest = new CreateWorkflowInstanceRequest
126+ {
127+ WorkflowDefinitionHandle = WorkflowDefinitionHandle . ByDefinitionId ( SimpleWorkflow . DefinitionId , VersionOptions . Latest )
128+ } ;
129+ await workflowClient . CreateInstanceAsync ( createRequest ) ;
130+ }
131+
132+ return workflowClient ;
133+ }
134+
135+ private static CreateAndRunWorkflowInstanceRequest CreateAndRunRequest ( ) =>
136+ new ( )
137+ {
138+ WorkflowDefinitionHandle = WorkflowDefinitionHandle . ByDefinitionId ( SimpleWorkflow . DefinitionId , VersionOptions . Latest )
139+ } ;
140+
141+ private static void AssertMinimumAttempts ( int actualAttempts , int expectedAttempts , string attemptType ) =>
142+ Assert . True ( actualAttempts >= expectedAttempts ,
143+ $ "Expected at least { expectedAttempts } { attemptType } attempts, but got { actualAttempts } ") ;
144+
177145 private static ResiliencePipeline CreateRetryPipeline ( ITransientExceptionDetector transientExceptionDetector , ILogger logger ) =>
178146 new ResiliencePipelineBuilder ( )
179147 . AddRetry ( new ( )
0 commit comments