Skip to content

Commit

Permalink
Add new HTTP workflow tests and enhance middleware
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
sfmskywalker committed Aug 16, 2024
1 parent 0a76741 commit 83d0e14
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 15 deletions.
25 changes: 13 additions & 12 deletions src/modules/Elsa.Http/Middleware/HttpWorkflowsMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,16 @@ public async Task InvokeAsync(HttpContext httpContext, IServiceProvider serviceP
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);

if (lookupResult != null)
{
var triggers = lookupResult.Triggers;

if (triggers?.Count > 1)
if (triggers.Count > 1)
{
await HandleMultipleWorkflowsFoundAsync(httpContext, () => triggers.Select(x => new
{
Expand All @@ -85,16 +87,16 @@ public async Task InvokeAsync(HttpContext httpContext, IServiceProvider serviceP
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)
{
Expand All @@ -109,7 +111,7 @@ public async Task InvokeAsync(HttpContext httpContext, IServiceProvider serviceP

if (bookmark != null)
{
await ResumeWorkflowAsync(httpContext, bookmark, input);
await ResumeWorkflowAsync(httpContext, bookmark, correlationId, input);
return;
}

Expand All @@ -131,7 +133,7 @@ public async Task InvokeAsync(HttpContext httpContext, IServiceProvider serviceP
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;
Expand All @@ -144,8 +146,6 @@ private async Task StartWorkflowAsync(HttpContext httpContext, StoredTrigger tri
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,
Expand All @@ -160,7 +160,7 @@ await ExecuteWithinTimeoutAsync(async ct =>
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;
Expand Down Expand Up @@ -193,7 +193,6 @@ private async Task ResumeWorkflowAsync(HttpContext httpContext, StoredBookmark b

await ExecuteWithinTimeoutAsync(async ct =>
{
var correlationId = await GetCorrelationIdAsync(serviceProvider, httpContext, ct);
var cancellationTokens = new CancellationTokens(ct, ct);
var resumeParams = new ResumeWorkflowHostParams
{
Expand All @@ -219,12 +218,14 @@ private async Task<IEnumerable<StoredTrigger>> FindTriggersAsync(IServiceProvide
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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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>
Original file line number Diff line number Diff line change
@@ -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}");
}
}
Original file line number Diff line number Diff line change
@@ -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])
}
]
};
}
}

0 comments on commit 83d0e14

Please sign in to comment.