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");
}
}