Complete management API and CLI command parity

This commit is contained in:
Joseph Doherty
2026-09-10 00:51:45 -04:00
parent 477e2abe97
commit 2750bd2809
38 changed files with 1637 additions and 199 deletions
@@ -355,6 +355,7 @@ public class AuditDrilldownDrawerTests : BunitContext
invocation => invocation.Arguments.Count == 1
&& invocation.Arguments[0] is string s
&& s.StartsWith("curl ", StringComparison.Ordinal));
clipboardCall.SetVoidResult();
// Build an event with a {headers, body} RequestSummary so the cURL
// builder has material to fold in.
@@ -301,6 +301,7 @@ public class AuditEventDetailTests : BunitContext
invocation => invocation.Arguments.Count == 1
&& invocation.Arguments[0] is string s
&& s.StartsWith("curl ", StringComparison.Ordinal));
clipboardCall.SetVoidResult();
var ev = MakeEvent(
channel: AuditChannel.ApiOutbound,
@@ -1,3 +1,4 @@
using Microsoft.JSInterop;
using System.Security.Claims;
using ZB.MOM.WW.ScadaBridge.Security;
using Bunit;
@@ -20,6 +21,13 @@ namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests;
/// </summary>
public class DataConnectionsPageTests : BunitContext
{
[Fact]
public void StorageSaveFixture_CompletesInvocationImmediately()
{
var invocation = JSInterop.JSRuntime.InvokeVoidAsync("treeviewStorage.save", "fixture-probe", "[]");
Assert.True(invocation.IsCompletedSuccessfully, "The storage-save fixture must complete JS calls before component disposal.");
}
private readonly ISiteRepository _siteRepo = Substitute.For<ISiteRepository>();
// M9-T25: the design page now injects IConnectionHealthQueryService to render
// per-connection live health badges. A substitute defaulting to an empty map
@@ -51,7 +59,7 @@ public class DataConnectionsPageTests : BunitContext
new Dictionary<int, ConnectionHealth>()));
JSInterop.Setup<string?>("treeviewStorage.load", _ => true).SetResult(null);
JSInterop.SetupVoid("treeviewStorage.save", _ => true);
JSInterop.SetupVoid("treeviewStorage.save", _ => true).SetVoidResult();
}
private void AddTestAuth()
@@ -61,7 +61,7 @@ public class JsInteropLoggingTests : BunitContext
JSInterop.SetupVoid("document.body.classList.add", "modal-open")
.SetException(new JSException("body lock failed"));
// Focus and any other interop is harmless here — allow it loosely.
JSInterop.SetupVoid("document.body.classList.remove", "modal-open");
JSInterop.SetupVoid("document.body.classList.remove", "modal-open").SetVoidResult();
var cut = Render<DiffDialog>();
cut.InvokeAsync(() => cut.Instance.ShowAsync("Compare", "a", "b"));
@@ -84,7 +84,7 @@ public class JsInteropLoggingTests : BunitContext
JSInterop.Mode = JSRuntimeMode.Strict;
JSInterop.SetupVoid("document.body.classList.add", "modal-open")
.SetException(new JSDisconnectedException("circuit gone"));
JSInterop.SetupVoid("document.body.classList.remove", "modal-open");
JSInterop.SetupVoid("document.body.classList.remove", "modal-open").SetVoidResult();
var cut = Render<DiffDialog>();
cut.InvokeAsync(() => cut.Instance.ShowAsync("Compare", "a", "b"));
@@ -53,7 +53,7 @@ public class TreeViewKeyboardNavigationTests : BunitContext
{
// Checkbox mode pushes the tri-state via JS interop after every render;
// stub it so bUnit's strict mode doesn't blow up on the unmocked call.
JSInterop.SetupVoid("treeviewStorage.setIndeterminate", _ => true);
JSInterop.SetupVoid("treeviewStorage.setIndeterminate", _ => true).SetVoidResult();
}
return Render<TreeView<TestNode>>(parameters =>
@@ -37,7 +37,7 @@ public class TreeViewStorageResilienceTests : BunitContext
// A garbage payload that is not valid JSON for a List<string>.
JSInterop.Setup<string?>("treeviewStorage.load", _ => true)
.SetResult("{not json at all]");
JSInterop.SetupVoid("treeviewStorage.save", _ => true);
JSInterop.SetupVoid("treeviewStorage.save", _ => true).SetVoidResult();
// Pre-fix: OnAfterRenderAsync threw JsonException out of the unguarded
// Deserialize call. Post-fix: the corrupt payload is ignored.
@@ -53,7 +53,7 @@ public class TreeViewStorageResilienceTests : BunitContext
// Valid JSON, but not a List<string> — an object, not an array.
JSInterop.Setup<string?>("treeviewStorage.load", _ => true)
.SetResult("{\"unexpected\": true}");
JSInterop.SetupVoid("treeviewStorage.save", _ => true);
JSInterop.SetupVoid("treeviewStorage.save", _ => true).SetVoidResult();
var cut = BuildTree();
@@ -44,7 +44,7 @@ public class TemplatesPageTests : BunitContext
// against sessionStorage (`templates-tree` key). bUnit requires explicit
// stubs for all JS interop calls, otherwise rendering throws.
JSInterop.Setup<string?>("treeviewStorage.load", _ => true).SetResult(null);
JSInterop.SetupVoid("treeviewStorage.save", _ => true);
JSInterop.SetupVoid("treeviewStorage.save", _ => true).SetVoidResult();
}
private void AddTestAuth(params string[] roles)
@@ -87,7 +87,7 @@ public class TopologyLiveUpdatesTimerDisposeRaceTests : BunitContext
Services.AddScoped<ZB.MOM.WW.ScadaBridge.CentralUI.Auth.SiteScopeService>();
JSInterop.Setup<string?>("treeviewStorage.load", _ => true).SetResult(null);
JSInterop.SetupVoid("treeviewStorage.save", _ => true);
JSInterop.SetupVoid("treeviewStorage.save", _ => true).SetVoidResult();
SeedEmptyRepos();
}
@@ -1,3 +1,4 @@
using Microsoft.JSInterop;
using System.Security.Claims;
using System.Text.Json;
using ZB.MOM.WW.ScadaBridge.Security;
@@ -34,6 +35,13 @@ namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests;
/// </summary>
public class TopologyPageTests : BunitContext
{
[Fact]
public void StorageSaveFixture_CompletesInvocationImmediately()
{
var invocation = JSInterop.JSRuntime.InvokeVoidAsync("treeviewStorage.save", "fixture-probe", "[]");
Assert.True(invocation.IsCompletedSuccessfully, "The storage-save fixture must complete JS calls before component disposal.");
}
private readonly ITemplateEngineRepository _repo = Substitute.For<ITemplateEngineRepository>();
private readonly ISiteRepository _siteRepo = Substitute.For<ISiteRepository>();
private readonly IDeploymentManagerRepository _deployRepo = Substitute.For<IDeploymentManagerRepository>();
@@ -97,7 +105,7 @@ public class TopologyPageTests : BunitContext
// TreeView persists expansion state via JS interop. Stub the calls so render doesn't throw.
JSInterop.Setup<string?>("treeviewStorage.load", _ => true).SetResult(null);
JSInterop.SetupVoid("treeviewStorage.save", _ => true);
JSInterop.SetupVoid("treeviewStorage.save", _ => true).SetVoidResult();
}
private void AddTestAuth()
@@ -38,7 +38,7 @@ public class TreeViewMultiSelectTests : BunitContext
{
// Indeterminate is set via JS interop after each render. We stub it so
// bUnit's strict mode doesn't blow up on the unmocked call.
JSInterop.SetupVoid("treeviewStorage.setIndeterminate", _ => true);
JSInterop.SetupVoid("treeviewStorage.setIndeterminate", _ => true).SetVoidResult();
return Render<TreeView<TestNode>>(parameters =>
{
@@ -348,7 +348,7 @@ public class TreeViewTests : BunitContext
public void SessionStorage_Set_ExpandWritesToStorage()
{
JSInterop.Setup<string?>("treeviewStorage.load", _ => true).SetResult(null);
JSInterop.SetupVoid("treeviewStorage.save", _ => true);
JSInterop.SetupVoid("treeviewStorage.save", _ => true).SetVoidResult();
var cut = RenderTreeView(storageKey: "test-tree");
@@ -366,7 +366,7 @@ public class TreeViewTests : BunitContext
public void SessionStorage_RestoresExpandedOnMount()
{
JSInterop.Setup<string?>("treeviewStorage.load", _ => true).SetResult("[\"a\"]");
JSInterop.SetupVoid("treeviewStorage.save", _ => true);
JSInterop.SetupVoid("treeviewStorage.save", _ => true).SetVoidResult();
var cut = RenderTreeView(storageKey: "test-tree");
@@ -381,7 +381,7 @@ public class TreeViewTests : BunitContext
{
// Storage returns empty array — meaning user explicitly collapsed everything
JSInterop.Setup<string?>("treeviewStorage.load", _ => true).SetResult("[]");
JSInterop.SetupVoid("treeviewStorage.save", _ => true);
JSInterop.SetupVoid("treeviewStorage.save", _ => true).SetVoidResult();
var cut = RenderTreeView(
storageKey: "test-tree",