513fa1aa16
Comment/XML-doc changes only: - add summaries to ~10k test methods, fixtures, fakes and helpers (+ <param> on theories, <returns> on async Task methods, <inheritdoc/> on interface implementations the checker can resolve) - strip ~2.6k project-tracking references (WP*, P*-*, row-NN, Task N, #NN, Component-0NN) from comments, keeping the explanatory text; reword false positives in comments only - real docs on fakes implementing BCL/library interfaces (IDisposable, ILogger, IOptionsMonitor, IAsyncLifetime) - reword the last 5 src/ false positives (CWE-1236, failures-1, year-0001, M365) Only non-comment edits: four one-line nested classes split across lines so members can carry docs. Build 0 errors / 0 warnings.
148 lines
6.6 KiB
C#
148 lines
6.6 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// Flow Debugger step-logs plan (2026-09-09): the Commons log-line vocabulary —
|
|
/// exception detail on a step (D3), and the <c>ScriptTrace.ScriptLog</c> row projecting
|
|
/// onto a <see cref="FlowLogLine"/> rather than a node (D1).
|
|
/// </summary>
|
|
public class FlowStepProjectorLogLineTests
|
|
{
|
|
/// <summary>Verifies that projecting an audit event carries its error detail onto the step.</summary>
|
|
[Fact]
|
|
public void Project_CarriesErrorDetail()
|
|
{
|
|
var evt = ScadaBridgeAuditEventFactory.Create(AuditChannel.ScriptTrace, AuditKind.ScriptRun, AuditStatus.Failed,
|
|
target: "S", executionId: Guid.NewGuid(), errorMessage: "boom", errorDetail: "System.InvalidOperationException: boom\n at X");
|
|
|
|
var step = FlowStepProjector.Project(evt);
|
|
|
|
Assert.Equal("boom", step.ErrorMessage);
|
|
Assert.StartsWith("System.InvalidOperationException", step.ErrorDetail);
|
|
}
|
|
|
|
/// <summary>Verifies that the projected error detail is null when the audit event has none.</summary>
|
|
[Fact]
|
|
public void Project_ErrorDetail_IsNullWhenAbsent()
|
|
{
|
|
var evt = ScadaBridgeAuditEventFactory.Create(AuditChannel.ScriptTrace, AuditKind.ScriptRun, AuditStatus.Delivered, target: "S");
|
|
|
|
Assert.Null(FlowStepProjector.Project(evt).ErrorDetail);
|
|
}
|
|
|
|
/// <summary>Verifies that a script log row projects to the unknown kind and never to a step kind of its own.</summary>
|
|
[Fact]
|
|
public void ScriptLog_ProjectsToUnknownKind_NeverItsOwnStepKind()
|
|
{
|
|
// Mixed-version rule (design §4): an old central renders a ScriptLog row as an Unknown node
|
|
// rather than dropping it. No FlowStepKind is added for it.
|
|
Assert.Equal(FlowStepKind.Unknown, FlowStepProjector.ResolveKind(nameof(AuditChannel.ScriptTrace), nameof(AuditKind.ScriptLog)));
|
|
Assert.False(Enum.GetNames<FlowStepKind>().Contains("ScriptLog"));
|
|
}
|
|
|
|
/// <summary>Verifies that a row is a log line only for the script trace channel with the script log kind.</summary>
|
|
[Fact]
|
|
public void IsLogLine_TrueOnlyForScriptTraceScriptLog()
|
|
{
|
|
var log = ScadaBridgeAuditEventFactory.Create(AuditChannel.ScriptTrace, AuditKind.ScriptLog, AuditStatus.Delivered, target: "S");
|
|
var run = ScadaBridgeAuditEventFactory.Create(AuditChannel.ScriptTrace, AuditKind.ScriptRun, AuditStatus.Delivered, target: "S");
|
|
|
|
Assert.True(FlowStepProjector.IsLogLine(log));
|
|
Assert.False(FlowStepProjector.IsLogLine(run));
|
|
}
|
|
|
|
/// <summary>Verifies that rows on other channels are not log lines.</summary>
|
|
[Fact]
|
|
public void IsLogLine_FalseForOtherChannels()
|
|
{
|
|
var outbound = ScadaBridgeAuditEventFactory.Create(AuditChannel.ApiOutbound, AuditKind.ApiCall, AuditStatus.Delivered, target: "S");
|
|
|
|
Assert.False(FlowStepProjector.IsLogLine(outbound));
|
|
}
|
|
|
|
/// <summary>Verifies that converting a row to a log line reads its level, source and exception.</summary>
|
|
[Fact]
|
|
public void ToLogLine_ReadsLevelSourceAndException()
|
|
{
|
|
var exec = Guid.NewGuid();
|
|
var extra = new ScriptTraceExtra { LogLevel = "warn", LogSource = "runtime", StepEventId = Guid.NewGuid(), Seq = 3 };
|
|
var evt = ScadaBridgeAuditEventFactory.Create(AuditChannel.ScriptTrace, AuditKind.ScriptLog, AuditStatus.Delivered,
|
|
target: "S", executionId: exec, requestSummary: "acked Uncertain", errorMessage: "inner",
|
|
errorDetail: "System.Exception: inner", extra: extra.ToJson());
|
|
|
|
var line = FlowStepProjector.ToLogLine(evt);
|
|
|
|
Assert.Equal(evt.EventId, line.EventId);
|
|
Assert.Equal(exec, line.ExecutionId);
|
|
Assert.Equal(evt.OccurredAtUtc, line.OccurredAtUtc);
|
|
Assert.Equal("warn", line.Level);
|
|
Assert.Equal("runtime", line.Source);
|
|
Assert.Equal("acked Uncertain", line.Message);
|
|
Assert.Equal("inner", line.ExceptionMessage);
|
|
Assert.Equal("System.Exception: inner", line.ExceptionDetail);
|
|
Assert.Equal(extra.StepEventId, line.StepEventId);
|
|
Assert.Equal(3, line.Seq);
|
|
}
|
|
|
|
/// <summary>Verifies that converting a row to a log line defaults the level and source when the extra payload is missing.</summary>
|
|
[Fact]
|
|
public void ToLogLine_DefaultsLevelAndSourceWhenExtraMissing()
|
|
{
|
|
var evt = ScadaBridgeAuditEventFactory.Create(AuditChannel.ScriptTrace, AuditKind.ScriptLog, AuditStatus.Delivered, target: "S");
|
|
|
|
var line = FlowStepProjector.ToLogLine(evt);
|
|
|
|
Assert.Equal("info", line.Level);
|
|
Assert.Equal("script", line.Source);
|
|
Assert.Equal(string.Empty, line.Message);
|
|
Assert.Null(line.ExceptionMessage);
|
|
Assert.Null(line.ExceptionDetail);
|
|
Assert.Null(line.StepEventId);
|
|
Assert.Null(line.Seq);
|
|
}
|
|
|
|
/// <summary>Verifies that the log members of the script trace extra round-trip through JSON.</summary>
|
|
[Fact]
|
|
public void ScriptTraceExtra_LogMembers_RoundTripThroughJson()
|
|
{
|
|
var extra = new ScriptTraceExtra { LogLevel = "error", LogSource = "script", StepEventId = Guid.NewGuid(), Seq = 12 };
|
|
|
|
var parsed = ScriptTraceExtra.TryParse(extra.ToJson());
|
|
|
|
Assert.NotNull(parsed);
|
|
Assert.Equal("error", parsed!.LogLevel);
|
|
Assert.Equal("script", parsed.LogSource);
|
|
Assert.Equal(extra.StepEventId, parsed.StepEventId);
|
|
Assert.Equal(12, parsed.Seq);
|
|
}
|
|
|
|
/// <summary>Verifies that a flow step's logs and error detail default to null.</summary>
|
|
[Fact]
|
|
public void FlowStep_LogsAndErrorDetail_DefaultToNull()
|
|
{
|
|
var step = new FlowStep(Guid.NewGuid(), null, null, FlowStepKind.ScriptRun, FlowLane.Site, "S", null,
|
|
DateTimeOffset.UnixEpoch, null, FlowStepOutcome.Ok, null, null, null, null, null, null, null, null);
|
|
|
|
Assert.Null(step.ErrorDetail);
|
|
Assert.Null(step.Logs);
|
|
|
|
var line = new FlowLogLine(Guid.NewGuid(), null, DateTimeOffset.UnixEpoch, "info", "script", "m", null, null, null, null);
|
|
Assert.Single((step with { Logs = [line] }).Logs!);
|
|
}
|
|
|
|
/// <summary>Verifies that the log truncation members of a flow graph default to off.</summary>
|
|
[Fact]
|
|
public void FlowGraph_LogTruncationMembers_DefaultToOff()
|
|
{
|
|
var graph = new FlowGraph([], [], false, false);
|
|
|
|
Assert.False(graph.LogsTruncated);
|
|
Assert.Equal(0, graph.UnattachedLogLines);
|
|
Assert.False(FlowGraph.Empty.LogsTruncated);
|
|
Assert.Equal(0, FlowGraph.Empty.UnattachedLogLines);
|
|
}
|
|
}
|