371bcb3f91
Worker.Tests-008: moved the misplaced WorkerLogRedactor test out of VariantConverterTests into Bootstrap/WorkerLogRedactorTests. Worker.Tests-009: renamed 46 snake_case alarm-test methods to PascalCase Method_Scenario_Expectation. Worker.Tests-010: replaced a weak Assert.Contains with an exact assertion against the real diagnostic message and corrected the XML doc. Worker.Tests-011: renamed and re-documented a cancellation test that overstated what it proved. Worker.Tests-012: added an oversized-frame (MessageTooLarge) test; renamed the mislabeled zero-length-payload test. Worker.Tests-013: removed the fixed-100ms ThrowIfCompletedAsync helper; the caller now races runTask deterministically. Worker.Tests-014: consolidated duplicated test fakes/helpers (FakeRuntimeSession, NoopComApartmentInitializer, NoopEventSink, frame helpers) into a shared TestSupport namespace. Worker.Tests-015: added MxAccessEventQueue coverage for drain-all (maxEvents 0), empty-queue drain, and enqueue-after-fault. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
123 lines
5.6 KiB
C#
123 lines
5.6 KiB
C#
using System;
|
|
using MxGateway.Contracts.Proto;
|
|
using MxGateway.Worker.MxAccess;
|
|
|
|
namespace MxGateway.Worker.Tests.MxAccess;
|
|
|
|
/// <summary>
|
|
/// Pins the pure helpers used to translate AVEVA's wnwrapConsumer XML
|
|
/// payloads into proto-friendly fields. The COM-side I/O on
|
|
/// <see cref="WnWrapAlarmConsumer"/> needs an AVEVA install and is
|
|
/// covered by the Skip-gated probe (<c>WnWrapConsumerProbeTests</c>);
|
|
/// these unit tests cover everything that doesn't touch the live COM
|
|
/// surface.
|
|
/// </summary>
|
|
public sealed class AlarmRecordTransitionMapperTests
|
|
{
|
|
[Fact]
|
|
public void ComposeFullReference_WithProviderAndGroup_UsesProviderBangGroupDotNameFormat()
|
|
{
|
|
string reference = AlarmRecordTransitionMapper.ComposeFullReference(
|
|
providerName: "GalaxyAlarmProvider",
|
|
groupName: "Tank01",
|
|
alarmName: "Level.HiHi");
|
|
Assert.Equal("GalaxyAlarmProvider!Tank01.Level.HiHi", reference);
|
|
}
|
|
|
|
[Fact]
|
|
public void ComposeFullReference_WithEmptyProvider_DropsProvider()
|
|
{
|
|
string reference = AlarmRecordTransitionMapper.ComposeFullReference(
|
|
providerName: null, groupName: "Tank01", alarmName: "Level.HiHi");
|
|
Assert.Equal("Tank01.Level.HiHi", reference);
|
|
}
|
|
|
|
[Fact]
|
|
public void ComposeFullReference_WithEmptyGroup_DropsGroup()
|
|
{
|
|
string reference = AlarmRecordTransitionMapper.ComposeFullReference(
|
|
providerName: "GalaxyAlarmProvider", groupName: null, alarmName: "GlobalAlarm");
|
|
Assert.Equal("GalaxyAlarmProvider!GlobalAlarm", reference);
|
|
}
|
|
|
|
[Fact]
|
|
public void ComposeFullReference_WithEmptyProviderAndGroup_ReturnsAlarmName()
|
|
{
|
|
string reference = AlarmRecordTransitionMapper.ComposeFullReference(
|
|
providerName: null, groupName: null, alarmName: "Bare");
|
|
Assert.Equal("Bare", reference);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("UNACK_ALM", MxAlarmStateKind.UnackAlm)]
|
|
[InlineData("ACK_ALM", MxAlarmStateKind.AckAlm)]
|
|
[InlineData("UNACK_RTN", MxAlarmStateKind.UnackRtn)]
|
|
[InlineData("ACK_RTN", MxAlarmStateKind.AckRtn)]
|
|
[InlineData("unack_alm", MxAlarmStateKind.UnackAlm)] // case-insensitive
|
|
[InlineData(" ACK_ALM ", MxAlarmStateKind.AckAlm)] // trim
|
|
[InlineData("UNKNOWN", MxAlarmStateKind.Unspecified)]
|
|
[InlineData("", MxAlarmStateKind.Unspecified)]
|
|
[InlineData(null, MxAlarmStateKind.Unspecified)]
|
|
public void ParseStateKind_ForEachStateString_DecodesStateKind(string? input, MxAlarmStateKind expected)
|
|
{
|
|
Assert.Equal(expected, AlarmRecordTransitionMapper.ParseStateKind(input));
|
|
}
|
|
|
|
[Theory]
|
|
// First sighting: new alarm in *_ALM → Raise.
|
|
[InlineData(MxAlarmStateKind.Unspecified, MxAlarmStateKind.UnackAlm, AlarmTransitionKind.Raise)]
|
|
[InlineData(MxAlarmStateKind.Unspecified, MxAlarmStateKind.AckAlm, AlarmTransitionKind.Raise)]
|
|
// First sighting in *_RTN → Clear (unusual; missed the original raise).
|
|
[InlineData(MxAlarmStateKind.Unspecified, MxAlarmStateKind.UnackRtn, AlarmTransitionKind.Clear)]
|
|
[InlineData(MxAlarmStateKind.Unspecified, MxAlarmStateKind.AckRtn, AlarmTransitionKind.Clear)]
|
|
// Active → Cleared.
|
|
[InlineData(MxAlarmStateKind.UnackAlm, MxAlarmStateKind.UnackRtn, AlarmTransitionKind.Clear)]
|
|
[InlineData(MxAlarmStateKind.AckAlm, MxAlarmStateKind.AckRtn, AlarmTransitionKind.Clear)]
|
|
// Cleared → Active (re-trigger).
|
|
[InlineData(MxAlarmStateKind.UnackRtn, MxAlarmStateKind.UnackAlm, AlarmTransitionKind.Raise)]
|
|
[InlineData(MxAlarmStateKind.AckRtn, MxAlarmStateKind.UnackAlm, AlarmTransitionKind.Raise)]
|
|
// Unacked → Acked (operator ack).
|
|
[InlineData(MxAlarmStateKind.UnackAlm, MxAlarmStateKind.AckAlm, AlarmTransitionKind.Acknowledge)]
|
|
[InlineData(MxAlarmStateKind.UnackRtn, MxAlarmStateKind.AckRtn, AlarmTransitionKind.Acknowledge)]
|
|
// No-op (state unchanged) — caller is supposed to filter these out.
|
|
[InlineData(MxAlarmStateKind.UnackAlm, MxAlarmStateKind.UnackAlm, AlarmTransitionKind.Unspecified)]
|
|
// Current=Unspecified → Unspecified.
|
|
[InlineData(MxAlarmStateKind.UnackAlm, MxAlarmStateKind.Unspecified, AlarmTransitionKind.Unspecified)]
|
|
public void MapTransition_ForEachStatePair_DecidesProtoKind(
|
|
MxAlarmStateKind previous,
|
|
MxAlarmStateKind current,
|
|
AlarmTransitionKind expected)
|
|
{
|
|
Assert.Equal(expected, AlarmRecordTransitionMapper.MapTransition(previous, current));
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseTransitionTimestampUtc_WithValidXmlFields_AssemblesUtc()
|
|
{
|
|
// Captured payload from probe (2026-05-01): EDT producer, GMTOFFSET=240, DSTADJUST=0.
|
|
// Local 13:26:14.709 + 240 minutes (4h) = 17:26:14.709 UTC.
|
|
DateTime utc = AlarmRecordTransitionMapper.ParseTransitionTimestampUtc(
|
|
"2026/5/1", "13:26:14.709", gmtOffsetMinutes: 240, dstAdjustMinutes: 0);
|
|
|
|
Assert.Equal(DateTimeKind.Utc, utc.Kind);
|
|
Assert.Equal(2026, utc.Year);
|
|
Assert.Equal(5, utc.Month);
|
|
Assert.Equal(1, utc.Day);
|
|
Assert.Equal(17, utc.Hour);
|
|
Assert.Equal(26, utc.Minute);
|
|
Assert.Equal(14, utc.Second);
|
|
Assert.Equal(709, utc.Millisecond);
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseTransitionTimestampUtc_WithUnparseableInputs_ReturnsMinValue()
|
|
{
|
|
Assert.Equal(DateTime.MinValue,
|
|
AlarmRecordTransitionMapper.ParseTransitionTimestampUtc(null, null, 0, 0));
|
|
Assert.Equal(DateTime.MinValue,
|
|
AlarmRecordTransitionMapper.ParseTransitionTimestampUtc("not a date", "13:00:00", 0, 0));
|
|
Assert.Equal(DateTime.MinValue,
|
|
AlarmRecordTransitionMapper.ParseTransitionTimestampUtc("2026/5/1", "not a time", 0, 0));
|
|
}
|
|
}
|