using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.AdminUI.Hubs; using ZB.MOM.WW.OtOpcUa.Commons.Messages.Drivers; namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests; /// /// Covers the in-process push contract the Blazor Server DriverStatusPanel relies on: /// fires on every /// , and TryGet returns the latest. /// The panel subscribes to this store directly instead of opening a self-targeted SignalR /// connection (which a server-side component can't reach behind a reverse proxy). /// public sealed class DriverStatusSnapshotStoreTests { private static DriverHealthChanged Snap(string instance, string state = "Healthy") => new("MAIN", instance, state, null, null, 0, new DateTime(2026, 5, 29, 0, 0, 0, DateTimeKind.Utc)); [Fact] public void Upsert_raises_SnapshotChanged_with_the_stored_snapshot() { var store = new InMemoryDriverStatusSnapshotStore(); var received = new List(); store.SnapshotChanged += received.Add; var snap = Snap("drv-1", "Faulted"); store.Upsert(snap); received.Count.ShouldBe(1); received[0].ShouldBeSameAs(snap); } [Fact] public void Upsert_then_TryGet_returns_the_latest_snapshot() { var store = new InMemoryDriverStatusSnapshotStore(); store.Upsert(Snap("drv-1", "Healthy")); store.Upsert(Snap("drv-1", "Degraded")); store.TryGet("drv-1", out var latest).ShouldBeTrue(); latest.State.ShouldBe("Degraded"); } [Fact] public void Unsubscribed_handler_stops_receiving_after_removal() { var store = new InMemoryDriverStatusSnapshotStore(); var count = 0; void Handler(DriverHealthChanged _) => count++; store.SnapshotChanged += Handler; store.Upsert(Snap("drv-1")); store.SnapshotChanged -= Handler; store.Upsert(Snap("drv-1")); count.ShouldBe(1); } [Fact] public void Snapshots_from_two_nodes_for_one_driver_are_kept_side_by_side_847() { var store = new InMemoryDriverStatusSnapshotStore(); var t0 = new DateTime(2026, 11, 11, 0, 0, 0, DateTimeKind.Utc); store.Upsert(new DriverHealthChanged("MAIN", "drv", "Faulted", null, "x", 1, t0, ReportingNodeId: "central-1")); store.Upsert(new DriverHealthChanged("MAIN", "drv", "Healthy", null, null, 0, t0.AddSeconds(1), ReportingNodeId: "central-2")); // A re-publish from central-1 replaces only central-1's entry. store.Upsert(new DriverHealthChanged("MAIN", "drv", "Faulted", null, "y", 2, t0.AddSeconds(2), ReportingNodeId: "central-1")); var all = store.GetAll(); all.Count.ShouldBe(2); all.Single(s => s.ReportingNodeId == "central-1").LastError.ShouldBe("y"); all.Single(s => s.ReportingNodeId == "central-2").State.ShouldBe("Healthy"); // TryGet (the per-driver panel / hub) answers the most recently published across nodes. store.TryGet("drv", out var latest).ShouldBeTrue(); latest.ReportingNodeId.ShouldBe("central-1"); latest.LastError.ShouldBe("y"); store.TryGet("other", out _).ShouldBeFalse(); } [Fact] public void Eviction_removes_every_node_entry_of_an_unconfigured_stale_driver_847() { var store = new InMemoryDriverStatusSnapshotStore(); var old = new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc); store.Upsert(new DriverHealthChanged("MAIN", "gone", "Healthy", null, null, 0, old, ReportingNodeId: "a")); store.Upsert(new DriverHealthChanged("MAIN", "gone", "Healthy", null, null, 0, old, ReportingNodeId: "b")); store.Upsert(new DriverHealthChanged("MAIN", "kept", "Healthy", null, null, 0, old, ReportingNodeId: "a")); store.EvictUnconfiguredStale(new HashSet { "kept" }, old.AddMinutes(1)).ShouldBe(2); store.GetAll().ShouldHaveSingleItem().DriverInstanceId.ShouldBe("kept"); } }