using Shouldly; using Xunit; namespace ZB.MOM.WW.OtOpcUa.Driver.S7.Tests; /// /// Unit tests for the S7 driver's 1-D array surface (PR-S7-A4). Wire-level round-trip /// tests against a live S7 still need a real PLC, so these tests exercise the /// driver's slice / pack helpers directly (visible via InternalsVisibleTo) and /// the init-time validation that rejects unsupported element types and over-budget /// ElementCount values up-front. /// [Trait("Category", "Unit")] public sealed class S7DriverArrayTests { [Fact] public void Int16_array_roundtrip_via_pack_then_slice() { // Big-endian 16-bit elements: validate that PackArray + SliceArray round-trip // a representative range including negatives and the boundary values. var input = new short[] { 0, 1, -1, short.MinValue, short.MaxValue, 12345 }; var elem = S7Driver.ArrayElementBytes(S7DataType.Int16); var bytes = S7Driver.PackArray(input, S7DataType.Int16, input.Length, elem, "t"); bytes.Length.ShouldBe(input.Length * elem); // Sanity-check big-endian layout: element[2] = -1 → 0xFFFF at byte offset 4. bytes[4].ShouldBe((byte)0xFF); bytes[5].ShouldBe((byte)0xFF); var output = (short[])S7Driver.SliceArray(bytes, S7DataType.Int16, input.Length, elem); output.ShouldBe(input); } [Fact] public void Int32_array_roundtrip_via_pack_then_slice() { var input = new[] { 0, 1, -1, int.MinValue, int.MaxValue, 0x12345678 }; var elem = S7Driver.ArrayElementBytes(S7DataType.Int32); var bytes = S7Driver.PackArray(input, S7DataType.Int32, input.Length, elem, "t"); var output = (int[])S7Driver.SliceArray(bytes, S7DataType.Int32, input.Length, elem); output.ShouldBe(input); } [Fact] public void Float32_array_roundtrip_via_pack_then_slice() { var input = new[] { 0f, 1.5f, -3.25f, float.MinValue, float.MaxValue, float.Epsilon }; var elem = S7Driver.ArrayElementBytes(S7DataType.Float32); var bytes = S7Driver.PackArray(input, S7DataType.Float32, input.Length, elem, "t"); var output = (float[])S7Driver.SliceArray(bytes, S7DataType.Float32, input.Length, elem); output.ShouldBe(input); } [Fact] public void Float64_array_roundtrip_via_pack_then_slice() { var input = new[] { 0d, Math.PI, -Math.E, double.MinValue, double.MaxValue }; var elem = S7Driver.ArrayElementBytes(S7DataType.Float64); var bytes = S7Driver.PackArray(input, S7DataType.Float64, input.Length, elem, "t"); var output = (double[])S7Driver.SliceArray(bytes, S7DataType.Float64, input.Length, elem); output.ShouldBe(input); } [Fact] public void IsArrayElementSupported_rejects_strings_and_bool() { // Variable-width string types and BOOL (packed-bit layout) are explicit follow-ups — // surface them as init-time rejections rather than mysterious BadInternalError on read. S7Driver.IsArrayElementSupported(S7DataType.Bool).ShouldBeFalse(); S7Driver.IsArrayElementSupported(S7DataType.String).ShouldBeFalse(); S7Driver.IsArrayElementSupported(S7DataType.WString).ShouldBeFalse(); S7Driver.IsArrayElementSupported(S7DataType.Char).ShouldBeFalse(); S7Driver.IsArrayElementSupported(S7DataType.WChar).ShouldBeFalse(); S7Driver.IsArrayElementSupported(S7DataType.Int16).ShouldBeTrue(); S7Driver.IsArrayElementSupported(S7DataType.Float64).ShouldBeTrue(); } [Fact] public async Task Initialize_rejects_array_of_String_with_FormatException() { // Init-time guard: even if the address parses cleanly, an array of variable-width // STRING is not yet supported and must fail-fast at config-load. The driver never // gets as far as opening the TcpClient because parsing is the first step. var opts = new S7DriverOptions { Host = "192.0.2.1", // reserved — TCP would never connect anyway Timeout = TimeSpan.FromMilliseconds(250), Tags = [ new S7TagDefinition( Name: "BadStrArr", Address: "DB1.DBB0", DataType: S7DataType.String, ElementCount: 4), ], }; using var drv = new S7Driver(opts, "s7-arr-bad-string"); await Should.ThrowAsync(async () => await drv.InitializeAsync("{}", TestContext.Current.CancellationToken)); } [Fact] public async Task Initialize_rejects_array_of_Bool_with_FormatException() { // BOOL arrays are stored as packed bits (one bit per element rounded up to a byte) — // the byte-range read trick used here for word-shaped elements doesn't generalize, so // arrays of Bool are explicitly out-of-scope for PR-S7-A4 and reject at init. var opts = new S7DriverOptions { Host = "192.0.2.1", Timeout = TimeSpan.FromMilliseconds(250), Tags = [ new S7TagDefinition( Name: "BadBoolArr", Address: "DB1.DBX0.0", DataType: S7DataType.Bool, ElementCount: 8), ], }; using var drv = new S7Driver(opts, "s7-arr-bad-bool"); await Should.ThrowAsync(async () => await drv.InitializeAsync("{}", TestContext.Current.CancellationToken)); } [Fact] public async Task Initialize_rejects_oversized_ElementCount_with_FormatException() { var opts = new S7DriverOptions { Host = "192.0.2.1", Timeout = TimeSpan.FromMilliseconds(250), Tags = [ new S7TagDefinition( Name: "TooBig", Address: "DB1.DBW0", DataType: S7DataType.Int16, ElementCount: S7Driver.MaxArrayElements + 1), ], }; using var drv = new S7Driver(opts, "s7-arr-too-big"); await Should.ThrowAsync(async () => await drv.InitializeAsync("{}", TestContext.Current.CancellationToken)); } }