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
{
/// Verifies that the driver type is correctly identified as TwinCAT.
[Fact]
public void DriverType_is_TwinCAT()
{
var drv = new TwinCATDriver(new TwinCATDriverOptions(), "drv-1");
drv.DriverType.ShouldBe("TwinCAT");
drv.DriverInstanceId.ShouldBe("drv-1");
}
/// Verifies that device addresses are parsed during initialization.
[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");
}
/// Verifies that malformed device addresses cause initialization to fault.
[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);
}
/// Verifies that shutdown clears all devices.
[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);
}
/// Verifies that reinitialization cycles devices.
[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);
}
/// Verifies that data type mapping covers atomic IEC types.
[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);
// IEC TIME/DATE/DT/TOD surface as their raw UDINT counter (Driver.TwinCAT-002).
TwinCATDataType.Time.ToDriverDataType().ShouldBe(DriverDataType.UInt32);
}
/// Verifies that status mapper covers known ADS error codes.
/// The raw ADS error code to map.
/// The expected OPC UA status code after mapping.
[Theory]
[InlineData(0u, TwinCATStatusMapper.Good)]
// Device-layer codes — confirmed from Beckhoff.TwinCAT.Ads 7.0.172 AdsErrorCode enum
[InlineData(1792u, TwinCATStatusMapper.BadDeviceFailure)] // DeviceError (generic)
[InlineData(1793u, TwinCATStatusMapper.BadNotSupported)] // DeviceServiceNotSupported
[InlineData(1794u, TwinCATStatusMapper.BadOutOfRange)] // DeviceInvalidGroup (ADS index-group error)
[InlineData(1798u, TwinCATStatusMapper.BadTypeMismatch)] // DeviceInvalidData (data format mismatch)
[InlineData(1804u, TwinCATStatusMapper.BadNodeIdUnknown)] // DeviceNotFound
[InlineData(1808u, TwinCATStatusMapper.BadNodeIdUnknown)] // DeviceSymbolNotFound
[InlineData(1809u, TwinCATStatusMapper.BadInvalidState)] // DeviceSymbolVersionInvalid — rediscovery trigger
[InlineData(1827u, TwinCATStatusMapper.BadNotWritable)] // DeviceAccessDenied
[InlineData(1861u, TwinCATStatusMapper.BadTimeout)] // ClientSyncTimeOut
// AMS router errors
[InlineData(7u, TwinCATStatusMapper.BadCommunicationError)] // TargetMachineNotFound
[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);
}
}