using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Health;
namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests.Health;
///
/// Tests for — the merge + diff logic for the
/// transport entry plus per-platform probe entries that
/// IHostConnectivityProbe.GetHostStatuses() surfaces.
///
public sealed class HostStatusAggregatorTests
{
private static HostConnectivityStatus Status(string host, HostState state) =>
new(host, state, DateTime.UtcNow);
[Fact]
public void Snapshot_Empty_WhenNothingTracked()
{
var agg = new HostStatusAggregator();
agg.Snapshot().ShouldBeEmpty();
}
[Fact]
public void Update_NewHost_FiresChange_PreviousIsUnknown()
{
var agg = new HostStatusAggregator();
var captured = new List();
agg.OnHostStatusChanged += (_, e) => captured.Add(e);
agg.Update(Status("PlatformA", HostState.Running));
captured.Count.ShouldBe(1);
captured[0].HostName.ShouldBe("PlatformA");
captured[0].OldState.ShouldBe(HostState.Unknown);
captured[0].NewState.ShouldBe(HostState.Running);
}
[Fact]
public void Update_SameState_DoesNotFire()
{
var agg = new HostStatusAggregator();
agg.Update(Status("PlatformA", HostState.Running));
var captured = new List();
agg.OnHostStatusChanged += (_, e) => captured.Add(e);
agg.Update(Status("PlatformA", HostState.Running));
captured.ShouldBeEmpty();
}
[Fact]
public void Update_StateTransition_FiresChangeWithCorrectPreviousAndNew()
{
var agg = new HostStatusAggregator();
agg.Update(Status("PlatformA", HostState.Running));
var captured = new List();
agg.OnHostStatusChanged += (_, e) => captured.Add(e);
agg.Update(Status("PlatformA", HostState.Stopped));
captured.Count.ShouldBe(1);
captured[0].OldState.ShouldBe(HostState.Running);
captured[0].NewState.ShouldBe(HostState.Stopped);
}
[Fact]
public void Snapshot_ReflectsEveryUpsertedHost()
{
var agg = new HostStatusAggregator();
agg.Update(Status("Transport", HostState.Running));
agg.Update(Status("PlatformA", HostState.Running));
agg.Update(Status("PlatformB", HostState.Stopped));
var snap = agg.Snapshot();
snap.Count.ShouldBe(3);
snap.Select(s => s.HostName).OrderBy(x => x).ShouldBe(new[] { "PlatformA", "PlatformB", "Transport" });
snap.First(s => s.HostName == "PlatformB").State.ShouldBe(HostState.Stopped);
}
[Fact]
public void Update_HostNameComparison_IsCaseInsensitive()
{
var agg = new HostStatusAggregator();
var captured = new List();
agg.OnHostStatusChanged += (_, e) => captured.Add(e);
agg.Update(Status("PlatformA", HostState.Running));
agg.Update(Status("platforma", HostState.Stopped)); // same host, different case
captured.Count.ShouldBe(2);
captured[1].OldState.ShouldBe(HostState.Running);
captured[1].NewState.ShouldBe(HostState.Stopped);
agg.Snapshot().Count.ShouldBe(1);
}
[Fact]
public void Remove_TrackedHost_ReturnsTrue_AndDropsFromSnapshot()
{
var agg = new HostStatusAggregator();
agg.Update(Status("PlatformA", HostState.Running));
agg.Remove("PlatformA").ShouldBeTrue();
agg.Snapshot().ShouldBeEmpty();
}
[Fact]
public void Remove_UnknownHost_ReturnsFalse()
{
var agg = new HostStatusAggregator();
agg.Remove("Nope").ShouldBeFalse();
}
[Fact]
public void ConcurrentUpdates_DoNotCorruptDictionary()
{
var agg = new HostStatusAggregator();
const int threadCount = 8;
const int updatesPerThread = 250;
var tasks = Enumerable.Range(0, threadCount).Select(t => Task.Run(() =>
{
for (var i = 0; i < updatesPerThread; i++)
{
var hostName = $"Host{(t * updatesPerThread + i) % 32}";
var state = i % 2 == 0 ? HostState.Running : HostState.Stopped;
agg.Update(Status(hostName, state));
}
})).ToArray();
Task.WaitAll(tasks);
agg.Snapshot().Count.ShouldBeLessThanOrEqualTo(32);
}
}