using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; using ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.PlcFamilies; namespace ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests; /// /// Review 07 U10: an N-file array write (N7:2, 4 elements) round-trips every element against /// ab_server on both transports, and leaves the words either side untouched. Simulator-only (gated by /// ); the real-controller proof is a hardware userGate. /// [Collection(AbLegacyServerCollection.Name)] [Trait("Category", "Integration")] [Trait("Simulator", "ab_server-PCCC")] public sealed class AbLegacyArrayWriteIntegrationTests(AbLegacyServerFixture sim) { /// Seeds the neighbours, writes an int[] to a 4-element Int array, reads it all back. /// The simulator profile. /// The transport stack the driver runs on. [AbLegacyTheory] [MemberData(nameof(TransportMatrix.Profiles), MemberType = typeof(TransportMatrix))] public async Task N_file_array_write_round_trips_every_element(AbLegacyServerProfile profile, AbLegacyTransport transport) { if (sim.SkipReasonFor(profile) is { } skip) Assert.Skip(skip); IntegrationWriteGate.SkipUnlessWritesAllowed(); var deviceUri = $"ab://{sim.Host}:{sim.PortFor(profile)}/{sim.CipPath}"; await using var drv = new AbLegacyDriver(new AbLegacyDriverOptions { Devices = [new AbLegacyDeviceOptions(deviceUri, profile.Family)], RawTags = AbLegacyRawTags.Entries( new AbLegacyTagDefinition("Before", deviceUri, "N7:1", AbLegacyDataType.Int), new AbLegacyTagDefinition("Arr", deviceUri, "N7:2", AbLegacyDataType.Int, ArrayLength: 4), new AbLegacyTagDefinition("After", deviceUri, "N7:6", AbLegacyDataType.Int)), Timeout = TimeSpan.FromSeconds(5), Probe = new AbLegacyProbeOptions { Enabled = false }, Transport = transport, }, driverInstanceId: $"ablegacy-arraywrite-{profile.Family}-{transport}"); var ct = TestContext.Current.CancellationToken; await drv.InitializeAsync("{}", ct); if (profile.Family == AbLegacyPlcFamily.Plc5 && transport == AbLegacyTransport.Native) { // Native PLC-5 writes are refused before the wire (no capture of the PLC-5 write frames exists). (await drv.WriteAsync([new WriteRequest("Arr", new[] { 1, 2, 3, 4 })], ct)).Single() .StatusCode.ShouldBe(AbLegacyStatusMapper.BadNotSupported); return; } (await drv.WriteAsync( [new WriteRequest("Before", (short)111), new WriteRequest("After", (short)666)], ct)) .ShouldAllBe(r => r.StatusCode == AbLegacyStatusMapper.Good); // ScadaBridge shape: a CLR int[] to an Int16 array node. (await drv.WriteAsync([new WriteRequest("Arr", new[] { 10, -20, 30, 32767 })], ct)).Single() .StatusCode.ShouldBe(AbLegacyStatusMapper.Good); var back = await drv.ReadAsync(["Before", "Arr", "After"], ct); back.ShouldAllBe(s => s.StatusCode == AbLegacyStatusMapper.Good); back[1].Value.ShouldBe(new short[] { 10, -20, 30, 32767 }); Convert.ToInt32(back[0].Value).ShouldBe(111); // neighbours untouched Convert.ToInt32(back[2].Value).ShouldBe(666); // A length mismatch is refused before the wire and changes nothing. (await drv.WriteAsync([new WriteRequest("Arr", new[] { 1, 2, 3 })], ct)).Single() .StatusCode.ShouldBe(AbLegacyStatusMapper.BadOutOfRange); (await drv.ReadAsync(["Arr"], ct)).Single().Value.ShouldBe(new short[] { 10, -20, 30, 32767 }); } }