using Akka.Hosting; using Akka.TestKit.Xunit2; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.AdminUI.Clients; using ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Acls; using ZB.MOM.WW.OtOpcUa.Commons.Messages.Admin; using ZB.MOM.WW.OtOpcUa.Commons.Messages.Mesh; using ZB.MOM.WW.OtOpcUa.Commons.OpcUa; using ZB.MOM.WW.OtOpcUa.ControlPlane; using ZB.MOM.WW.OtOpcUa.ControlPlane.AdminOperations; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests; /// /// Gitea #597 — the AdminUI /alerts acknowledge / shelve path, from the page's client call down to /// the command the cluster receives: (what Alerts.razor's /// per-row buttons call) Asks the real registered under /// , which maps the request to an and hands /// it to the mesh transport. Until now only the actor half had a test; the client seam the page goes /// through had none. /// /// /// The razor click handler itself is not driven here — this repo has no bUnit. What it adds on top of the /// client call is the operator name and comment: null, both reproduced below exactly as the page /// passes them. /// public sealed class AlertsAckPathTests : TestKit { private (AdminOperationsClient Client, Akka.TestKit.TestProbe Mesh) Wire() { var mesh = CreateTestProbe("mesh-router"); var actor = Sys.ActorOf(AdminOperationsActor.Props( AclScopeTestDb.Factory($"alerts-ack-{Guid.NewGuid():N}"), coordinator: CreateTestProbe("coordinator").Ref, probes: Enumerable.Empty(), meshRouter: mesh.Ref)); var registry = new ActorRegistry(); registry.Register(actor); return (new AdminOperationsClient(registry), mesh); } /// The page's Acknowledge reaches the cluster as an Acknowledge AlarmCommand on the /// alarm-commands topic carrying the alarm id and the operator, and the page is told it was sent. [Fact] public async Task Acknowledge_from_the_alerts_page_dispatches_an_Acknowledge_command_and_reports_sent() { var (client, mesh) = Wire(); var result = await client.AcknowledgeAlarmAsync("alarm-42", "operator-jo", comment: null, CancellationToken.None); result.Ok.ShouldBeTrue(); result.Message.ShouldBeNull(); var sent = mesh.ExpectMsg(TimeSpan.FromSeconds(5)); sent.Topic.ShouldBe(AlarmCommandsTopic.Name); var command = sent.Message.ShouldBeOfType(); command.AlarmId.ShouldBe("alarm-42"); command.Operation.ShouldBe("Acknowledge"); command.User.ShouldBe("operator-jo"); command.Comment.ShouldBeNull(); command.UnshelveAtUtc.ShouldBeNull(); } /// The page's timed shelve carries its expiry through to the command — the instant the engine /// projects as the node's UnshelveTime. [Fact] public async Task Timed_shelve_from_the_alerts_page_carries_its_expiry_to_the_command() { var (client, mesh) = Wire(); var until = new DateTime(2026, 10, 8, 12, 30, 0, DateTimeKind.Utc); var result = await client.ShelveAlarmAsync( "alarm-7", "operator-kim", ShelveKind.Timed, unshelveAtUtc: until, comment: null, CancellationToken.None); result.Ok.ShouldBeTrue(); var command = mesh.ExpectMsg(TimeSpan.FromSeconds(5)).Message.ShouldBeOfType(); command.Operation.ShouldBe("TimedShelve"); command.UnshelveAtUtc.ShouldBe(until); } }