61193629b6
v2-ci / build (push) Failing after 36s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
Both bugs surfaced only on split-role deployments (the MAIN cluster's admin-only nodes), where the AdminUI runs without the driver role. - Test Connect returned "No probe registered" for every driver: the IDriverProbe set was registered only under the driver role, but the admin-operations singleton that consumes it is pinned to admin. Extract AddOtOpcUaDriverProbes() (idempotent via TryAddEnumerable) and call it in the hasAdmin path too. - Live driver-status/alerts/script-log panels showed "SignalR error: Connection refused": these Blazor Server components opened a HubConnection to their own hub via the browser's public URL, which server-side code can't reach behind Traefik (host :9200 -> container :9000). Read the in-process source directly instead -- DriverStatus via IDriverStatusSnapshotStore.SnapshotChanged, Alerts/ScriptLog via a new IInProcessBroadcaster<T>. Fleet status was unaffected (reads DB/ActorSystem). Adds unit tests for probe registration, the snapshot-store event, and the broadcaster.
60 lines
2.0 KiB
C#
60 lines
2.0 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// Covers the in-process push contract the Blazor Server <c>DriverStatusPanel</c> relies on:
|
|
/// <see cref="IDriverStatusSnapshotStore.SnapshotChanged"/> fires on every
|
|
/// <see cref="IDriverStatusSnapshotStore.Upsert"/>, and <c>TryGet</c> 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).
|
|
/// </summary>
|
|
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<DriverHealthChanged>();
|
|
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);
|
|
}
|
|
}
|