refactor(central-ui): split Notification Report out of the Outbox page

This commit is contained in:
Joseph Doherty
2026-05-19 06:03:15 -04:00
parent 016f5d48a6
commit 34e464edab
3 changed files with 21 additions and 146 deletions

View File

@@ -22,8 +22,8 @@ namespace ScadaLink.CentralUI.Tests.Pages;
/// <see cref="ICentralHealthAggregator"/> is an interface (mockable), but
/// <see cref="CommunicationService"/> is a concrete class whose outbox calls
/// route through an injected notification-outbox <see cref="IActorRef"/>; the
/// tests reuse the scripted-actor seam established by the Notification Outbox
/// page tests (see <c>NotificationOutboxPageTests</c>).
/// tests reuse the scripted-actor seam established by the Notification Report
/// page tests (see <c>NotificationReportPageTests</c>).
/// </summary>
public class HealthPageTests : BunitContext
{

View File

@@ -13,30 +13,26 @@ using ScadaLink.Commons.Interfaces.Repositories;
using ScadaLink.Commons.Messages.Notification;
using ScadaLink.Communication;
using ScadaLink.Security;
using NotificationOutboxPage = ScadaLink.CentralUI.Components.Pages.Monitoring.NotificationOutbox;
using NotificationReportPage = ScadaLink.CentralUI.Components.Pages.Notifications.NotificationReport;
namespace ScadaLink.CentralUI.Tests.Pages;
/// <summary>
/// bUnit rendering tests for the Notification Outbox monitoring page (Task 23).
/// bUnit rendering tests for the Notification Report page.
///
/// Testability note: <see cref="CommunicationService"/> is a concrete class with
/// non-virtual methods, so NSubstitute cannot intercept it. The outbox calls all
/// non-virtual methods, so NSubstitute cannot intercept it. The report calls all
/// route through an injected <see cref="IActorRef"/> (the notification-outbox
/// proxy), so the tests wire a real, lightweight <see cref="ActorSystem"/> with a
/// scripted <see cref="ReceiveActor"/> that replies with fixed responses — the
/// same seam <c>SetNotificationOutbox</c> exists for.
/// </summary>
public class NotificationOutboxPageTests : BunitContext
public class NotificationReportPageTests : BunitContext
{
private readonly ActorSystem _system = ActorSystem.Create("notif-outbox-tests");
private readonly ActorSystem _system = ActorSystem.Create("notif-report-tests");
private readonly CommunicationService _comms;
// Mutable scripted replies — individual tests can override before rendering.
private NotificationKpiResponse _kpiReply =
new("k", true, null, QueueDepth: 7, StuckCount: 2, ParkedCount: 1,
DeliveredLastInterval: 42, OldestPendingAge: TimeSpan.FromMinutes(9));
// Mutable scripted reply — individual tests can override before rendering.
private NotificationOutboxQueryResponse _queryReply =
new("q", true, null, new List<NotificationSummary>
{
@@ -54,7 +50,7 @@ public class NotificationOutboxPageTests : BunitContext
private readonly List<RetryNotificationRequest> _retryRequests = new();
private readonly List<DiscardNotificationRequest> _discardRequests = new();
public NotificationOutboxPageTests()
public NotificationReportPageTests()
{
_comms = new CommunicationService(
Options.Create(new CommunicationOptions()),
@@ -88,7 +84,7 @@ public class NotificationOutboxPageTests : BunitContext
[Fact]
public void Page_RequiresDeploymentPolicy()
{
var attr = typeof(NotificationOutboxPage)
var attr = typeof(NotificationReportPage)
.GetCustomAttributes(typeof(AuthorizeAttribute), true)
.Cast<AuthorizeAttribute>()
.FirstOrDefault();
@@ -97,28 +93,10 @@ public class NotificationOutboxPageTests : BunitContext
Assert.Equal(AuthorizationPolicies.RequireDeployment, attr!.Policy);
}
[Fact]
public void Renders_KpiTiles_WithValues()
{
var cut = Render<NotificationOutboxPage>();
// KPI data arrives via an async actor Ask after first render.
cut.WaitForAssertion(() =>
{
Assert.Contains("Queue Depth", cut.Markup);
Assert.Contains("Stuck", cut.Markup);
Assert.Contains("Parked", cut.Markup);
Assert.Contains("Delivered", cut.Markup);
// KPI numeric values surface in the tiles.
Assert.Contains(">7<", cut.Markup); // QueueDepth
Assert.Contains(">42<", cut.Markup); // DeliveredLastInterval
});
}
[Fact]
public void Renders_NotificationRows()
{
var cut = Render<NotificationOutboxPage>();
var cut = Render<NotificationReportPage>();
cut.WaitForAssertion(() =>
{
@@ -131,7 +109,7 @@ public class NotificationOutboxPageTests : BunitContext
[Fact]
public void StuckRow_IsBadged()
{
var cut = Render<NotificationOutboxPage>();
var cut = Render<NotificationReportPage>();
cut.WaitForAssertion(() =>
{
@@ -147,7 +125,7 @@ public class NotificationOutboxPageTests : BunitContext
[Fact]
public void ClickRetry_OnParkedRow_CallsRetryNotification()
{
var cut = Render<NotificationOutboxPage>();
var cut = Render<NotificationReportPage>();
cut.WaitForState(() => cut.Markup.Contains("Pump fault at Plant-A"));
@@ -168,7 +146,7 @@ public class NotificationOutboxPageTests : BunitContext
[Fact]
public void ClickDiscard_OnParkedRow_CallsDiscardNotification()
{
var cut = Render<NotificationOutboxPage>();
var cut = Render<NotificationReportPage>();
cut.WaitForState(() => cut.Markup.Contains("Pump fault at Plant-A"));
@@ -186,18 +164,6 @@ public class NotificationOutboxPageTests : BunitContext
});
}
[Fact]
public void KpiFailure_ShowsErrorMessage()
{
_kpiReply = new NotificationKpiResponse(
"k", false, "outbox repository unavailable", 0, 0, 0, 0, null);
var cut = Render<NotificationOutboxPage>();
cut.WaitForAssertion(() =>
Assert.Contains("outbox repository unavailable", cut.Markup));
}
[Fact]
public void QueryFailure_ShowsErrorMessage()
{
@@ -205,7 +171,7 @@ public class NotificationOutboxPageTests : BunitContext
"q", false, "outbox query backend unavailable",
new List<NotificationSummary>(), TotalCount: 0);
var cut = Render<NotificationOutboxPage>();
var cut = Render<NotificationReportPage>();
cut.WaitForAssertion(() =>
Assert.Contains("outbox query backend unavailable", cut.Markup));
@@ -226,9 +192,8 @@ public class NotificationOutboxPageTests : BunitContext
/// </summary>
private sealed class ScriptedOutboxActor : ReceiveActor
{
public ScriptedOutboxActor(NotificationOutboxPageTests test)
public ScriptedOutboxActor(NotificationReportPageTests test)
{
Receive<NotificationKpiRequest>(_ => Sender.Tell(test._kpiReply));
Receive<NotificationOutboxQueryRequest>(_ => Sender.Tell(test._queryReply));
Receive<RetryNotificationRequest>(r =>
{