using System.Net; using System.Net.Sockets; using Akka.Actor; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Commons.Types; using ZB.MOM.WW.OtOpcUa.Runtime.Health; using ZB.MOM.WW.OtOpcUa.Runtime.Historian; using ZB.MOM.WW.OtOpcUa.Runtime.Tests.Harness; namespace ZB.MOM.WW.OtOpcUa.Runtime.Tests.Health; public sealed class HealthProbeActorTests : RuntimeActorTestBase { [Fact] public async Task DbHealthProbeActor_returns_reachable_against_in_memory_db() { var db = NewInMemoryDbFactory(); var actor = Sys.ActorOf(DbHealthProbeActor.Props(db)); var status = await actor.Ask( DbHealthProbeActor.GetStatus.Instance, TimeSpan.FromSeconds(3)); status.Reachable.ShouldBeTrue(); status.LastError.ShouldBeNull(); } [Fact] public void PeerOpcUaProbeActor_reports_Ok_true_against_a_live_listener() { using var listener = new TcpListener(IPAddress.Loopback, 0); listener.Start(); var port = ((IPEndPoint)listener.LocalEndpoint).Port; var received = new System.Collections.Generic.List(); Sys.ActorOf(PeerOpcUaProbeActor.Props( NodeId.Parse($"127.0.0.1:{port}"), interval: TimeSpan.FromMilliseconds(50), connectTimeout: TimeSpan.FromMilliseconds(500), opcUaPort: port, broadcast: msg => received.Add(msg))); AwaitCondition(() => received.OfType().Any(r => r.Ok), TimeSpan.FromSeconds(3)); } [Fact] public void PeerOpcUaProbeActor_reports_Ok_false_against_an_unreachable_endpoint() { // Port 1 is reserved (tcpmux) and almost never bound on dev machines, so the connect fails fast. var received = new System.Collections.Generic.List(); Sys.ActorOf(PeerOpcUaProbeActor.Props( NodeId.Parse("127.0.0.1:1"), interval: TimeSpan.FromMilliseconds(50), connectTimeout: TimeSpan.FromMilliseconds(300), opcUaPort: 1, broadcast: msg => received.Add(msg))); AwaitCondition(() => received.OfType().Any(r => !r.Ok), TimeSpan.FromSeconds(3)); } [Fact] public void HistorianAdapterActor_buffers_rows() { var actor = Sys.ActorOf(HistorianAdapterActor.Props()); for (var i = 0; i < 5; i++) actor.Tell(new HistorianAdapterActor.HistoryRow("driver-a", $"tag-{i}", i, DateTime.UtcNow)); ExpectNoMsg(TimeSpan.FromMilliseconds(100)); // No direct readback of the count from a sealed actor — assert by Ask of a self-probe later // when the engine wiring lands (F11). For now this asserts the actor accepts the contract. } }