Files
ScadaBridge/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Pages/PageScopedDetailStateTests.cs
T
Joseph Doherty 513fa1aa16 docs(tests): fix CommentChecker findings across tests/ — 13,392 → 0 (repo-wide scan clean)
Comment/XML-doc changes only:
- add summaries to ~10k test methods, fixtures, fakes and helpers
  (+ <param> on theories, <returns> on async Task methods,
  <inheritdoc/> on interface implementations the checker can resolve)
- strip ~2.6k project-tracking references (WP*, P*-*, row-NN, Task N,
  #NN, Component-0NN) from comments, keeping the explanatory text;
  reword false positives in comments only
- real docs on fakes implementing BCL/library interfaces
  (IDisposable, ILogger, IOptionsMonitor, IAsyncLifetime)
- reword the last 5 src/ false positives (CWE-1236, failures-1,
  year-0001, M365)

Only non-comment edits: four one-line nested classes split across lines
so members can carry docs. Build 0 errors / 0 warnings.
2026-10-07 15:05:49 -04:00

129 lines
5.8 KiB
C#

using System.Reflection;
using System.Security.Claims;
using Bunit;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using NSubstitute;
using ZB.MOM.WW.ScadaBridge.CentralUI.Auth;
using ZB.MOM.WW.ScadaBridge.CentralUI.Components.Shared;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Sites;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
using ZB.MOM.WW.ScadaBridge.Communication;
using AuditLogPage = ZB.MOM.WW.ScadaBridge.CentralUI.Components.Pages.Audit.ConfigurationAuditLog;
using ParkedMessagesPage = ZB.MOM.WW.ScadaBridge.CentralUI.Components.Pages.Monitoring.ParkedMessages;
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Pages;
/// <summary>
/// The detail modal/drawer on these surfaces is gated on a held id — user intent —
/// rather than on the row still resolving, so that a data refresh can never unmount
/// the subtree mid-render and dispose its event handlers (see
/// NotificationReportDetailModalTests.Modal_StaysOpen_WhenItsRowLeavesThePage).
///
/// That makes it the surface's own job to clear the id on navigation actions that
/// invalidate page-scoped state, and the criterion is per-surface:
///
/// • ParkedMessages and ConfigurationAuditLog have NO keyed detail fetch — their
/// modal content resolves from the loaded page alone. An entry paged out of view
/// can never resolve again, so the surface would sit on a permanently empty
/// notice recoverable only by paging back. These must clear on paging.
///
/// • NotificationReport and SiteCallsReport DO fetch detail by id. Their modal
/// still shows real content after the row leaves the page, so they deliberately
/// do NOT clear on paging, and are not covered here.
///
/// Clearing on an explicit navigation action is intent, not a resolve-driven
/// unmount, so it does not reopen the handler-disposal race.
/// </summary>
public class PageScopedDetailStateTests : BunitContext
{
private static void SetPrivate(object target, string field, object? value) =>
target.GetType()
.GetField(field, BindingFlags.Instance | BindingFlags.NonPublic)!
.SetValue(target, value);
private static object? GetPrivate(object target, string field) =>
target.GetType()
.GetField(field, BindingFlags.Instance | BindingFlags.NonPublic)!
.GetValue(target);
private static Task InvokePrivate(object target, string method, params object[] args) =>
(Task)target.GetType()
.GetMethod(method, BindingFlags.Instance | BindingFlags.NonPublic)!
.Invoke(target, args)!;
private void RegisterCommonServices()
{
var identity = new ClaimsIdentity(
new[] { new Claim(ClaimTypes.Name, "deployer") }, "TestCookie");
var stubAuth = new StubAuthStateProvider(
new AuthenticationState(new ClaimsPrincipal(identity)));
Services.AddSingleton<AuthenticationStateProvider>(stubAuth);
Services.AddScoped(_ => new SiteScopeService(stubAuth));
Services.AddScoped<IDialogService, DialogService>();
JSInterop.Mode = JSRuntimeMode.Loose;
}
/// <summary>Verifies that paging the Parked Messages list closes the open detail drawer.</summary>
/// <param name="pagingMethod">The name of the paging method to invoke.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
[Theory]
[InlineData("PrevPage")]
[InlineData("NextPage")]
public async Task ParkedMessages_Paging_ClosesTheDrawer(string pagingMethod)
{
RegisterCommonServices();
var siteRepo = Substitute.For<ISiteRepository>();
siteRepo.GetAllSitesAsync().Returns(new List<Site>());
Services.AddSingleton(siteRepo);
Services.AddSingleton(new CommunicationService(
Options.Create(new CommunicationOptions()),
NullLogger<CommunicationService>.Instance));
var cut = Render<ParkedMessagesPage>();
// Drawer open on some message.
await cut.InvokeAsync(() =>
SetPrivate(cut.Instance, "_drawerMessageId", "msg-being-viewed"));
Assert.Equal("msg-being-viewed", GetPrivate(cut.Instance, "_drawerMessageId"));
await cut.InvokeAsync(() => InvokePrivate(cut.Instance, pagingMethod));
// Paging away invalidates it — this surface has no keyed detail to fall back
// on, so leaving it set would strand the drawer on an empty notice.
Assert.Null(GetPrivate(cut.Instance, "_drawerMessageId"));
}
/// <summary>Verifies that paging the configuration audit log closes the open state modal.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task ConfigurationAuditLog_Paging_ClosesTheStateModal()
{
RegisterCommonServices();
Services.AddSingleton(Substitute.For<ICentralUiRepository>());
var cut = Render<AuditLogPage>();
await cut.InvokeAsync(() => SetPrivate(cut.Instance, "_modalEntryId", 4242));
Assert.Equal(4242, GetPrivate(cut.Instance, "_modalEntryId"));
await cut.InvokeAsync(() => InvokePrivate(cut.Instance, "OnPageChanged", 2));
Assert.Null(GetPrivate(cut.Instance, "_modalEntryId"));
}
private sealed class StubAuthStateProvider : AuthenticationStateProvider
{
private readonly AuthenticationState _state;
/// <summary>Initializes the stub with the authentication state it returns.</summary>
/// <param name="state">The authentication state to return.</param>
public StubAuthStateProvider(AuthenticationState state) => _state = state;
/// <inheritdoc />
public override Task<AuthenticationState> GetAuthenticationStateAsync()
=> Task.FromResult(_state);
}
}