Add new HTTP workflow tests and enhance middleware

Introduce tests for resuming specific HTTP workflow instances. Enhanced `HttpWorkflowsMiddleware` to handle workflow instance IDs and correlation IDs for more precise control over workflow activation and resumption.
This commit is contained in:
Sipke Schoorstra 2024-08-16 13:16:47 +02:00
parent 0a76741755
commit 83d0e14248
5 changed files with 86 additions and 15 deletions

View file

@ -69,6 +69,8 @@ public class HttpWorkflowsMiddleware(RequestDelegate next, IOptions<HttpActivity
var request = httpContext.Request;
var method = request.Method.ToLowerInvariant();
var httpWorkflowLookupService = serviceProvider.GetRequiredService<IHttpWorkflowLookupService>();
var workflowInstanceId = await GetWorkflowInstanceIdAsync(serviceProvider, httpContext, cancellationToken);
var correlationId = await GetCorrelationIdAsync(serviceProvider, httpContext, cancellationToken);
var bookmarkHash = ComputeBookmarkHash(serviceProvider, matchingPath, method);
var lookupResult = await httpWorkflowLookupService.FindWorkflowAsync(bookmarkHash, cancellationToken);
@ -76,7 +78,7 @@ public class HttpWorkflowsMiddleware(RequestDelegate next, IOptions<HttpActivity
{
var triggers = lookupResult.Triggers;
if (triggers?.Count > 1)
if (triggers.Count > 1)
{
await HandleMultipleWorkflowsFoundAsync(httpContext, () => triggers.Select(x => new
{
@ -85,16 +87,16 @@ public class HttpWorkflowsMiddleware(RequestDelegate next, IOptions<HttpActivity
return;
}
var trigger = triggers?.FirstOrDefault();
var trigger = triggers.FirstOrDefault();
if (trigger != null)
{
var workflowGraph = lookupResult.WorkflowGraph!;
await StartWorkflowAsync(httpContext, trigger, workflowGraph, input);
await StartWorkflowAsync(httpContext, trigger, workflowGraph, workflowInstanceId, correlationId, input);
return;
}
}
var bookmarks = await FindBookmarksAsync(serviceProvider, bookmarkHash, cancellationToken).ToList();
var bookmarks = await FindBookmarksAsync(serviceProvider, bookmarkHash, workflowInstanceId, correlationId, cancellationToken).ToList();
if (bookmarks.Count > 1)
{
@ -109,7 +111,7 @@ public class HttpWorkflowsMiddleware(RequestDelegate next, IOptions<HttpActivity
if (bookmark != null)
{
await ResumeWorkflowAsync(httpContext, bookmark, input);
await ResumeWorkflowAsync(httpContext, bookmark, correlationId, input);
return;
}
@ -131,7 +133,7 @@ public class HttpWorkflowsMiddleware(RequestDelegate next, IOptions<HttpActivity
return await workflowDefinitionService.FindWorkflowGraphAsync(workflowDefinitionId, cancellationToken);
}
private async Task StartWorkflowAsync(HttpContext httpContext, StoredTrigger trigger, WorkflowGraph workflowGraph, IDictionary<string, object> input)
private async Task StartWorkflowAsync(HttpContext httpContext, StoredTrigger trigger, WorkflowGraph workflowGraph, string? workflowInstanceId, string? correlationId, Dictionary<string, object>? input)
{
var serviceProvider = httpContext.RequestServices;
var cancellationToken = httpContext.RequestAborted;
@ -144,8 +146,6 @@ public class HttpWorkflowsMiddleware(RequestDelegate next, IOptions<HttpActivity
await ExecuteWithinTimeoutAsync(async ct =>
{
var cancellationTokens = new CancellationTokens(ct, ct);
var workflowInstanceId = await GetWorkflowInstanceIdAsync(serviceProvider, httpContext, httpContext.RequestAborted);
var correlationId = await GetCorrelationIdAsync(serviceProvider, httpContext, httpContext.RequestAborted);
var startParams = new StartWorkflowHostParams
{
Input = input,
@ -160,7 +160,7 @@ public class HttpWorkflowsMiddleware(RequestDelegate next, IOptions<HttpActivity
await HandleWorkflowFaultAsync(serviceProvider, httpContext, workflowHost.WorkflowState, cancellationToken);
}
private async Task ResumeWorkflowAsync(HttpContext httpContext, StoredBookmark bookmark, IDictionary<string, object> input)
private async Task ResumeWorkflowAsync(HttpContext httpContext, StoredBookmark bookmark, string? correlationId, IDictionary<string, object> input)
{
var serviceProvider = httpContext.RequestServices;
var cancellationToken = httpContext.RequestAborted;
@ -193,7 +193,6 @@ public class HttpWorkflowsMiddleware(RequestDelegate next, IOptions<HttpActivity
await ExecuteWithinTimeoutAsync(async ct =>
{
var correlationId = await GetCorrelationIdAsync(serviceProvider, httpContext, ct);
var cancellationTokens = new CancellationTokens(ct, ct);
var resumeParams = new ResumeWorkflowHostParams
{
@ -219,12 +218,14 @@ public class HttpWorkflowsMiddleware(RequestDelegate next, IOptions<HttpActivity
return await triggerStore.FindManyAsync(triggerFilter, cancellationToken);
}
private async Task<IEnumerable<StoredBookmark>> FindBookmarksAsync(IServiceProvider serviceProvider, string bookmarkHash, CancellationToken cancellationToken)
private async Task<IEnumerable<StoredBookmark>> FindBookmarksAsync(IServiceProvider serviceProvider, string bookmarkHash, string? workflowInstanceId, string? correlationId, CancellationToken cancellationToken)
{
var bookmarkStore = serviceProvider.GetRequiredService<IBookmarkStore>();
var bookmarkFilter = new BookmarkFilter
{
Hash = bookmarkHash
Hash = bookmarkHash,
WorkflowInstanceId = workflowInstanceId,
CorrelationId = correlationId
};
return await bookmarkStore.FindManyAsync(bookmarkFilter, cancellationToken);
}

View file

@ -30,9 +30,6 @@
<None Update="Scenarios\WorkflowCompletion\fork.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Scenarios\HttpWorkflows\http-hello-world.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Scenarios\LogPersistenceModes\input-output-logging-1.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
@ -81,6 +78,9 @@
<None Update="Scenarios\WorkflowDefinitionReload\Workflows\http-workflow.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Scenarios\HttpWorkflows\Workflows\http-hello-world.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View file

@ -0,0 +1,40 @@
using System.Net;
using Elsa.Workflows.Contracts;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Workflows.ComponentTests.Scenarios.HttpWorkflows;
public class ResumeSpecificHttpWorkflowInstanceTests(App app) : AppComponentTest(app)
{
[Theory]
[InlineData("workflowInstanceId")]
[InlineData("correlationId")]
public async Task ResumingSpecificWorkflow_ShouldResumeSpecifiedWorkflow(string identifierKey)
{
var client = WorkflowServer.CreateHttpWorkflowClient();
// Start 3 instances.
var workflowInstanceId1 = await StartWorkflowAsync(client, identifierKey);
var workflowInstanceId2 = await StartWorkflowAsync(client, identifierKey);
var workflowInstanceId3 = await StartWorkflowAsync(client, identifierKey);
// Resume the 2nd instance.
var response = await ResumeWorkflowAsync(client, identifierKey, workflowInstanceId2);
// Response should be OK.
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
private async Task<string> StartWorkflowAsync(HttpClient client, string identifierKey)
{
var identityGenerator = Scope.ServiceProvider.GetRequiredService<IIdentityGenerator>();
var identifierValue = identityGenerator.GenerateId();
await client.GetStringAsync($"simple-http-api/start?{identifierKey}={identifierValue}");
return identifierValue;
}
private async Task<HttpResponseMessage> ResumeWorkflowAsync(HttpClient client, string identifierKey, string identifierValue)
{
return await client.GetAsync($"simple-http-api/resume?{identifierKey}={identifierValue}");
}
}

View file

@ -0,0 +1,30 @@
using Elsa.Http;
using Elsa.Workflows.Activities;
using Elsa.Workflows.Contracts;
using Microsoft.AspNetCore.Http;
namespace Elsa.Workflows.ComponentTests.Scenarios.HttpWorkflows.Workflows;
public class SimpleHttpApiWorkflow : WorkflowBase
{
protected override void Build(IWorkflowBuilder builder)
{
builder.Root = new Sequence
{
Activities =
[
new HttpEndpoint
{
Path = new("simple-http-api/start"),
SupportedMethods = new([HttpMethods.Get]),
CanStartWorkflow = true
},
new HttpEndpoint
{
Path = new("simple-http-api/resume"),
SupportedMethods = new([HttpMethods.Get])
}
]
};
}
}