Merge wt/step-logs-t2: Task 2 FlowTraceCollector low-priority lane

This commit is contained in:
Joseph Doherty
2026-09-09 11:33:15 -04:00
2 changed files with 109 additions and 2 deletions
@@ -1,5 +1,7 @@
using ZB.MOM.WW.Audit;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Debugging;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
namespace ZB.MOM.WW.ScadaBridge.Commons.Tests.Debugging;
@@ -16,6 +18,12 @@ public class FlowTraceCollectorTests
Target = i.ToString(),
};
/// <summary>Low-priority-lane helper (D7, 2026-09-09): a canonical row built through the
/// factory, so <c>DetailsJson</c> carries a real envelope around <paramref name="details"/>.</summary>
private static AuditEvent Evt(string? details = null) => ScadaBridgeAuditEventFactory.Create(
AuditChannel.ScriptTrace, AuditKind.ScriptRun, AuditStatus.Delivered,
target: "S", requestSummary: details);
[Fact]
public void Add_BeyondCap_KeepsFirstN_AndFlagsTruncated()
{
@@ -128,6 +136,65 @@ public class FlowTraceCollectorTests
Assert.Throws<ArgumentOutOfRangeException>(() => new FlowTraceCollector(maxBytes: 0));
}
/// <summary>D7 (2026-09-09): the low-priority lane stops short of the event cap so the
/// step rows a run emits at the end always have room.</summary>
[Fact]
public void Add_LowPriority_StopsBeforeTheReserve_AndSetsLogsTruncated()
{
var c = new FlowTraceCollector(maxEvents: 60, maxBytes: 1_000_000); // reserve = 50 → low-priority stops at 10
for (var i = 0; i < 12; i++) c.Add(Evt(), lowPriority: true);
Assert.Equal(10, c.Count);
Assert.True(c.LogsTruncated);
Assert.False(c.Truncated);
c.Add(Evt()); // a step row still fits
Assert.Equal(11, c.Count);
}
/// <summary>D7 (2026-09-09): the low-priority lane also stops short of the byte budget.</summary>
[Fact]
public void Add_LowPriority_RespectsByteShare()
{
var c = new FlowTraceCollector(maxEvents: 500, maxBytes: 1_000); // 75% = 750 chars
c.Add(Evt(details: new string('x', 600)), lowPriority: true);
c.Add(Evt(details: new string('x', 200)), lowPriority: true);
Assert.Equal(1, c.Count);
Assert.True(c.LogsTruncated);
}
/// <summary>D7 (2026-09-09): the reserve floors at 1, so a tiny cap still admits one log line.</summary>
[Fact]
public void Add_LowPriority_WithTinyCap_StillAllowsOne()
{
var c = new FlowTraceCollector(maxEvents: 20, maxBytes: 1_000_000); // max(1, 20-50) = 1
c.Add(Evt(), lowPriority: true);
Assert.Equal(1, c.Count);
Assert.False(c.LogsTruncated);
c.Add(Evt(), lowPriority: true);
Assert.Equal(1, c.Count);
Assert.True(c.LogsTruncated);
}
/// <summary>D7 (2026-09-09): <see cref="FlowTraceCollector.Complete"/> closes the low-priority
/// lane too — a late log line is dropped silently, not counted as truncated.</summary>
[Fact]
public void Add_LowPriority_IsNoOpAfterComplete()
{
var c = new FlowTraceCollector();
c.Add(Evt(), lowPriority: true);
c.Complete();
c.Add(Evt(), lowPriority: true);
Assert.Equal(1, c.Count);
Assert.False(c.LogsTruncated);
}
/// <summary>Upper-cases <c>DetailsJson</c> so a test can see the filtered copy land.</summary>
private sealed class UpperRedactor : IAuditRedactor
{