Files
lmxopcua/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Raw/AdminUiFormSweepGuardTests.cs
T
Joseph Doherty 1475991fd6 fix(adminui,client): error boundary inside the interactive scope, stale-commit OnSaved, AbCip browse delay emit, DisposeBridge cancel (review R3)
M1: PageErrorBoundary wraps the body of every InteractiveServer page; the inert layout boundary is removed.
m1: DisposeBridge cancels its CTS when the 5 s wait times out.
m2: a CSV import that commits after the modal closed still raises OnSaved.
m3: document SecurityMode=None no-fallback in Client.CLI.md / Client.UI.md.
m4: browsePageDelayMs written only when loaded or changed; negative flagged.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 07:31:19 -04:00

52 lines
2.2 KiB
C#

using Shouldly;
using Xunit;
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Raw;
/// <summary>
/// Issue #604 source-scan guards (Razor has no bUnit): an <c>EditForm</c> with a validator but nothing
/// to show its messages is a silent no-op Save, the interactive pages' error boundary is pinned in PageErrorBoundaryTests, and the
/// API-key Delete must not fire straight from the row button.
/// </summary>
public sealed class AdminUiFormSweepGuardTests
{
/// <summary>EditForm pages whose validation messages are rendered by a child/section component.</summary>
private static readonly HashSet<string> AllowList = new(StringComparer.Ordinal);
[Fact]
public void Every_razor_with_an_EditForm_and_a_DataAnnotationsValidator_renders_validation_messages()
{
var offenders = new List<string>();
foreach (var file in Directory.EnumerateFiles(ComponentsDir(), "*.razor", SearchOption.AllDirectories))
{
var text = File.ReadAllText(file);
if (!text.Contains("<EditForm") || !text.Contains("<DataAnnotationsValidator")) { continue; }
if (text.Contains("<ValidationSummary") || text.Contains("<ValidationMessage")) { continue; }
var name = Path.GetRelativePath(ComponentsDir(), file).Replace('\\', '/');
if (!AllowList.Contains(name)) { offenders.Add(name); }
}
offenders.ShouldBeEmpty("these forms validate but never show a message: " + string.Join(", ", offenders));
}
[Fact]
public void ApiKeys_delete_button_goes_through_the_confirm_dialog()
{
var text = File.ReadAllText(Path.Combine(ComponentsDir(), "Pages", "ApiKeys.razor"));
text.ShouldContain("<RawConfirmDialog");
text.ShouldNotContain("DeleteAsync(k.ApiKeyId)");
}
private static string ComponentsDir()
{
var dir = new DirectoryInfo(AppContext.BaseDirectory);
while (dir is not null)
{
var candidate = Path.Combine(dir.FullName, "src", "Server", "ZB.MOM.WW.OtOpcUa.AdminUI", "Components");
if (Directory.Exists(candidate)) { return candidate; }
dir = dir.Parent;
}
throw new DirectoryNotFoundException("AdminUI Components directory not found above the test binaries.");
}
}