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

103 lines
4.3 KiB
C#

using System.Security.Claims;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using ZB.MOM.WW.ScadaBridge.CentralUI.Auth;
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Auth;
/// <summary>
/// Regression tests for session-expiry detection. The Blazor circuit's
/// <c>CookieAuthenticationStateProvider</c> serves a frozen constructor-time
/// principal, so <c>SessionExpiry</c> could never observe a server-side cookie
/// expiry by polling the auth state. The fix adds <c>GET /auth/ping</c>, an
/// endpoint evaluated per HTTP request (where the cookie middleware re-validates
/// the cookie): it returns 200 while the session is live and 401 once the
/// cookie has lapsed, giving <c>SessionExpiry</c> a real signal to redirect on.
/// </summary>
public class AuthPingEndpointTests
{
private static IReadOnlyList<RouteEndpoint> BuildEndpoints()
{
var builder = WebApplication.CreateBuilder();
builder.Services.AddRouting();
builder.Services.AddAntiforgery();
builder.Services.AddSingleton<ZB.MOM.WW.ScadaBridge.Security.SessionActivityTracker>();
builder.Services.AddSingleton(TimeProvider.System);
// Dispose the host: an undisposed WebApplication leaks its config
// PhysicalFileProvider (appsettings reload-watch FileSystemWatcher — a
// process-wide macOS run-loop thread) and a ConsoleLoggerProcessor
// thread, which keep the test host process alive after the run.
using var app = builder.Build();
app.MapAuthEndpoints();
return ((IEndpointRouteBuilder)app).DataSources
.SelectMany(ds => ds.Endpoints)
.OfType<RouteEndpoint>()
.ToList();
}
private static RouteEndpoint? Find(IReadOnlyList<RouteEndpoint> endpoints, string pattern, string method)
=> endpoints.FirstOrDefault(e =>
e.RoutePattern.RawText == pattern &&
(e.Metadata.GetMetadata<HttpMethodMetadata>()?.HttpMethods.Contains(method) ?? false));
/// <summary>Verifies that the GET route for the auth ping endpoint is mapped.</summary>
[Fact]
public void AuthPing_GetRoute_IsMapped()
{
var ping = Find(BuildEndpoints(), "/auth/ping", "GET");
Assert.NotNull(ping);
}
/// <summary>Verifies that an anonymous user receives a 401 response from the auth ping endpoint.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task AuthPing_AnonymousUser_Returns401()
{
var context = new DefaultHttpContext
{
User = new ClaimsPrincipal(new ClaimsIdentity()) // not authenticated
};
await AuthEndpoints.HandlePing(context);
Assert.Equal(StatusCodes.Status401Unauthorized, context.Response.StatusCode);
}
/// <summary>Verifies that an authenticated user receives a 200 response from the auth ping endpoint.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task AuthPing_AuthenticatedUser_Returns200()
{
var identity = new ClaimsIdentity(
new[] { new Claim(ClaimTypes.Name, "alice") }, authenticationType: "TestCookie");
var context = new DefaultHttpContext
{
User = new ClaimsPrincipal(identity)
};
await AuthEndpoints.HandlePing(context);
Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
}
/// <summary>Verifies that the auth ping endpoint returns a status code instead of redirecting through the cookie middleware.</summary>
[Fact]
public void AuthPing_DoesNotTriggerCookieRedirect()
{
// The endpoint must NOT use RequireAuthorization(): that would make the
// cookie middleware answer an expired request with a 302 to /login,
// which a fetch() follows transparently and reads as a 200 login page —
// SessionExpiry would never see the expiry. The endpoint allows
// anonymous access and decides 200/401 itself.
var ping = Find(BuildEndpoints(), "/auth/ping", "GET");
Assert.NotNull(ping);
var authorize = ping!.Metadata
.GetOrderedMetadata<Microsoft.AspNetCore.Authorization.IAuthorizeData>();
Assert.Empty(authorize);
}
}