elsa-core/test/unit/Elsa.Bpmn.UnitTests/BpmnDiagnosticEventNamesTests.cs
Sipke Schoorstra 952dfa05ff
feat(bpmn): project interpreter diagnostics onto the scope's execution log (#8058)
* feat(bpmn): project interpreter diagnostics onto the scope's execution log

Under Option A only bound work carries an activity id, so gateways, events and
flows had no per-element trace in the journal. BpmnScopeHost now projects each
new BpmnExecutionState.Diagnostics entry onto the scope's own execution log
before Prune() runs, keyed by element id, with a persisted high-water mark so
a resumed scope never re-emits one. The scope's own start and completion stay
out, since they are already journaled as the activity's own lifecycle. Event
names and the payload shape are documented as a public compatibility surface
for elsa-studio#1000 to mirror.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(bpmn): project element and flow diagnostics dropped by the FlowId exclusion

The diagnostics exclusion keyed on Kind == TokenEmitted && FlowId is null/empty
also dropped an error or cancel boundary's own token emission, since a
boundary fires without an inbound flow. Narrow the rule to skip only
diagnostics that name neither an element nor a flow -- the scope's own
terminal Completed summary -- so every diagnostic keyed on an element or a
flow, including a start event's and a boundary's, is projected.

Also make DiagnosticSequence resilient: TryParse instead of Parse, logging a
warning and skipping projection for an id that doesn't match diag:N rather
than faulting the evaluation. Add a reflection-based test that keeps
BpmnDiagnosticEventNames in lockstep with BpmnDiagnosticKind, and record the
diagnostics volume measurement in the wiki.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(bpmn): require exact diag:N ids and seed the diagnostics cursor from prior state

Reject any diagnostic id that is not the exact "diag:" prefix followed by a
non-negative integer, so a malformed id can no longer poison the durable
projection cursor and cause later, genuinely valid, lower-sequence
diagnostics to be skipped forever.

Also seed a missing cursor from the highest valid sequence in the scope's
prior persisted state instead of treating it as zero, so a scope persisted
before diagnostics projection existed does not replay every retained
historical diagnostic as new on its next evaluation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 19:01:06 -07:00

40 lines
1.6 KiB
C#

using System.Reflection;
using Bpmn.Model.State;
using Elsa.Bpmn.Hosting;
namespace Elsa.Bpmn.UnitTests;
/// <summary>
/// <see cref="BpmnDiagnosticEventNames"/> mirrors every member of <see cref="BpmnDiagnosticKind"/> by name so
/// <see cref="BpmnScopeHost"/> can derive an execution log event name from the enum without depending on the
/// library's integer values. Nothing in the compiler links the two, so a library upgrade that renames or adds a
/// member would drift silently; these tests catch that at build time instead.
/// </summary>
public class BpmnDiagnosticEventNamesTests
{
private static readonly IReadOnlyDictionary<string, string> EventNameConstants = typeof(BpmnDiagnosticEventNames)
.GetFields(BindingFlags.Public | BindingFlags.Static)
.Where(f => f.IsLiteral && !f.IsInitOnly && f.FieldType == typeof(string) && f.Name != nameof(BpmnDiagnosticEventNames.Source))
.ToDictionary(f => f.Name, f => (string)f.GetRawConstantValue()!);
[Fact]
public void EveryDiagnosticKindMember_HasAnIdenticallyNamedConstant()
{
var memberNames = Enum.GetNames<BpmnDiagnosticKind>();
Assert.All(memberNames, name =>
{
Assert.True(EventNameConstants.TryGetValue(name, out var value), $"'{name}' has no matching constant in {nameof(BpmnDiagnosticEventNames)}.");
Assert.Equal(name, value);
});
}
[Fact]
public void NoConstant_IsNotADiagnosticKindMember()
{
var memberNames = Enum.GetNames<BpmnDiagnosticKind>().ToHashSet();
Assert.All(EventNameConstants.Keys, name => Assert.Contains(name, memberNames));
}
}