using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Driver.AbCip;
namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests;
///
/// libplctag hands back whatever bytes the controller sends and decodes them as the authored type
/// — it never checks the CIP type code. Seen live on a production press: WW_ActualVacuum is a
/// REAL (the L5X and the controller's symbol table agree), the legacy Kepware export types it as
/// Short, and reading it authored as INT returned Good -17346 (the low half of a float)
/// where the real value was 5.37. A scalar atomic read whose buffer is not the authored type's
/// width is now BadTypeMismatch. Same-width mismatches (DINT vs REAL) remain undetectable
/// without the type code.
///
[Trait("Category", "Unit")]
public sealed class AbCipReadWidthMismatchTests
{
private const string Device = "ab://10.0.0.5/1,0";
private static async Task ReadStatusAsync(AbCipDataType authored, int bufferSize, string path = "WW_ActualVacuum")
{
var factory = new FakeAbCipTagFactory
{
Customise = p => new FakeAbCipTag(p) { Value = 1, Size = bufferSize },
};
var drv = new AbCipDriver(new AbCipDriverOptions
{
Devices = [new AbCipDeviceOptions(Device)],
RawTags = AbCipRawTags.From(new AbCipTagDefinition("T", Device, path, authored)),
Probe = new AbCipProbeOptions { Enabled = false },
}, "abcip-1", factory);
await drv.InitializeAsync("{}", TestContext.Current.CancellationToken);
var r = await drv.ReadAsync(["T"], TestContext.Current.CancellationToken);
return r[0].StatusCode;
}
/// An INT read that comes back 4 bytes wide is a type mismatch, not a Good value.
[Fact]
public async Task INT_authored_over_a_4_byte_REAL_is_BadTypeMismatch()
{
(await ReadStatusAsync(AbCipDataType.Int, bufferSize: 4)).ShouldBe(AbCipStatusMapper.BadTypeMismatch);
}
/// Matching widths still read Good — including the live-observed sizes per type.
/// Authored type.
/// Buffer size the controller returned for that type (observed live).
[Theory]
[InlineData(AbCipDataType.Int, 2)]
[InlineData(AbCipDataType.DInt, 4)]
[InlineData(AbCipDataType.Real, 4)]
[InlineData(AbCipDataType.LInt, 8)]
[InlineData(AbCipDataType.SInt, 1)]
public async Task Matching_width_reads_Good(AbCipDataType type, int size)
{
(await ReadStatusAsync(type, size)).ShouldBe(AbCipStatusMapper.Good);
}
/// An unknown size (a runtime that cannot report it) never blocks a read.
[Fact]
public async Task Unknown_size_is_not_checked()
{
(await ReadStatusAsync(AbCipDataType.Int, bufferSize: -1)).ShouldBe(AbCipStatusMapper.Good);
}
/// A bit-in-DINT read decodes from the parent's 4-byte buffer and is not width-checked.
[Fact]
public async Task Bit_index_read_is_not_width_checked()
{
(await ReadStatusAsync(AbCipDataType.Bool, bufferSize: 4, path: "Flags.3")).ShouldBe(AbCipStatusMapper.Good);
}
}