fix(workflows): default CreateBookmarkArgs.IncludeActivityInstanceId to true (#8157)

Unset CreateBookmarkArgs objects were hashing without ActivityInstanceId,
so CreateBookmark(stimulus) and new CreateBookmarkArgs { Stimulus = … }
silently diverged from CreateBookmark() and the documented overload default.

Explicit IncludeActivityInstanceId = false call sites are unchanged.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This commit is contained in:
Sipke Schoorstra 2026-09-14 16:10:12 +02:00 committed by GitHub
parent 3c4549752d
commit 5193bea262
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 114 additions and 2 deletions

View file

@ -31,9 +31,11 @@ public class CreateBookmarkArgs
public bool AutoBurn { get; set; } = true;
/// <summary>
/// Whether the activity instance ID should be included in the bookmark payload.
/// Whether the activity instance ID should be included in the stimulus hash. Defaults to <c>true</c>
/// so unset <see cref="CreateBookmarkArgs"/> objects match <c>CreateBookmark()</c> and documented overload defaults.
/// Set to <c>false</c> for broadcast-style bookmarks that must resume regardless of activity instance.
/// </summary>
public bool IncludeActivityInstanceId { get; set; }
public bool IncludeActivityInstanceId { get; set; } = true;
/// <summary>
/// Whether the activity being resumed should be automatically completed if CallBack is not specified.

View file

@ -0,0 +1,110 @@
using Elsa.Testing.Shared;
using Elsa.Workflows.Activities;
using Elsa.Workflows.Models;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.Workflows.Core.UnitTests.Models;
public class CreateBookmarkArgsTests
{
[Fact]
public void NewCreateBookmarkArgs_IncludeActivityInstanceId_DefaultsToTrue()
{
var args = new CreateBookmarkArgs();
Assert.True(args.IncludeActivityInstanceId);
}
[Fact]
public void UnsetIncludeActivityInstanceId_InObjectInitializer_IsTrue()
{
var args = new CreateBookmarkArgs
{
Stimulus = "order-created"
};
Assert.True(args.IncludeActivityInstanceId);
}
[Fact]
public void ExplicitFalse_IsPreserved()
{
var args = new CreateBookmarkArgs
{
Stimulus = "order-created",
IncludeActivityInstanceId = false
};
Assert.False(args.IncludeActivityInstanceId);
}
[Fact]
public async Task CreateBookmark_StimulusConvenience_MatchesParameterlessIdentityInclusion()
{
var context = await CreateContextAsync();
var noArgs = context.CreateBookmark();
var withStimulus = context.CreateBookmark("order-created");
Assert.Contains(context.Id, noArgs.Hash);
Assert.Contains(context.Id, withStimulus.Hash);
}
[Fact]
public async Task CreateBookmark_UnsetArgs_MatchesStimulusConvenienceHash()
{
var context = await CreateContextAsync();
const string stimulus = "order-created";
var fromConvenience = context.CreateBookmark(stimulus);
var fromUnsetArgs = context.CreateBookmark(new CreateBookmarkArgs
{
Stimulus = stimulus
});
var fromCallbackOverload = context.CreateBookmark(stimulus, callback: null);
Assert.Equal(fromConvenience.Hash, fromUnsetArgs.Hash);
Assert.Equal(fromConvenience.Hash, fromCallbackOverload.Hash);
Assert.Contains(context.Id, fromUnsetArgs.Hash);
}
[Fact]
public async Task CreateBookmark_ExplicitFalse_ExcludesActivityInstanceIdFromHash()
{
var context = await CreateContextAsync();
const string stimulus = "order-created";
var included = context.CreateBookmark(stimulus);
var excluded = context.CreateBookmark(new CreateBookmarkArgs
{
Stimulus = stimulus,
IncludeActivityInstanceId = false
});
Assert.Contains(context.Id, included.Hash);
Assert.DoesNotContain(context.Id, excluded.Hash);
Assert.NotEqual(included.Hash, excluded.Hash);
}
private static Task<ActivityExecutionContext> CreateContextAsync()
{
var fixture = new ActivityTestFixture(new WriteLine("test"));
fixture.ConfigureServices(services =>
{
services.AddSingleton<IHasher, ConcatHasher>();
services.AddSingleton<IIdentityGenerator, GuidIdentityGenerator>();
});
return fixture.BuildAsync();
}
/// <summary>
/// Encodes hash inputs so tests can see whether the activity instance ID was passed through.
/// </summary>
private sealed class ConcatHasher : IHasher
{
public string Hash(string value) => value;
public string Hash(params object?[] values) =>
string.Join("|", values.Select(value => value?.ToString() ?? string.Empty));
}
}