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));
}
[Fact]
public void Complete_ClosesTheCollector_LaterAddsAreIgnored_AndNeverTruncate()
{
var c = new FlowTraceCollector(maxEvents: 2);
var first = NewEvent();
c.Add(first);
Assert.False(c.Completed);
var snapshot = c.Complete();
Assert.True(c.Completed);
Assert.Same(first, Assert.Single(snapshot));
c.Add(NewEvent());
c.Add(NewEvent());
c.Add(NewEvent());
Assert.Equal(1, c.Count);
Assert.False(c.Truncated);
Assert.Equal(snapshot, c.Complete()); // idempotent
}
private static AuditEvent NewEvent() => new()
{
EventId = Guid.NewGuid(),
OccurredAtUtc = DateTimeOffset.UtcNow,
Actor = "t",
Action = "ScriptTrace.ScriptRun",
Outcome = AuditOutcome.Success,
DetailsJson = "{}",
};
}