using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; using ZB.MOM.WW.OtOpcUa.Driver.AbCip; using ZB.MOM.WW.OtOpcUa.Driver.AbCip.PlcFamilies; namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests; [Trait("Category", "Unit")] public sealed class AbCipDriverTests { [Fact] public void DriverType_is_AbCip() { var drv = new AbCipDriver(new AbCipDriverOptions(), "drv-1"); drv.DriverType.ShouldBe("AbCip"); drv.DriverInstanceId.ShouldBe("drv-1"); } [Fact] public async Task InitializeAsync_with_empty_devices_succeeds_and_marks_healthy() { var drv = new AbCipDriver(new AbCipDriverOptions(), "drv-1"); await drv.InitializeAsync("{}", CancellationToken.None); drv.GetHealth().State.ShouldBe(DriverState.Healthy); } [Fact] public async Task InitializeAsync_registers_each_device_with_its_family_profile() { var drv = new AbCipDriver(new AbCipDriverOptions { Devices = [ new AbCipDeviceOptions("ab://10.0.0.5/1,0", AbCipPlcFamily.ControlLogix), new AbCipDeviceOptions("ab://10.0.0.6/", AbCipPlcFamily.Micro800), ], }, "drv-1"); await drv.InitializeAsync("{}", CancellationToken.None); drv.DeviceCount.ShouldBe(2); drv.GetDeviceState("ab://10.0.0.5/1,0")!.Profile.ShouldBe(AbCipPlcFamilyProfile.ControlLogix); drv.GetDeviceState("ab://10.0.0.6/")!.Profile.ShouldBe(AbCipPlcFamilyProfile.Micro800); } [Fact] public async Task InitializeAsync_with_malformed_host_address_faults() { var drv = new AbCipDriver(new AbCipDriverOptions { Devices = [new AbCipDeviceOptions("not-a-valid-address")], }, "drv-1"); await Should.ThrowAsync( () => drv.InitializeAsync("{}", CancellationToken.None)); drv.GetHealth().State.ShouldBe(DriverState.Faulted); } [Fact] public async Task ShutdownAsync_clears_devices_and_marks_unknown() { var drv = new AbCipDriver(new AbCipDriverOptions { Devices = [new AbCipDeviceOptions("ab://10.0.0.5/1,0")], }, "drv-1"); await drv.InitializeAsync("{}", CancellationToken.None); drv.DeviceCount.ShouldBe(1); await drv.ShutdownAsync(CancellationToken.None); drv.DeviceCount.ShouldBe(0); drv.GetHealth().State.ShouldBe(DriverState.Unknown); } [Fact] public async Task ReinitializeAsync_cycles_devices() { var drv = new AbCipDriver(new AbCipDriverOptions { Devices = [new AbCipDeviceOptions("ab://10.0.0.5/1,0")], }, "drv-1"); await drv.InitializeAsync("{}", CancellationToken.None); await drv.ReinitializeAsync("{}", CancellationToken.None); drv.DeviceCount.ShouldBe(1); drv.GetHealth().State.ShouldBe(DriverState.Healthy); } [Fact] public void Family_profiles_expose_expected_defaults() { AbCipPlcFamilyProfile.ControlLogix.LibplctagPlcAttribute.ShouldBe("controllogix"); AbCipPlcFamilyProfile.ControlLogix.DefaultConnectionSize.ShouldBe(4002); AbCipPlcFamilyProfile.ControlLogix.DefaultCipPath.ShouldBe("1,0"); AbCipPlcFamilyProfile.Micro800.DefaultCipPath.ShouldBe(""); // no backplane routing AbCipPlcFamilyProfile.Micro800.SupportsRequestPacking.ShouldBeFalse(); AbCipPlcFamilyProfile.Micro800.SupportsConnectedMessaging.ShouldBeFalse(); AbCipPlcFamilyProfile.CompactLogix.DefaultConnectionSize.ShouldBe(504); AbCipPlcFamilyProfile.GuardLogix.LibplctagPlcAttribute.ShouldBe("controllogix"); } [Fact] public void PlcTagHandle_IsInvalid_for_zero_or_negative_native_id() { PlcTagHandle.FromNative(-5).IsInvalid.ShouldBeTrue(); PlcTagHandle.FromNative(0).IsInvalid.ShouldBeTrue(); PlcTagHandle.FromNative(42).IsInvalid.ShouldBeFalse(); } [Fact] public void PlcTagHandle_Dispose_is_idempotent() { var h = PlcTagHandle.FromNative(42); h.Dispose(); h.Dispose(); // must not throw } [Fact] public void AbCipDataType_maps_atomics_to_driver_types() { AbCipDataType.Bool.ToDriverDataType().ShouldBe(DriverDataType.Boolean); AbCipDataType.DInt.ToDriverDataType().ShouldBe(DriverDataType.Int32); AbCipDataType.Real.ToDriverDataType().ShouldBe(DriverDataType.Float32); AbCipDataType.LReal.ToDriverDataType().ShouldBe(DriverDataType.Float64); AbCipDataType.String.ToDriverDataType().ShouldBe(DriverDataType.String); } }