560b327ee1
v2-ci / build (push) Failing after 33s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
Imports the freshly-rebuilt ZB.MOM.WW.MxGateway.Client + ZB.MOM.WW.MxGateway.Contracts nupkgs (0.1.0) from /tmp/mxgw-dist. Replaces the vendored libs/ DLLs and the pre-restructure MxGateway.* namespaces across the runtime Galaxy driver, Galaxy.Browser, and their tests. Key changes: - nuget-packages/ added as a local feed via NuGet.config; .gitignore exempts it from the *.nupkg rule so the packages are tracked - Directory.Packages.props pins both packages at 0.1.0 - 4 csprojs swap <Reference HintPath="libs/...dll"/> for <PackageReference/> - 36 .cs files renamed `using MxGateway.*` -> `using ZB.MOM.WW.MxGateway.*` - libs/ removed (vendored DLLs + README.md) GalaxyBrowseSession rewritten around the new lazy API: - RootAsync calls GalaxyRepositoryClient.BrowseAsync (returns LazyBrowseNodes) and caches them by TagName instead of bulk-fetching the whole hierarchy - ExpandAsync looks up the cached LazyBrowseNode and calls its ExpandAsync, giving true one-wire-call-per-click instead of in-memory parent/child scan - _byGobjectId + _hasChildrenSet dropped (LazyBrowseNode carries HasChildrenHint) - AttributesAsync unchanged (already uses DiscoverHierarchyAsync MaxDepth=0) Tests: Galaxy.Tests 245/245, Galaxy.Browser.Tests 10/10, AdminUI.Tests 66/66. Pre-existing 12 solution errors unchanged (test sinks + Cli XML comments).
157 lines
7.9 KiB
C#
157 lines
7.9 KiB
C#
using ZB.MOM.WW.MxGateway.Contracts.Proto;
|
|
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Runtime;
|
|
// MxStatusCategory needed for Driver.Galaxy-003 regression tests.
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests.Runtime;
|
|
|
|
/// <summary>
|
|
/// Exhaustive table-driven tests for <see cref="StatusCodeMap"/>. 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 <c>HistorianQualityMapper</c> output.
|
|
/// </summary>
|
|
public sealed class StatusCodeMapTests
|
|
{
|
|
/// <summary>Verifies that known quality bytes map to the expected OPC UA status codes.</summary>
|
|
/// <param name="input">The Galaxy quality byte to map.</param>
|
|
/// <param name="expected">The expected OPC UA status code.</param>
|
|
[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);
|
|
}
|
|
|
|
/// <summary>Verifies that unknown quality bytes fall back to category bucket status codes.</summary>
|
|
/// <param name="input">The unknown Galaxy quality byte to categorize.</param>
|
|
[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);
|
|
}
|
|
|
|
// ===== Driver.Galaxy-003 regression: FromMxStatus uses IsSuccess() (category + success) =====
|
|
// The proto doc: "clients should branch on category, not on a specific success value."
|
|
// IsSuccess() requires BOTH success != 0 AND category == Ok — checking success alone
|
|
// would invert the mapping when the worker sets success=1 for an error code (the numeric
|
|
// value is NOT a boolean).
|
|
|
|
/// <summary>Verifies that null status maps to Good status code.</summary>
|
|
[Fact]
|
|
public void FromMxStatus_NullStatus_IsGood()
|
|
{
|
|
StatusCodeMap.FromMxStatus(null).ShouldBe(StatusCodeMap.Good);
|
|
}
|
|
|
|
/// <summary>Verifies that non-zero success with OK category maps to Good.</summary>
|
|
[Fact]
|
|
public void FromMxStatus_SuccessNonZeroAndCategoryOk_IsGood()
|
|
{
|
|
// Both conditions required — this is the canonical OK path.
|
|
var s = new MxStatusProxy { Success = 1, Category = MxStatusCategory.Ok };
|
|
StatusCodeMap.FromMxStatus(s).ShouldBe(StatusCodeMap.Good);
|
|
}
|
|
|
|
/// <summary>Verifies that non-zero success with non-OK category does not map to Good.</summary>
|
|
[Fact]
|
|
public void FromMxStatus_SuccessNonZeroButCategoryNotOk_IsNotGood()
|
|
{
|
|
// Bug fixed by Driver.Galaxy-003: success != 0 alone used to return Good even when
|
|
// category indicates an error. The proto says success is NOT a boolean — category wins.
|
|
var s = new MxStatusProxy { Success = 1, Category = MxStatusCategory.CommunicationError };
|
|
StatusCodeMap.FromMxStatus(s).ShouldNotBe(StatusCodeMap.Good);
|
|
}
|
|
|
|
/// <summary>Verifies that known detail codes map to their specific status codes.</summary>
|
|
[Fact]
|
|
public void FromMxStatus_SuccessZeroAndCategoryNotOk_DetailKnown_MapsToSpecificCode()
|
|
{
|
|
var s = new MxStatusProxy { Success = 0, Category = MxStatusCategory.OperationalError, Detail = 8 /* Bad_NotConnected */ };
|
|
StatusCodeMap.FromMxStatus(s).ShouldBe(StatusCodeMap.BadNotConnected);
|
|
}
|
|
|
|
/// <summary>Verifies that non-zero DetectedBy maps to communication error when detail is zero.</summary>
|
|
[Fact]
|
|
public void FromMxStatus_SuccessZero_DetailZero_DetectedByNonZero_IsCommunicationError()
|
|
{
|
|
var s = new MxStatusProxy { Success = 0, Detail = 0, RawDetectedBy = 3 };
|
|
StatusCodeMap.FromMxStatus(s).ShouldBe(StatusCodeMap.BadCommunicationError);
|
|
}
|
|
|
|
/// <summary>Verifies that all-zero status maps to Bad status code.</summary>
|
|
[Fact]
|
|
public void FromMxStatus_SuccessZero_AllZero_IsBad()
|
|
{
|
|
var s = new MxStatusProxy();
|
|
StatusCodeMap.FromMxStatus(s).ShouldBe(StatusCodeMap.Bad);
|
|
}
|
|
|
|
/// <summary>Verifies that top two bits of status codes follow OPC UA convention.</summary>
|
|
[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);
|
|
}
|
|
|
|
// ===== Driver.Galaxy-004 regression: ToQualityCategoryByte lives next to its inverse =====
|
|
|
|
/// <summary>Verifies that status codes extract to their OPC DA category byte equivalent.</summary>
|
|
/// <param name="statusCode">The OPC UA status code to convert.</param>
|
|
/// <param name="expected">The expected OPC DA category byte.</param>
|
|
[Theory]
|
|
[InlineData(0x00000000u, (byte)192)] // Good
|
|
[InlineData(0x00D80000u, (byte)192)] // GoodLocalOverride — still Good category
|
|
[InlineData(0x40000000u, (byte)64)] // Uncertain
|
|
[InlineData(0x408F0000u, (byte)64)] // UncertainSubNormal — still Uncertain category
|
|
[InlineData(0x80000000u, (byte)0)] // Bad
|
|
[InlineData(0x808A0000u, (byte)0)] // BadNotConnected — still Bad category
|
|
[InlineData(0x80020000u, (byte)0)] // BadInternalError — still Bad category
|
|
public void ToQualityCategoryByte_ExtractsTopTwoBitsAsOpcDaByte(uint statusCode, byte expected)
|
|
{
|
|
StatusCodeMap.ToQualityCategoryByte(statusCode).ShouldBe(expected);
|
|
}
|
|
|
|
/// <summary>Verifies that ToQualityCategoryByte is the right inverse of FromQualityByte for category bytes.</summary>
|
|
[Fact]
|
|
public void ToQualityCategoryByte_IsRightInverseOfFromQualityByte_ForCategoryBytes()
|
|
{
|
|
// The category bytes the probe watcher uses are 0, 64, 192. Round-trip: a value
|
|
// that came FROM those bytes should map back to the same byte.
|
|
StatusCodeMap.ToQualityCategoryByte(StatusCodeMap.FromQualityByte(0)).ShouldBe((byte)0);
|
|
StatusCodeMap.ToQualityCategoryByte(StatusCodeMap.FromQualityByte(64)).ShouldBe((byte)64);
|
|
StatusCodeMap.ToQualityCategoryByte(StatusCodeMap.FromQualityByte(192)).ShouldBe((byte)192);
|
|
}
|
|
}
|