using Akka.Actor; using Microsoft.Extensions.Logging.Abstractions; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Runtime.Drivers; using ZB.MOM.WW.OtOpcUa.Runtime.Tests.Harness; namespace ZB.MOM.WW.OtOpcUa.Runtime.Tests.Drivers; /// /// Verifies the adapter: it implements the Commons /// IOpcUaNodeWriteGateway by lazily resolving the and Asking it a /// , translating the actor's /// reply into a Commons NodeWriteOutcome. A /// TestProbe stands in for the DriverHostActor (passed via resolveDriverHost) so the /// gateway can be driven without the full driver harness. /// public sealed class ActorNodeWriteGatewayTests : RuntimeActorTestBase { /// Probe receives the RouteNodeWrite and replies success → the outcome is (true, null). [Fact] public async Task Success_reply_maps_to_successful_outcome() { var probe = CreateTestProbe(); var gateway = new ActorNodeWriteGateway(resolveDriverHost: () => probe.Ref, NullLogger.Instance); var writeTask = gateway.WriteAsync("eq-1/speed", 123.0, CancellationToken.None); var routed = probe.ExpectMsg(TimeSpan.FromSeconds(5)); routed.NodeId.ShouldBe("eq-1/speed"); routed.Value.ShouldBe(123.0); probe.Reply(new DriverHostActor.NodeWriteResult(true, null)); var outcome = await writeTask; outcome.Success.ShouldBeTrue(); outcome.Reason.ShouldBeNull(); } /// Probe replies failure → the outcome carries the same (false, reason) verbatim. [Fact] public async Task Failure_reply_maps_to_failure_outcome_with_reason() { var probe = CreateTestProbe(); var gateway = new ActorNodeWriteGateway(resolveDriverHost: () => probe.Ref, NullLogger.Instance); var writeTask = gateway.WriteAsync("eq-1/speed", 123.0, CancellationToken.None); probe.ExpectMsg(TimeSpan.FromSeconds(5)); probe.Reply(new DriverHostActor.NodeWriteResult(false, "not primary")); var outcome = await writeTask; outcome.Success.ShouldBeFalse(); outcome.Reason.ShouldBe("not primary"); } /// The probe never replies; a short Ask timeout makes the gateway fast-fail with a negative, /// non-null-reason outcome rather than hanging. [Fact] public async Task No_reply_within_timeout_maps_to_negative_outcome() { var probe = CreateTestProbe(); var gateway = new ActorNodeWriteGateway( resolveDriverHost: () => probe.Ref, NullLogger.Instance, askTimeout: TimeSpan.FromMilliseconds(200)); var outcome = await gateway.WriteAsync("eq-1/speed", 123.0, CancellationToken.None); outcome.Success.ShouldBeFalse(); outcome.Reason.ShouldNotBeNull(); outcome.Reason.ShouldBe("write timeout"); } /// When no DriverHostActor is registered (resolver returns null), the gateway short-circuits /// to (false, "writes unavailable") and never messages any actor. [Fact] public async Task No_actor_registered_maps_to_writes_unavailable() { var gateway = new ActorNodeWriteGateway(resolveDriverHost: () => null, NullLogger.Instance); var outcome = await gateway.WriteAsync("eq-1/speed", 123.0, CancellationToken.None); outcome.Success.ShouldBeFalse(); outcome.Reason.ShouldBe("writes unavailable"); } }