bd63810645
- Native-condition Acknowledge on a non-Primary node returns BadInvalidState (nothing routed/applied/reported) via NativeAlarmAckPrimaryGate, fed from IDriverPrimaryRoleSource by OtOpcUaServerHostedService. - A re-ack carrying a comment reports one comment event (Acked/Confirmed unchanged). - TimedShelve applies its requested duration and the scripted projection carries UnshelveAtUtc, so ShelvingState/UnshelveTime counts down instead of ~24.8 days. - Native re-ack still routed to the driver (deliberate: documented retry path), pinned. - AdminUI /alerts client ack/shelve path test; docs/AlarmTracking.md decisions incl. accepted SDK EventsMonitored residual (item 6). - Also #599/03/S20: RemoveEquipmentSubtree severs inverse Organizes edges. Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
82 lines
3.7 KiB
C#
82 lines
3.7 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// Gitea #597 — the AdminUI <c>/alerts</c> acknowledge / shelve path, from the page's client call down to
|
|
/// the command the cluster receives: <see cref="AdminOperationsClient"/> (what <c>Alerts.razor</c>'s
|
|
/// per-row buttons call) Asks the real <see cref="AdminOperationsActor"/> registered under
|
|
/// <see cref="AdminOperationsActorKey"/>, which maps the request to an <see cref="AlarmCommand"/> 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.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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 <c>comment: null</c>, both reproduced below exactly as the page
|
|
/// passes them.
|
|
/// </remarks>
|
|
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<IDriverProbe>(),
|
|
meshRouter: mesh.Ref));
|
|
var registry = new ActorRegistry();
|
|
registry.Register<AdminOperationsActorKey>(actor);
|
|
return (new AdminOperationsClient(registry), mesh);
|
|
}
|
|
|
|
/// <summary>The page's Acknowledge reaches the cluster as an <c>Acknowledge</c> AlarmCommand on the
|
|
/// <c>alarm-commands</c> topic carrying the alarm id and the operator, and the page is told it was sent.</summary>
|
|
[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<MeshCommand>(TimeSpan.FromSeconds(5));
|
|
sent.Topic.ShouldBe(AlarmCommandsTopic.Name);
|
|
var command = sent.Message.ShouldBeOfType<AlarmCommand>();
|
|
command.AlarmId.ShouldBe("alarm-42");
|
|
command.Operation.ShouldBe("Acknowledge");
|
|
command.User.ShouldBe("operator-jo");
|
|
command.Comment.ShouldBeNull();
|
|
command.UnshelveAtUtc.ShouldBeNull();
|
|
}
|
|
|
|
/// <summary>The page's timed shelve carries its expiry through to the command — the instant the engine
|
|
/// projects as the node's <c>UnshelveTime</c>.</summary>
|
|
[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<MeshCommand>(TimeSpan.FromSeconds(5)).Message.ShouldBeOfType<AlarmCommand>();
|
|
command.Operation.ShouldBe("TimedShelve");
|
|
command.UnshelveAtUtc.ShouldBe(until);
|
|
}
|
|
}
|