Files
ScadaBridge/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Components/ConnectionCertificatesTests.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

151 lines
6.3 KiB
C#

using System.Security.Claims;
using Bunit;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.DependencyInjection;
using NSubstitute;
using ZB.MOM.WW.ScadaBridge.CentralUI.Services;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Sites;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
using ZB.MOM.WW.ScadaBridge.Security;
using ConnectionCertificates =
ZB.MOM.WW.ScadaBridge.CentralUI.Components.Pages.Design.ConnectionCertificates;
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Components;
/// <summary>
/// Covers the connection-certificates management page: an
/// Administrator-gated page that resolves a data connection's owning site, lists
/// the site's trusted-peer / rejected certificates via
/// <see cref="ICertManagementService.ListAsync"/>, and removes a certificate via
/// <see cref="ICertManagementService.RemoveAsync"/> (the store is node-wide per
/// site node).
/// </summary>
public class ConnectionCertificatesTests : BunitContext
{
private readonly ICertManagementService _certs = Substitute.For<ICertManagementService>();
private readonly ISiteRepository _siteRepo = Substitute.For<ISiteRepository>();
private const int ConnectionId = 7;
private const string SiteIdentifier = "plant-a";
/// <summary>Initializes the test context and registers the services the page requires.</summary>
public ConnectionCertificatesTests()
{
Services.AddSingleton(_certs);
Services.AddSingleton(_siteRepo);
// The page resolves the connection → owning site so the cert relay targets
// the right site identifier (the trusted-peer store is node-wide per site node).
var connection = new DataConnection("PLC-OPC", "OpcUa", 1) { Id = ConnectionId };
_siteRepo.GetDataConnectionByIdAsync(ConnectionId, Arg.Any<CancellationToken>())
.Returns(Task.FromResult<DataConnection?>(connection));
_siteRepo.GetSiteByIdAsync(1, Arg.Any<CancellationToken>())
.Returns(Task.FromResult<Site?>(new Site("Plant-A", SiteIdentifier) { Id = 1 }));
UseRoles(Roles.Administrator);
}
private static ClaimsPrincipal BuildPrincipal(params string[] roles)
{
var claims = new List<Claim> { new(JwtTokenService.UsernameClaimType, "tester") };
claims.AddRange(roles.Select(r => new Claim(JwtTokenService.RoleClaimType, r)));
return new ClaimsPrincipal(new ClaimsIdentity(claims, "TestAuth"));
}
private void UseRoles(params string[] roles)
{
Services.AddSingleton<AuthenticationStateProvider>(
new TestAuthStateProvider(BuildPrincipal(roles)));
Services.AddAuthorizationCore();
Services.AddTestConfigurationMutations();
AuthorizationPolicies.AddScadaBridgeAuthorization(Services);
Services.AddSingleton<IAuthorizationService, DefaultAuthorizationService>();
}
private static TrustedCertInfo Cert(string thumbprint, bool rejected = false) => new(
Thumbprint: thumbprint,
Subject: $"CN={thumbprint}",
Issuer: "CN=ca",
NotBeforeUtc: new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc),
NotAfterUtc: new DateTime(2027, 1, 1, 0, 0, 0, DateTimeKind.Utc),
Rejected: rejected);
private IRenderedComponent<ConnectionCertificates> RenderPage()
{
var host = Render<CascadingAuthenticationState>(parameters => parameters
.Add(p => p.ChildContent, (RenderFragment)(builder =>
{
builder.OpenComponent<ConnectionCertificates>(0);
builder.AddAttribute(1, nameof(ConnectionCertificates.Id), ConnectionId);
builder.CloseComponent();
})));
return host.FindComponent<ConnectionCertificates>();
}
/// <summary>Verifies that the page lists both certificates from the owning site.</summary>
[Fact]
public void Lists_Two_Certs_From_Owning_Site()
{
_certs.ListAsync(SiteIdentifier, Arg.Any<CancellationToken>())
.Returns(new CertTrustResult(true, null, new[]
{
Cert("AAAA1111"),
Cert("BBBB2222", rejected: true),
}));
var cut = RenderPage();
Assert.NotEmpty(cut.FindAll("[data-test=connection-certificates]"));
var rows = cut.FindAll("[data-test=cert-row]");
Assert.Equal(2, rows.Count);
Assert.Contains("AAAA1111", cut.Markup);
Assert.Contains("BBBB2222", cut.Markup);
// List was resolved against the connection's owning site identifier.
_certs.Received(1).ListAsync(SiteIdentifier, Arg.Any<CancellationToken>());
}
/// <summary>Verifies that removing a certificate calls RemoveAsync with the site identifier and thumbprint.</summary>
[Fact]
public void Remove_Calls_RemoveAsync_With_SiteIdentifier_And_Thumbprint()
{
_certs.ListAsync(SiteIdentifier, Arg.Any<CancellationToken>())
.Returns(new CertTrustResult(true, null, new[] { Cert("AAAA1111") }));
_certs.RemoveAsync(SiteIdentifier, "AAAA1111", Arg.Any<CancellationToken>())
.Returns(new CertTrustResult(true, null, null));
var cut = RenderPage();
cut.Find("[data-test=cert-remove-btn]").Click();
_certs.Received(1).RemoveAsync(SiteIdentifier, "AAAA1111", Arg.Any<CancellationToken>());
}
/// <summary>Verifies that an empty certificate store shows the empty-state note.</summary>
[Fact]
public void Empty_Store_Shows_Empty_Note()
{
_certs.ListAsync(SiteIdentifier, Arg.Any<CancellationToken>())
.Returns(new CertTrustResult(true, null, Array.Empty<TrustedCertInfo>()));
var cut = RenderPage();
Assert.NotEmpty(cut.FindAll("[data-test=cert-empty]"));
Assert.Empty(cut.FindAll("[data-test=cert-row]"));
}
/// <summary>Verifies that a list failure shows a load error.</summary>
[Fact]
public void List_Failure_Shows_Load_Error()
{
_certs.ListAsync(SiteIdentifier, Arg.Any<CancellationToken>())
.Returns(new CertTrustResult(false, "Site unreachable.", null));
var cut = RenderPage();
var error = cut.Find("[data-test=cert-load-error]");
Assert.Contains("Site unreachable.", error.TextContent);
}
}