fix(centralui)+test(centralui): Low-wave integration — T12 Topology race fixture gains T13's IStaleInstanceProbe dependency; T11 sandbox-run continuation skips StateHasChanged after Dispose

Cross-task regression caught by the combined sweep: TopologyLiveUpdatesTimerDisposeRaceTests
(T12) builds a real DeploymentService, whose constructor grew an IStaleInstanceProbe
parameter in T13 (P7-6 up-to-date pre-filter) — 4 DI-resolution failures on the
integration branch that neither task saw in isolation. Registered a substitute, matching
TopologyPageTests.

Bundle review Minor: ApiMethodForm/SharedScriptForm copied TemplateScriptDialog's sandbox
pattern exactly, whose finally re-renders unconditionally; the plan asked the continuation
to be guarded on disposal, so a _disposed flag now suppresses the post-cancel
StateHasChanged (the reference dialog keeps its original shape — out of T11's scope).
This commit is contained in:
Joseph Doherty
2026-08-20 08:42:56 -04:00
parent 3bf1537ea0
commit 57c8973b0d
3 changed files with 10 additions and 2 deletions
@@ -232,6 +232,7 @@
private Dictionary<string, object?> _paramValues = new(); private Dictionary<string, object?> _paramValues = new();
private ScriptAnalysis.SandboxRunResult? _runResult; private ScriptAnalysis.SandboxRunResult? _runResult;
private CancellationTokenSource? _runCts; private CancellationTokenSource? _runCts;
private bool _disposed;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
@@ -381,6 +382,7 @@
/// <summary>Cancels an in-flight Test Run when the component is torn down.</summary> /// <summary>Cancels an in-flight Test Run when the component is torn down.</summary>
public void Dispose() public void Dispose()
{ {
_disposed = true;
_runCts?.Cancel(); _runCts?.Cancel();
_runCts?.Dispose(); _runCts?.Dispose();
_runCts = null; _runCts = null;
@@ -422,7 +424,8 @@
finally finally
{ {
_running = false; _running = false;
StateHasChanged(); // The run may have been cancelled BY Dispose — the component is gone, don't re-render it.
if (!_disposed) StateHasChanged();
} }
} }
@@ -202,6 +202,7 @@
private Dictionary<string, object?> _paramValues = new(); private Dictionary<string, object?> _paramValues = new();
private ScriptAnalysis.SandboxRunResult? _runResult; private ScriptAnalysis.SandboxRunResult? _runResult;
private CancellationTokenSource? _runCts; private CancellationTokenSource? _runCts;
private bool _disposed;
// CentralUI-024: delegates to the shared helper so the claim type stays // CentralUI-024: delegates to the shared helper so the claim type stays
// resolved through JwtTokenService rather than a duplicated magic string. // resolved through JwtTokenService rather than a duplicated magic string.
@@ -245,6 +246,7 @@
/// <summary>Cancels an in-flight Test Run when the component is torn down.</summary> /// <summary>Cancels an in-flight Test Run when the component is torn down.</summary>
public void Dispose() public void Dispose()
{ {
_disposed = true;
_runCts?.Cancel(); _runCts?.Cancel();
_runCts?.Dispose(); _runCts?.Dispose();
_runCts = null; _runCts = null;
@@ -344,7 +346,8 @@
finally finally
{ {
_running = false; _running = false;
StateHasChanged(); // The run may have been cancelled BY Dispose — the component is gone, don't re-render it.
if (!_disposed) StateHasChanged();
} }
} }
@@ -56,6 +56,8 @@ public class TopologyLiveUpdatesTimerDisposeRaceTests : BunitContext
Services.AddSingleton(_siteRepo); Services.AddSingleton(_siteRepo);
Services.AddSingleton(_deployRepo); Services.AddSingleton(_deployRepo);
Services.AddSingleton(Substitute.For<ZB.MOM.WW.ScadaBridge.DeploymentManager.IFlatteningPipeline>()); Services.AddSingleton(Substitute.For<ZB.MOM.WW.ScadaBridge.DeploymentManager.IFlatteningPipeline>());
// DeploymentService takes IStaleInstanceProbe since the Low-wave P7-6 pre-filter (T13).
Services.AddSingleton(Substitute.For<ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Transport.IStaleInstanceProbe>());
Services.AddSingleton(_audit); Services.AddSingleton(_audit);
var comms = new CommunicationService( var comms = new CommunicationService(