using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Config; using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Runtime; namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests.Runtime; /// /// Tests for . /// The SDK session types are sealed with internal ctors and cannot be faked, so we /// drive the cache-seeding path through /// and verify the /// handle-count seams — the contract under test is purely that /// zeroes both dictionaries /// so the next write is forced to re-AddItem + re-AdviseSupervisory. /// public sealed class GatewayGalaxyDataWriterTests { private static GalaxyMxSession MinimalSession() => new(new GalaxyMxAccessOptions(ClientName: "OtOpcUa-Test")); /// /// Approach (b): seed the item-handle cache directly via the internal test seam, /// confirm the count is positive, call , /// and confirm both caches are cleared. /// The next write (not simulated here — needs a live gw) would therefore be forced /// to re-AddItem because the cache is empty. /// [Fact] public void InvalidateHandleCaches_clears_item_and_supervised_handle_caches() { var session = MinimalSession(); var writer = new GatewayGalaxyDataWriter(session, writeUserId: 0); // Pre-seed both caches via the internal test seam so we can assert the // "after a write" state without spinning up a real gRPC gateway session. writer.SeedHandleCachesForTest("TestMachine_001.TestAttr", itemHandle: 42, supervised: true); writer.CachedItemHandleCount.ShouldBe(1); writer.CachedSupervisedHandleCount.ShouldBe(1); writer.InvalidateHandleCaches(); writer.CachedItemHandleCount.ShouldBe(0); writer.CachedSupervisedHandleCount.ShouldBe(0); } /// /// A second seed + invalidate cycle proves the method isn't one-shot — a reconnect /// followed by writes followed by another reconnect must also start fresh. /// [Fact] public void InvalidateHandleCaches_is_repeatable_across_multiple_reconnects() { var session = MinimalSession(); var writer = new GatewayGalaxyDataWriter(session, writeUserId: 0); // First session cycle writer.SeedHandleCachesForTest("Tag.A", itemHandle: 1, supervised: false); writer.SeedHandleCachesForTest("Tag.B", itemHandle: 2, supervised: true); writer.CachedItemHandleCount.ShouldBe(2); writer.InvalidateHandleCaches(); writer.CachedItemHandleCount.ShouldBe(0); writer.CachedSupervisedHandleCount.ShouldBe(0); // Second session cycle — handles re-populated after the reconnect's replay writer.SeedHandleCachesForTest("Tag.A", itemHandle: 99, supervised: true); writer.CachedItemHandleCount.ShouldBe(1); writer.InvalidateHandleCaches(); writer.CachedItemHandleCount.ShouldBe(0); } /// /// on a fresh (never-used) /// writer must be a no-op rather than throwing — the reconnect supervisor may call it /// before any write has occurred. /// [Fact] public void InvalidateHandleCaches_on_empty_caches_is_a_noop() { var session = MinimalSession(); var writer = new GatewayGalaxyDataWriter(session, writeUserId: 0); // Caches are empty — must not throw. writer.CachedItemHandleCount.ShouldBe(0); writer.CachedSupervisedHandleCount.ShouldBe(0); writer.InvalidateHandleCaches(); writer.CachedItemHandleCount.ShouldBe(0); writer.CachedSupervisedHandleCount.ShouldBe(0); } }