@@ -31,6 +31,11 @@ public class TestRunner : ITestRunner, IDisposable
3131 private string _runId ;
3232 private string _clientTrackingId ;
3333
34+ // Whether to wait for the asynchronous response by polling the redirected URI
35+ private bool _waitForAsyncResponse ;
36+ // Maximum time to wait for the asynchronous response before timing out
37+ private TimeSpan _asyncResponseTimeout ;
38+
3439 // Requests sent to the mock test server that are generated by the workflow during its execution.
3540 // Use a ConcurrentBag to store the requests during the test execution to ensure thread safety of this collection
3641 private ConcurrentBag < MockRequest > _mockRequests ;
@@ -99,6 +104,13 @@ public WorkflowRunStatus WorkflowRunStatus
99104
100105 #region Lifetime management
101106
107+ /// <inheritdoc cref="ITestRunner.WaitForAsynchronousResponse(TimeSpan)"/>
108+ public void WaitForAsynchronousResponse ( TimeSpan maxTimeout )
109+ {
110+ _waitForAsyncResponse = true ;
111+ _asyncResponseTimeout = maxTimeout ;
112+ }
113+
102114 /// <summary>
103115 /// Initializes a new instance of the <see cref="TestRunner"/> class.
104116 /// </summary>
@@ -282,17 +294,35 @@ public Dictionary<string, string> GetWorkflowActionTrackedProperties(string acti
282294 /// <inheritdoc cref="ITestRunner.TriggerWorkflow(HttpMethod, Dictionary{string, string})" />
283295 public HttpResponseMessage TriggerWorkflow ( HttpMethod method , Dictionary < string , string > requestHeaders = null )
284296 {
285- return TriggerWorkflow ( null , method , requestHeaders ) ;
297+ return TriggerWorkflow ( null , null , method , string . Empty , requestHeaders ) ;
298+ }
299+
300+ /// <inheritdoc cref="ITestRunner.TriggerWorkflow(Dictionary{string, string}, HttpMethod, Dictionary{string, string})" />
301+ public HttpResponseMessage TriggerWorkflow ( Dictionary < string , string > queryParams , HttpMethod method , Dictionary < string , string > requestHeaders = null )
302+ {
303+ return TriggerWorkflow ( queryParams , null , method , string . Empty , requestHeaders ) ;
304+ }
305+
306+ /// <inheritdoc cref="ITestRunner.TriggerWorkflow(Dictionary{string, string}, HttpMethod, string, Dictionary{string, string})" />
307+ public HttpResponseMessage TriggerWorkflow ( Dictionary < string , string > queryParams , HttpMethod method , string relativePath , Dictionary < string , string > requestHeaders = null )
308+ {
309+ return TriggerWorkflow ( queryParams , null , method , relativePath , requestHeaders ) ;
286310 }
287311
288312 /// <inheritdoc cref="ITestRunner.TriggerWorkflow(HttpContent, HttpMethod, Dictionary{string, string})" />
289313 public HttpResponseMessage TriggerWorkflow ( HttpContent content , HttpMethod method , Dictionary < string , string > requestHeaders = null )
290314 {
291- return TriggerWorkflow ( content , method , string . Empty , requestHeaders ) ;
315+ return TriggerWorkflow ( null , content , method , string . Empty , requestHeaders ) ;
292316 }
293317
294318 /// <inheritdoc cref="ITestRunner.TriggerWorkflow(HttpContent, HttpMethod, string, Dictionary{string, string})" />
295319 public HttpResponseMessage TriggerWorkflow ( HttpContent content , HttpMethod method , string relativePath , Dictionary < string , string > requestHeaders = null )
320+ {
321+ return TriggerWorkflow ( null , content , method , relativePath , requestHeaders ) ;
322+ }
323+
324+ /// <inheritdoc cref="ITestRunner.TriggerWorkflow(Dictionary{string, string}, HttpContent, HttpMethod, string, Dictionary{string, string})" />
325+ public HttpResponseMessage TriggerWorkflow ( Dictionary < string , string > queryParams , HttpContent content , HttpMethod method , string relativePath , Dictionary < string , string > requestHeaders = null )
296326 {
297327 string triggerName = _workflowDefinition . HttpTriggerName ;
298328 if ( string . IsNullOrEmpty ( triggerName ) )
@@ -306,7 +336,7 @@ public HttpResponseMessage TriggerWorkflow(HttpContent content, HttpMethod metho
306336 {
307337 Content = content ,
308338 Method = method ,
309- RequestUri = callbackDef . ValueWithRelativePath ( relativePath )
339+ RequestUri = callbackDef . ValueWithQueryAndRelativePath ( queryParams , relativePath )
310340 } ;
311341
312342 if ( requestHeaders != null )
@@ -432,30 +462,73 @@ private JToken GetWorkflowActionRepetitionMessage(string actionName, int repetit
432462 /// <returns>The response from the workflow.</returns>
433463 private HttpResponseMessage PollAndReturnFinalWorkflowResponse ( HttpRequestMessage httpRequestMessage )
434464 {
465+ HttpResponseMessage asyncResponse = null ;
466+
435467 // Call the endpoint for the HTTP trigger
436468 var initialWorkflowHttpResponse = _client . SendAsync ( httpRequestMessage ) . Result ;
437469
438470 // Store some of the run metadata for test assertions, this may not exist for stateless workflows
439471 _runId = GetHeader ( initialWorkflowHttpResponse . Headers , "x-ms-workflow-run-id" ) ;
440472 _clientTrackingId = GetHeader ( initialWorkflowHttpResponse . Headers , "x-ms-client-tracking-id" ) ;
441473
442- if ( initialWorkflowHttpResponse . StatusCode != HttpStatusCode . Accepted )
474+ // Check for and handle asynchronous response
475+ var callbackLocation = initialWorkflowHttpResponse . Headers ? . Location ;
476+ if ( initialWorkflowHttpResponse . StatusCode == HttpStatusCode . Accepted && callbackLocation is not null )
443477 {
444- return initialWorkflowHttpResponse ;
478+ // If the _waitForAsyncResponse is not set (to true), return the initial response
479+ if ( ! _waitForAsyncResponse )
480+ {
481+ return initialWorkflowHttpResponse ;
482+ }
483+
484+ var retryAfterSeconds = initialWorkflowHttpResponse . Headers ? . RetryAfter ? . Delta ?? TimeSpan . FromSeconds ( 5 ) ;
485+
486+ var stopwatchAsyncRes = new Stopwatch ( ) ;
487+ stopwatchAsyncRes . Start ( ) ;
488+
489+ while ( stopwatchAsyncRes . Elapsed < _asyncResponseTimeout )
490+ {
491+ using ( var latestAsyncResponse = _client . GetAsync ( callbackLocation ) . Result )
492+ {
493+ if ( latestAsyncResponse . StatusCode != HttpStatusCode . Accepted )
494+ {
495+ stopwatchAsyncRes . Stop ( ) ;
496+ asyncResponse = latestAsyncResponse ;
497+ break ;
498+ }
499+ Thread . Sleep ( retryAfterSeconds ) ;
500+ }
501+ }
502+
503+ if ( stopwatchAsyncRes . Elapsed >= _asyncResponseTimeout )
504+ {
505+ throw new TestException ( $ "Workflow is taking more than { _asyncResponseTimeout . TotalMinutes } minutes for returning the final async response.") ;
506+ }
445507 }
446508
509+ // Wait till the workflow ends, i.e., workflow status is not "Running".
510+ // This should be checked in case of HTTP trigger workflows as well to make sure that any
511+ // actions after the Response action are properly run before testing their outputs.
447512 var stopwatch = new Stopwatch ( ) ;
448513 stopwatch . Start ( ) ;
449-
450514 while ( stopwatch . Elapsed < TimeSpan . FromMinutes ( Constants . MAX_TIME_MINUTES_WHILE_POLLING_WORKFLOW_RESULT ) )
451515 {
452516 using ( var latestWorkflowHttpResponse = _client . GetAsync ( TestEnvironment . GetRunsRequestUriWithManagementHost ( flowName : _workflowDefinition . WorkflowName ) ) . Result )
453517 {
454518 var latestWorkflowHttpResponseContent = latestWorkflowHttpResponse . Content . ReadAsAsync < JToken > ( ) . Result ;
455519 var runStatusOfWorkflow = latestWorkflowHttpResponseContent [ "value" ] [ 0 ] [ "properties" ] [ "status" ] . ToString ( ) ;
456- // If we got status code other than Accepted then return the response
520+ // If we got status code other than Accepted then return the appropriate response.
457521 if ( latestWorkflowHttpResponse . StatusCode != HttpStatusCode . Accepted && runStatusOfWorkflow != ActionStatus . Running . ToString ( ) )
458522 {
523+ // If there is an asynchronous response return it.
524+ if ( asyncResponse is not null )
525+ return asyncResponse ;
526+
527+ // If the initial response was from an HTTP trigger workflow, return it.
528+ if ( initialWorkflowHttpResponse . StatusCode != HttpStatusCode . Accepted )
529+ return initialWorkflowHttpResponse ;
530+
531+ // It must be a non-HTTP trigger workflow, return the output of the workflow run.
459532 return latestWorkflowHttpResponse ;
460533 }
461534 Thread . Sleep ( 1000 ) ;
0 commit comments