using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; namespace ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Tests; /// /// Review 02 P1: AbLegacy ran but did not implement , /// so every changed tag paid its own trip through the actor chain. It now raises one /// per poll cycle, alongside the per-tag event. /// [Trait("Category", "Unit")] public sealed class AbLegacyBatchSubscribeTests { private const string Host = "ab://10.0.0.5/1,0"; [Fact] public async Task One_batch_per_poll_carries_every_changed_tag_and_per_tag_events_still_fire() { var factory = new FakeAbLegacyTagFactory { Customise = p => new FakeAbLegacyTag(p) { Value = p.TagName == "N7:0" ? 1 : 2 } }; var drv = new AbLegacyDriver(new AbLegacyDriverOptions { Devices = [new AbLegacyDeviceOptions(Host)], RawTags = AbLegacyRawTags.Entries( new AbLegacyTagDefinition("A", Host, "N7:0", AbLegacyDataType.Int), new AbLegacyTagDefinition("B", Host, "N9:0", AbLegacyDataType.Int)), Probe = new AbLegacyProbeOptions { Enabled = false }, }, "drv-batch", factory); await drv.InitializeAsync("{}", CancellationToken.None); IBatchSubscribable batching = drv; // the capability the actor prefers var batches = new System.Collections.Concurrent.ConcurrentQueue(); var singles = new System.Collections.Concurrent.ConcurrentQueue(); batching.OnDataChangeBatch += (_, e) => batches.Enqueue(e); drv.OnDataChange += (_, e) => singles.Enqueue(e); var handle = await drv.SubscribeAsync(["A", "B"], TimeSpan.FromMilliseconds(100), CancellationToken.None); var deadline = DateTime.UtcNow.AddSeconds(5); while (batches.IsEmpty && DateTime.UtcNow < deadline) await Task.Delay(10, TestContext.Current.CancellationToken); batches.TryPeek(out var first).ShouldBeTrue(); first!.SubscriptionHandle.ShouldBe(handle); first.Items.Select(i => i.FullReference).ShouldBe(new[] { "A", "B" }, ignoreOrder: true); first.Items.Single(i => i.FullReference == "A").Snapshot.Value.ShouldBe(1); singles.Count.ShouldBeGreaterThanOrEqualTo(2); // positive control: the legacy per-tag event still fires await drv.UnsubscribeAsync(handle, CancellationToken.None); await drv.ShutdownAsync(CancellationToken.None); } }