using Shouldly;
using TwinCAT.Ads.TypeSystem;
using TwinCAT.TypeSystem;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Driver.TwinCAT;
namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.Tests;
///
/// Coverage for — the discovery
/// helper that maps a TwinCAT to a driver .
/// PR-1.5 added ENUM and ALIAS chain handling on top of the original primitive name match.
///
[Trait("Category", "Unit")]
public sealed class TwinCATTypeResolutionTests
{
[Fact]
public void Primitive_resolves_directly()
{
var dt = new PrimitiveType("DINT", typeof(int));
AdsTwinCATClient.ResolveSymbolDataType(dt).ShouldBe(TwinCATDataType.DInt);
}
[Fact]
public void Null_data_type_returns_null()
{
AdsTwinCATClient.ResolveSymbolDataType(null).ShouldBeNull();
}
[Fact]
public void Single_alias_resolves_to_underlying_atomic()
{
var primitive = new PrimitiveType("INT", typeof(short));
var alias = new AliasType("DegreesC", primitive);
AdsTwinCATClient.ResolveSymbolDataType(alias).ShouldBe(TwinCATDataType.Int);
}
[Fact]
public void Chained_alias_resolves_through_multiple_links()
{
var primitive = new PrimitiveType("DINT", typeof(int));
var inner = new AliasType("Inner", primitive);
var outer = new AliasType("Outer", inner);
AdsTwinCATClient.ResolveSymbolDataType(outer).ShouldBe(TwinCATDataType.DInt);
}
[Fact]
public void Alias_to_unknown_primitive_returns_null()
{
// A primitive whose IEC name we don't recognise should drop through cleanly. Pick a CLR
// type with a known marshal size so PrimitiveType's ctor accepts it; the name is what
// exercises the unknown-type branch in MapSymbolTypeName.
var primitive = new PrimitiveType("MYSTERY", typeof(int));
var alias = new AliasType("Wrap", primitive);
AdsTwinCATClient.ResolveSymbolDataType(alias).ShouldBeNull();
}
[Fact]
public void Enum_resolves_to_underlying_integer_base_type()
{
var underlying = new PrimitiveType("INT", typeof(short));
var enumType = new TestEnumType("EState", underlying);
AdsTwinCATClient.ResolveSymbolDataType(enumType).ShouldBe(TwinCATDataType.Int);
}
[Fact]
public void Enum_with_dint_base_resolves_to_DInt()
{
var underlying = new PrimitiveType("DINT", typeof(int));
var enumType = new TestEnumType("EBigFlags", underlying);
AdsTwinCATClient.ResolveSymbolDataType(enumType).ShouldBe(TwinCATDataType.DInt);
}
[Fact]
public void Cyclic_alias_chain_terminates_at_depth_cap()
{
// Pathological: a self-referential alias. The resolver must give up rather than spin.
var loop = new SelfReferentialAlias();
AdsTwinCATClient.ResolveSymbolDataType(loop).ShouldBeNull();
}
///
/// Minimal in-test stub for . Only Category and
/// BaseType are exercised by ;
/// the rest of the surface throws to flag any accidental dependence in future tests.
///
private sealed class TestEnumType(string name, IDataType baseType) : IDataType, IAliasType
{
public string Name { get; } = name;
public IDataType BaseType { get; } = baseType;
public DataTypeCategory Category => DataTypeCategory.Enum;
public string BaseTypeName => BaseType.Name;
public string FullName => Name;
public string Namespace => string.Empty;
public int Id => 0;
public string Comment => string.Empty;
public ITypeAttributeCollection Attributes => throw new NotSupportedException();
public bool IsContainer => false;
public bool IsPointer => false;
public bool IsReference => false;
public bool IsPrimitive => false;
public int Size => BaseType.Size;
public int ByteSize => BaseType.ByteSize;
public int BitSize => BaseType.BitSize;
public bool IsBitType => false;
public bool IsByteAligned => true;
}
/// Self-referential alias — exercises the depth cap.
private sealed class SelfReferentialAlias : IDataType, IAliasType
{
public string Name => "Loop";
public IDataType BaseType => this;
public DataTypeCategory Category => DataTypeCategory.Alias;
public string BaseTypeName => Name;
public string FullName => Name;
public string Namespace => string.Empty;
public int Id => 0;
public string Comment => string.Empty;
public ITypeAttributeCollection Attributes => throw new NotSupportedException();
public bool IsContainer => false;
public bool IsPointer => false;
public bool IsReference => false;
public bool IsPrimitive => false;
public int Size => 0;
public int ByteSize => 0;
public int BitSize => 0;
public bool IsBitType => false;
public bool IsByteAligned => true;
}
}