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);
}
}