using ZB.MOM.WW.Audit; using ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts; namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests.Scripts; /// Flow Debugger design 2026-09-05 ยง5.4: the per-run inline trace sink. public class FlowTraceCollectorTests { private static AuditEvent Evt(int i) => new() { EventId = Guid.NewGuid(), OccurredAtUtc = DateTimeOffset.UtcNow, Actor = "t", Action = "ScriptTrace.AttributeWrite", Outcome = AuditOutcome.Success, Target = i.ToString(), }; [Fact] public void Add_BeyondCap_KeepsFirstN_AndFlagsTruncated() { var c = new FlowTraceCollector(maxEvents: 500); Assert.False(c.Truncated); for (var i = 0; i < 501; i++) c.Add(Evt(i)); var snap = c.Snapshot(); Assert.Equal(500, snap.Count); Assert.True(c.Truncated); Assert.Equal("0", snap[0].Target); Assert.Equal("499", snap[499].Target); } [Fact] public void Snapshot_IsACopy_InEmissionOrder() { var c = new FlowTraceCollector(); c.Add(Evt(1)); var first = c.Snapshot(); c.Add(Evt(2)); Assert.Single(first); Assert.Equal(2, c.Count); Assert.Equal(new[] { "1", "2" }, c.Snapshot().Select(e => e.Target)); } [Fact] public async Task ConcurrentAdds_NeverThrow_AndNeverExceedCap() { var c = new FlowTraceCollector(maxEvents: 100); var tasks = Enumerable.Range(0, 8).Select(t => Task.Run(() => { for (var i = 0; i < 50; i++) c.Add(Evt(t * 100 + i)); })); await Task.WhenAll(tasks); Assert.Equal(100, c.Snapshot().Count); Assert.True(c.Truncated); } [Fact] public void DefaultCap_Is500_AndNonPositiveCapIsRejected() { Assert.Equal(500, FlowTraceCollector.DefaultMaxEvents); Assert.Throws(() => new FlowTraceCollector(0)); } }