using System.Security.Claims;
using Akka.Actor;
using Bunit;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using NSubstitute;
using ScadaLink.Commons.Entities.Sites;
using ScadaLink.Commons.Interfaces.Repositories;
using ScadaLink.Commons.Messages.Notification;
using ScadaLink.Communication;
using ScadaLink.HealthMonitoring;
using HealthPage = ScadaLink.CentralUI.Components.Pages.Monitoring.Health;
namespace ScadaLink.CentralUI.Tests.Pages;
///
/// bUnit rendering tests for the Health Monitoring dashboard (Task 24).
///
/// Scope: the Notification Outbox KPI tile row added to the Health dashboard.
/// is an interface (mockable), but
/// is a concrete class whose outbox calls
/// route through an injected notification-outbox ; the
/// tests reuse the scripted-actor seam established by the Notification Outbox
/// page tests (see NotificationOutboxPageTests).
///
public class HealthPageTests : BunitContext
{
private readonly ActorSystem _system = ActorSystem.Create("health-page-tests");
private readonly CommunicationService _comms;
// Mutable scripted reply — individual tests can override before rendering.
private NotificationKpiResponse _kpiReply =
new("k", true, null, QueueDepth: 12, StuckCount: 4, ParkedCount: 3,
DeliveredLastInterval: 88, OldestPendingAge: TimeSpan.FromMinutes(6));
public HealthPageTests()
{
_comms = new CommunicationService(
Options.Create(new CommunicationOptions()),
NullLogger.Instance);
var outbox = _system.ActorOf(Props.Create(() => new ScriptedOutboxActor(this)));
_comms.SetNotificationOutbox(outbox);
Services.AddSingleton(_comms);
var aggregator = Substitute.For();
aggregator.GetAllSiteStates()
.Returns(new Dictionary());
Services.AddSingleton(aggregator);
var siteRepo = Substitute.For();
siteRepo.GetAllSitesAsync(Arg.Any())
.Returns(Task.FromResult>(new List()));
Services.AddSingleton(siteRepo);
var claims = new[]
{
new Claim("Username", "tester"),
new Claim(ClaimTypes.Role, "Admin"),
};
var user = new ClaimsPrincipal(new ClaimsIdentity(claims, "TestAuth"));
Services.AddSingleton(new TestAuthStateProvider(user));
Services.AddAuthorizationCore();
}
[Fact]
public void Renders_OutboxKpiTiles_WithValues()
{
var cut = Render();
// KPI data arrives via an async actor Ask after first render.
cut.WaitForAssertion(() =>
{
Assert.Contains("Notification Outbox", cut.Markup);
Assert.Contains("Queue Depth", cut.Markup);
Assert.Contains("Stuck", cut.Markup);
Assert.Contains("Parked", cut.Markup);
// KPI numeric values surface in the tiles.
Assert.Contains(">12<", cut.Markup); // QueueDepth
Assert.Contains(">4<", cut.Markup); // StuckCount
Assert.Contains(">3<", cut.Markup); // ParkedCount
});
}
[Fact]
public void OutboxKpiFailure_ShowsGracefulFallback()
{
_kpiReply = new NotificationKpiResponse(
"k", false, "outbox repository unavailable", 0, 0, 0, 0, null);
var cut = Render();
cut.WaitForAssertion(() =>
{
// Failure must not crash the page; tiles fall back to a dash.
Assert.Contains("Notification Outbox", cut.Markup);
Assert.Contains("Queue Depth", cut.Markup);
Assert.Contains(">—<", cut.Markup);
});
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_system.Terminate().Wait(TimeSpan.FromSeconds(5));
}
base.Dispose(disposing);
}
///
/// Stand-in for the notification-outbox actor. Replies to the KPI request
/// with the test's currently-scripted response.
///
private sealed class ScriptedOutboxActor : ReceiveActor
{
public ScriptedOutboxActor(HealthPageTests test)
{
Receive(_ => Sender.Tell(test._kpiReply));
}
}
}