using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; using ZB.MOM.WW.OtOpcUa.Driver.TwinCAT; namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.Tests; [Trait("Category", "Unit")] public sealed class TwinCATDriverTests { [Fact] public void DriverType_is_TwinCAT() { var drv = new TwinCATDriver(new TwinCATDriverOptions(), "drv-1"); drv.DriverType.ShouldBe("TwinCAT"); drv.DriverInstanceId.ShouldBe("drv-1"); } [Fact] public async Task InitializeAsync_parses_device_addresses() { var drv = new TwinCATDriver(new TwinCATDriverOptions { Devices = [ new TwinCATDeviceOptions("ads://5.23.91.23.1.1:851"), new TwinCATDeviceOptions("ads://10.0.0.1.1.1:852", DeviceName: "Machine2"), ], }, "drv-1"); await drv.InitializeAsync("{}", CancellationToken.None); drv.DeviceCount.ShouldBe(2); drv.GetDeviceState("ads://5.23.91.23.1.1:851")!.ParsedAddress.Port.ShouldBe(851); drv.GetDeviceState("ads://10.0.0.1.1.1:852")!.Options.DeviceName.ShouldBe("Machine2"); } [Fact] public async Task InitializeAsync_malformed_address_faults() { var drv = new TwinCATDriver(new TwinCATDriverOptions { Devices = [new TwinCATDeviceOptions("not-an-address")], }, "drv-1"); await Should.ThrowAsync( () => drv.InitializeAsync("{}", CancellationToken.None)); drv.GetHealth().State.ShouldBe(DriverState.Faulted); } [Fact] public async Task ShutdownAsync_clears_devices() { var drv = new TwinCATDriver(new TwinCATDriverOptions { Devices = [new TwinCATDeviceOptions("ads://5.23.91.23.1.1:851")], Probe = new TwinCATProbeOptions { Enabled = false }, }, "drv-1"); await drv.InitializeAsync("{}", CancellationToken.None); 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 TwinCATDriver(new TwinCATDriverOptions { Devices = [new TwinCATDeviceOptions("ads://5.23.91.23.1.1:851")], Probe = new TwinCATProbeOptions { Enabled = false }, }, "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 DataType_mapping_covers_atomic_iec_types() { TwinCATDataType.Bool.ToDriverDataType().ShouldBe(DriverDataType.Boolean); TwinCATDataType.DInt.ToDriverDataType().ShouldBe(DriverDataType.Int32); TwinCATDataType.Real.ToDriverDataType().ShouldBe(DriverDataType.Float32); TwinCATDataType.LReal.ToDriverDataType().ShouldBe(DriverDataType.Float64); TwinCATDataType.String.ToDriverDataType().ShouldBe(DriverDataType.String); TwinCATDataType.WString.ToDriverDataType().ShouldBe(DriverDataType.String); TwinCATDataType.Time.ToDriverDataType().ShouldBe(DriverDataType.Int32); } [Theory] [InlineData(0u, TwinCATStatusMapper.Good)] [InlineData(1798u, TwinCATStatusMapper.BadNodeIdUnknown)] // symbol not found [InlineData(1808u, TwinCATStatusMapper.BadNotWritable)] // access denied [InlineData(1861u, TwinCATStatusMapper.BadTimeout)] // sync timeout [InlineData(1793u, TwinCATStatusMapper.BadOutOfRange)] // invalid index group [InlineData(1794u, TwinCATStatusMapper.BadOutOfRange)] // invalid index offset [InlineData(1792u, TwinCATStatusMapper.BadNotSupported)] // service not supported [InlineData(7u, TwinCATStatusMapper.BadCommunicationError)] // port unreachable [InlineData(99999u, TwinCATStatusMapper.BadCommunicationError)] // unknown → generic comm fail public void StatusMapper_covers_known_ads_error_codes(uint adsError, uint expected) { TwinCATStatusMapper.MapAdsError(adsError).ShouldBe(expected); } }