From bee295d3ee7997f08847da29b105526ce3255da7 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Wed, 17 Jun 2026 11:04:13 -0400 Subject: [PATCH] fix(central-ui): mirror WaitForAttribute on inbound-script analysis RouteTarget Add WaitForAttribute(attributeName, targetValue, timeout, cancellationToken) to InboundScriptHost.RouteTarget and SandboxInboundScriptHost.RouteTarget, mirroring the shipped runtime signature in RouteHelper. Eliminates the false CS error the editor raised against valid Route.To("X").WaitForAttribute(...) calls in inbound API method scripts. Test asserts the call diagnoses clean under ScriptKind.InboundApi. --- .../ScriptAnalysis/InboundScriptHost.cs | 16 ++++++++++++++++ .../ScriptAnalysis/SandboxInboundScriptHost.cs | 15 +++++++++++++++ .../ScriptAnalysis/ScriptAnalysisServiceTests.cs | 16 ++++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/ScriptAnalysis/InboundScriptHost.cs b/src/ZB.MOM.WW.ScadaBridge.CentralUI/ScriptAnalysis/InboundScriptHost.cs index fda5d3d4..607f42bb 100644 --- a/src/ZB.MOM.WW.ScadaBridge.CentralUI/ScriptAnalysis/InboundScriptHost.cs +++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/ScriptAnalysis/InboundScriptHost.cs @@ -98,5 +98,21 @@ public class InboundScriptHost IReadOnlyDictionary attributeValues, System.Threading.CancellationToken cancellationToken = default) => System.Threading.Tasks.Task.CompletedTask; + + /// + /// Waits until a named attribute on the target instance reaches the specified value, + /// or until the timeout elapses. + /// + /// The name of the attribute to watch. + /// The value to wait for. + /// Maximum time to wait before returning false. + /// Cancellation token. + /// A task that resolves to true if the attribute reached the target value within the timeout, false otherwise. + public System.Threading.Tasks.Task WaitForAttribute( + string attributeName, + object? targetValue, + System.TimeSpan timeout, + System.Threading.CancellationToken cancellationToken = default) => + System.Threading.Tasks.Task.FromResult(false); } } diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/ScriptAnalysis/SandboxInboundScriptHost.cs b/src/ZB.MOM.WW.ScadaBridge.CentralUI/ScriptAnalysis/SandboxInboundScriptHost.cs index 639a0f40..2c155945 100644 --- a/src/ZB.MOM.WW.ScadaBridge.CentralUI/ScriptAnalysis/SandboxInboundScriptHost.cs +++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/ScriptAnalysis/SandboxInboundScriptHost.cs @@ -104,6 +104,21 @@ public class SandboxInboundScriptHost CancellationToken cancellationToken = default) => throw Unavailable("SetAttributes(...)"); + /// + /// Always throws ; cross-site routing is unavailable in a Test Run. + /// + /// Attribute name (included in the exception message). + /// Unused target value. + /// Unused timeout. + /// Unused token. + /// Never returns; always throws . + public Task WaitForAttribute( + string attributeName, + object? targetValue, + TimeSpan timeout, + CancellationToken cancellationToken = default) => + throw Unavailable($"WaitForAttribute(\"{attributeName}\")"); + private ScriptSandboxException Unavailable(string operation) => new($"Route.To(\"{_instanceCode}\").{operation} is not available in Test Run — " + "cross-site routing needs a deployed site reachable over the cluster transport."); diff --git a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/ScriptAnalysis/ScriptAnalysisServiceTests.cs b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/ScriptAnalysis/ScriptAnalysisServiceTests.cs index 4b7500d3..a044e39a 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/ScriptAnalysis/ScriptAnalysisServiceTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/ScriptAnalysis/ScriptAnalysisServiceTests.cs @@ -644,4 +644,20 @@ public class ScriptAnalysisServiceTests Assert.All(lines, l => Assert.Equal($"S{i}", l.Trim())); } } + + // ── Inbound-script analysis surface ────────────────────────────────── + + [Fact] + public void InboundScript_WaitForAttribute_DiagnosesClean() + { + // WaitForAttribute is a shipped inbound-script helper. The editor must + // not flag it as an error: the InboundScriptHost mirror must expose the + // method so Roslyn resolves it during static analysis. + var resp = _svc.Diagnose(new DiagnoseRequest( + Code: "var ok = await Route.To(\"site-a\").WaitForAttribute(\"Flag\", true, System.TimeSpan.FromSeconds(5));", + Kind: ScriptKind.InboundApi)); + + Assert.DoesNotContain(resp.Markers, m => m.Code.StartsWith("CS")); + Assert.DoesNotContain(resp.Markers, m => m.Code.StartsWith("SCADA")); + } }