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; /// /// PR 9 — per-device timeout integration scaffold. Build-only at PR 9 time: the ab_server /// PCCC simulator answers in <100 ms locally so a 500 ms per-device timeout doesn't /// normally trip. Either an iptables --delay sidecar or a tc qdisc netem /// filter must be wired up first; until then the test asserts that a generous /// per-device timeout still completes successfully (the precedence path itself is /// exercised), with the slow-path failure case expressed in unit tests via /// . /// [Collection(AbLegacyServerCollection.Name)] [Trait("Category", "Integration")] [Trait("Simulator", "ab_server-PCCC")] public sealed class AbLegacyPerDeviceTimeoutTests(AbLegacyServerFixture sim) { [AbLegacyFact] public async Task Per_device_Timeout_override_flows_into_runtime_against_ab_server() { if (sim.SkipReason is not null) Assert.Skip(sim.SkipReason); var deviceUri = $"ab://{sim.Host}:{sim.Port}/{sim.CipPath}"; await using var drv = new AbLegacyDriver(new AbLegacyDriverOptions { // Driver-wide tight 500 ms; per-device override gives the simulator 5 s headroom // to demonstrate the precedence rule in a wire-level setting. Timeout = TimeSpan.FromMilliseconds(500), Devices = [new AbLegacyDeviceOptions( deviceUri, AbLegacyPlcFamily.Slc500, Timeout: TimeSpan.FromSeconds(5))], Tags = [new AbLegacyTagDefinition( Name: "IntCounter", DeviceHostAddress: deviceUri, Address: "N7:0", DataType: AbLegacyDataType.Int)], Probe = new AbLegacyProbeOptions { Enabled = false }, }, driverInstanceId: "ablegacy-pr9-timeout"); await drv.InitializeAsync("{}", TestContext.Current.CancellationToken); var snapshots = await drv.ReadAsync(["IntCounter"], TestContext.Current.CancellationToken); // Per-device override picked up; the read against the simulator succeeds because the // 5 s per-device cap supersedes the otherwise-too-tight 500 ms driver-wide default. snapshots.Single().StatusCode.ShouldBe(AbLegacyStatusMapper.Good); } /// /// Skeleton for the inverse — slow-link (tc qdisc / iptables --delay) + /// tight per-device timeout. Skipped pending the netem sidecar work tracked in /// Docker/README.md. /// [AbLegacyFact(Skip = "Pending netem / iptables-delay sidecar — see Docker/README.md")] public Task Per_device_Timeout_below_simulated_delay_surfaces_BadCommunicationError() { // Future shape: // docker compose --profile slc500-slow up -d (adds netem qdisc on the egress) // override Timeout: TimeSpan.FromMilliseconds(100) // ReadAsync ⇒ snapshots.Single().StatusCode == BadCommunicationError // while a sibling device (no override → 5 s) keeps reading Good. return Task.CompletedTask; } }