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; } }