using MxGateway.Contracts.Proto; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Runtime; namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests.Runtime; /// /// Exhaustive table-driven tests for . Pinning the byte→uint /// mapping here protects against accidental drift — every Galaxy deployment that /// reaches the parity matrix in PR 5.2 depends on these specific OPC UA StatusCode /// values matching the legacy HistorianQualityMapper output. /// public sealed class StatusCodeMapTests { [Theory] [InlineData((byte)192, 0x00000000u)] // Good [InlineData((byte)216, 0x00D80000u)] // Good_LocalOverride [InlineData((byte)64, 0x40000000u)] // Uncertain [InlineData((byte)68, 0x40A40000u)] // Uncertain_LastUsableValue [InlineData((byte)80, 0x408D0000u)] // Uncertain_SensorNotAccurate [InlineData((byte)84, 0x408E0000u)] // Uncertain_EngineeringUnitsExceeded [InlineData((byte)88, 0x408F0000u)] // Uncertain_SubNormal [InlineData((byte)0, 0x80000000u)] // Bad [InlineData((byte)4, 0x80890000u)] // Bad_ConfigurationError [InlineData((byte)8, 0x808A0000u)] // Bad_NotConnected [InlineData((byte)12, 0x808B0000u)] // Bad_DeviceFailure [InlineData((byte)16, 0x808C0000u)] // Bad_SensorFailure [InlineData((byte)20, 0x80050000u)] // Bad_CommunicationError [InlineData((byte)24, 0x808D0000u)] // Bad_OutOfService [InlineData((byte)32, 0x80320000u)] // Bad_WaitingForInitialData public void FromQualityByte_KnownValues_MapToOpcUaStatusCode(byte input, uint expected) { StatusCodeMap.FromQualityByte(input).ShouldBe(expected); } [Theory] [InlineData((byte)200)] // Unknown Good — falls back to category bucket [InlineData((byte)100)] // Unknown Uncertain [InlineData((byte)40)] // Unknown Bad public void FromQualityByte_UnknownValues_FallBackToCategoryBucket(byte input) { var mapped = StatusCodeMap.FromQualityByte(input); if (input >= 192) mapped.ShouldBe(StatusCodeMap.Good); else if (input >= 64) mapped.ShouldBe(StatusCodeMap.Uncertain); else mapped.ShouldBe(StatusCodeMap.Bad); } [Fact] public void FromMxStatus_NullStatus_IsGood() { StatusCodeMap.FromMxStatus(null).ShouldBe(StatusCodeMap.Good); } [Fact] public void FromMxStatus_SuccessNonZero_IsGood() { var s = new MxStatusProxy { Success = 1 }; StatusCodeMap.FromMxStatus(s).ShouldBe(StatusCodeMap.Good); } [Fact] public void FromMxStatus_SuccessZero_DetailKnown_MapsToSpecificCode() { var s = new MxStatusProxy { Success = 0, Detail = 8 /* Bad_NotConnected */ }; StatusCodeMap.FromMxStatus(s).ShouldBe(StatusCodeMap.BadNotConnected); } [Fact] public void FromMxStatus_SuccessZero_DetailZero_DetectedByNonZero_IsCommunicationError() { var s = new MxStatusProxy { Success = 0, Detail = 0, RawDetectedBy = 3 }; StatusCodeMap.FromMxStatus(s).ShouldBe(StatusCodeMap.BadCommunicationError); } [Fact] public void FromMxStatus_SuccessZero_AllZero_IsBad() { var s = new MxStatusProxy(); StatusCodeMap.FromMxStatus(s).ShouldBe(StatusCodeMap.Bad); } [Fact] public void TopByteCategoryBits_StayWithinOpcUaConvention() { // Sanity check that every Bad code we mint actually has the Bad top byte (0x80…), // every Uncertain has 0x40…, every Good has 0x00…. Pins the OPC UA Part 4 invariant. StatusCodeMap.Good.ShouldBeLessThan(0x40000000u); StatusCodeMap.GoodLocalOverride.ShouldBeLessThan(0x40000000u); ((StatusCodeMap.Uncertain >> 30) & 0x3u).ShouldBe(1u); ((StatusCodeMap.UncertainLastUsableValue >> 30) & 0x3u).ShouldBe(1u); ((StatusCodeMap.Bad >> 30) & 0x3u).ShouldBe(2u); ((StatusCodeMap.BadNotConnected >> 30) & 0x3u).ShouldBe(2u); ((StatusCodeMap.BadOutOfService >> 30) & 0x3u).ShouldBe(2u); } }