using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; using ZB.MOM.WW.OtOpcUa.Driver.AbCip; namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests; /// /// CONV-4 — keys per-host resilience (bulkhead / circuit /// breaker). It must resolve an authored tag (by its RawPath) to its OWN device host, not fall back /// to the first device — otherwise a broken device B trips device A's breaker. Unknown refs keep the /// current first-device fallback. /// [Trait("Category", "Unit")] public sealed class AbCipResolveHostTests { private const string DeviceA = "ab://10.0.0.5/1,0"; private const string DeviceB = "ab://10.0.0.6/1,0"; private static AbCipDriver NewDriver(params RawTagEntry[] rawTags) => new( new AbCipDriverOptions { Devices = [new AbCipDeviceOptions(DeviceA), new AbCipDeviceOptions(DeviceB)], RawTags = rawTags, Probe = new AbCipProbeOptions { Enabled = false }, }, "abcip-1", new FakeAbCipTagFactory()); /// An authored tag routed to device B resolves to device B's host, not the first device. [Fact] public async Task ResolveHost_TagRoutedToDeviceB_ReturnsItsOwnDeviceHost() { var drv = NewDriver(new RawTagEntry( RawPath: "line1/motorB/speed", TagConfig: """{"tagPath":"Motor1.Speed","dataType":"DInt"}""", WriteIdempotent: false, DeviceName: DeviceB)); await drv.InitializeAsync("{}", CancellationToken.None); drv.ResolveHost("line1/motorB/speed").ShouldBe(DeviceB); } /// An unresolvable ref keeps the current first-device fallback. [Fact] public void ResolveHost_UnknownRef_KeepsCurrentFallback() { var drv = NewDriver(); drv.ResolveHost("totally-unknown").ShouldBe(DeviceA); } }