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.
100 lines
5.1 KiB
C#
100 lines
5.1 KiB
C#
using ZB.MOM.WW.ScadaBridge.Commons.Messages.InboundApi;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Types.Debugging;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
|
|
using ZB.MOM.WW.ScadaBridge.Communication.Grpc;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.Communication.Tests;
|
|
|
|
/// <summary>
|
|
/// Flow Debugger (design 2026-09-05 §5.2/§5.4): the site_command.proto trace fields — the
|
|
/// <c>trace</c> flag on both route requests, and <c>child_execution_id</c> +
|
|
/// <c>trace_events</c> on the call response — round-trip through
|
|
/// <see cref="SiteCommandDtoMapper"/>, and an old-shape DTO (no fields set) maps to the
|
|
/// untraced defaults.
|
|
/// </summary>
|
|
public class SiteCommandDtoMapperTraceTests
|
|
{
|
|
private static readonly DateTimeOffset T = new(2026, 9, 5, 12, 0, 0, TimeSpan.Zero);
|
|
|
|
/// <summary>Verifies that a route-to-call response round-trips its child execution id and trace events.</summary>
|
|
[Fact]
|
|
public void RouteToCallResponse_RoundTrips_ChildExecutionId_AndTraceEvents()
|
|
{
|
|
var child = Guid.NewGuid();
|
|
var run = ScadaBridgeAuditEventFactory.Create(AuditChannel.ScriptTrace, AuditKind.ScriptRun, AuditStatus.Delivered,
|
|
occurredAtUtc: T.UtcDateTime, actor: "ScriptActor:S", target: "S", sourceNode: "node-b",
|
|
executionId: child, parentExecutionId: Guid.NewGuid(), sourceSiteId: "site-a", durationMs: 40,
|
|
requestSummary: "{\"x\":1}", responseSummary: "\"ok\"",
|
|
extra: new ScriptTraceExtra { TriggerKind = "call" }.ToJson());
|
|
var write = ScadaBridgeAuditEventFactory.Create(AuditChannel.ScriptTrace, AuditKind.AttributeWrite, AuditStatus.Delivered,
|
|
occurredAtUtc: T.UtcDateTime.AddMilliseconds(5), actor: "ScriptActor:S", target: "A", executionId: child,
|
|
extra: new ScriptTraceExtra { Writes = [new AttributeWriteDetail("A", "1", true, null)] }.ToJson());
|
|
var original = new RouteToCallResponse("c", true, 7L, null, T, child, [run, write]);
|
|
|
|
var restored = SiteCommandDtoMapper.FromProto(SiteCommandDtoMapper.ToProto(original));
|
|
|
|
Assert.Equal(child, restored.ChildExecutionId);
|
|
Assert.NotNull(restored.TraceEvents);
|
|
Assert.Equal(2, restored.TraceEvents!.Count);
|
|
Assert.Equal(run.Action, restored.TraceEvents[0].Action);
|
|
Assert.Equal(run.DetailsJson, restored.TraceEvents[0].DetailsJson);
|
|
Assert.Equal(write.EventId, restored.TraceEvents[1].EventId);
|
|
Assert.Equal(original, restored with { TraceEvents = original.TraceEvents });
|
|
Assert.Equal(run, restored.TraceEvents[0]);
|
|
}
|
|
|
|
/// <summary>Verifies that old-shape DTOs map to untraced defaults.</summary>
|
|
[Fact]
|
|
public void OldShapeDtos_MapToUntracedDefaults()
|
|
{
|
|
// A DTO from a site/central that predates the fields: proto3 defaults everywhere.
|
|
var call = SiteCommandDtoMapper.FromProto(new RouteToCallRequestDto
|
|
{
|
|
CorrelationId = "c", InstanceUniqueName = "i", ScriptName = "s",
|
|
Timestamp = Google.Protobuf.WellKnownTypes.Timestamp.FromDateTimeOffset(T),
|
|
});
|
|
Assert.False(call.Trace);
|
|
|
|
var set = SiteCommandDtoMapper.FromProto(new RouteToSetAttributesRequestDto
|
|
{
|
|
CorrelationId = "c", InstanceUniqueName = "i",
|
|
Timestamp = Google.Protobuf.WellKnownTypes.Timestamp.FromDateTimeOffset(T),
|
|
});
|
|
Assert.False(set.Trace);
|
|
|
|
var reply = SiteCommandDtoMapper.FromProto(new RouteToCallResponseDto
|
|
{
|
|
CorrelationId = "c", Success = true,
|
|
Timestamp = Google.Protobuf.WellKnownTypes.Timestamp.FromDateTimeOffset(T),
|
|
});
|
|
Assert.Null(reply.ChildExecutionId);
|
|
Assert.Null(reply.TraceEvents);
|
|
}
|
|
|
|
/// <summary>Verifies that the trace flag propagates on both request types.</summary>
|
|
[Fact]
|
|
public void TraceFlag_PropagatesOnBothRequests()
|
|
{
|
|
var call = new RouteToCallRequest("c", "i", "s", null, T, Guid.NewGuid(), Trace: true);
|
|
Assert.True(SiteCommandDtoMapper.ToProto(call).Trace);
|
|
Assert.True(SiteCommandDtoMapper.FromProto(SiteCommandDtoMapper.ToProto(call)).Trace);
|
|
|
|
var set = new RouteToSetAttributesRequest("c", "i", new Dictionary<string, string> { ["a"] = "1" }, T, Guid.NewGuid(), Trace: true);
|
|
Assert.True(SiteCommandDtoMapper.ToProto(set).Trace);
|
|
Assert.True(SiteCommandDtoMapper.FromProto(SiteCommandDtoMapper.ToProto(set)).Trace);
|
|
}
|
|
|
|
/// <summary>Verifies that an empty trace events list maps to null rather than an empty list.</summary>
|
|
[Fact]
|
|
public void EmptyTraceEventsList_MapsToNull_NotEmpty()
|
|
{
|
|
// An untraced run sends none; the CLR side distinguishes "none" (null) so the UI can
|
|
// label the site lane — an empty list would read as "traced, did nothing".
|
|
var original = new RouteToCallResponse("c", true, null, null, T, Guid.NewGuid(), Array.Empty<ZB.MOM.WW.Audit.AuditEvent>());
|
|
var restored = SiteCommandDtoMapper.FromProto(SiteCommandDtoMapper.ToProto(original));
|
|
Assert.Null(restored.TraceEvents);
|
|
Assert.Equal(original.ChildExecutionId, restored.ChildExecutionId);
|
|
}
|
|
}
|