fix(adminui): register the /dev/context-menu-demo route only in DEBUG builds (R04 S7)

The dev-only demo page shipped routable in the production assembly. Its route
moves from @page to a code-behind [Route] under #if DEBUG, so a Release build
keeps the component type (nothing referencing it breaks) but has no /dev/*
route. The page-authorization guard classifies it under #if DEBUG, and
DevRouteGatingTests asserts each configuration's half (Debug: exactly the demo
route; Release: none) with a positive control. Both configurations pass.

Refs #745
Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
This commit is contained in:
Joseph Doherty
2026-10-22 17:57:54 -04:00
parent 746267c2a2
commit f12573d3fa
4 changed files with 64 additions and 1 deletions
@@ -0,0 +1,43 @@
using System.Reflection;
using Microsoft.AspNetCore.Components;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.AdminUI.Components;
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Authorization;
/// <summary>
/// R04 S7: <c>/dev/*</c> routes exist only in DEBUG builds. The test project builds in the same
/// configuration as the AdminUI assembly, so each configuration asserts its own half — run
/// <c>dotnet test -c Release</c> to exercise the production half.
/// </summary>
[Trait("Category", "Unit")]
public sealed class DevRouteGatingTests
{
private static IReadOnlyList<string> DevRoutes() =>
typeof(Routes).Assembly.GetTypes()
.SelectMany(t => t.GetCustomAttributes<RouteAttribute>(inherit: false))
.Select(r => r.Template)
.Where(t => t.StartsWith("/dev/", StringComparison.OrdinalIgnoreCase))
.ToList();
[Fact]
public void Dev_routes_follow_the_build_configuration()
{
#if DEBUG
DevRoutes().ShouldBe([ZB.MOM.WW.OtOpcUa.AdminUI.Components.Pages.Dev.ContextMenuDemo.RoutePath]);
#else
DevRoutes().ShouldBeEmpty();
#endif
}
[Fact]
public void Production_routes_are_present_in_every_configuration()
{
// Positive control: the scan sees routes at all (an empty assembly scan would pass the Release half).
typeof(Routes).Assembly.GetTypes()
.SelectMany(t => t.GetCustomAttributes<RouteAttribute>(inherit: false))
.Select(r => r.Template)
.ShouldContain("/hosts");
}
}
@@ -59,7 +59,10 @@ public class PageAuthorizationGuardTests
// ── AuthenticatedRead (17): dashboards, lists, live tails (read-only) + the dev ContextMenu demo ──
[typeof(Home)] = AdminUiPolicies.AuthenticatedRead,
#if DEBUG
// Routable only in DEBUG builds (R04 S7) — a Release build registers no /dev/* route.
[typeof(global::ZB.MOM.WW.OtOpcUa.AdminUI.Components.Pages.Dev.ContextMenuDemo)] = AdminUiPolicies.AuthenticatedRead,
#endif
[typeof(Fleet)] = AdminUiPolicies.AuthenticatedRead,
[typeof(global::ZB.MOM.WW.OtOpcUa.AdminUI.Components.Pages.FleetDiagnostics)] = AdminUiPolicies.AuthenticatedRead,
[typeof(global::ZB.MOM.WW.OtOpcUa.AdminUI.Components.Pages.Hosts)] = AdminUiPolicies.AuthenticatedRead,