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:
parent
0a76741755
commit
83d0e14248
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}");
|
||||
}
|
||||
}
|
||||
|
|
@ -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])
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue