diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/InstanceConfigure.razor b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/InstanceConfigure.razor
index 7ab70a3f..db92a690 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/InstanceConfigure.razor
+++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/InstanceConfigure.razor
@@ -158,9 +158,10 @@
Save Bindings
@* Test Bindings: one-shot live read of every bound attribute
whose row has a connection picked AND an effective tag
- path. Disabled when no testable rows. Currently OPC UA
- only — other protocols (none yet) would need their own
- wire+adapter support to round-trip through ReadTagValuesCommand. *@
+ path. Disabled when no testable rows. Protocol-agnostic —
+ any connection whose adapter implements ReadBatchAsync
+ (OPC UA and MxGateway today) round-trips through
+ ReadTagValuesCommand. *@
@@ -612,8 +613,10 @@
///
/// Builds the list of testable rows: attributes that have a connection
- /// picked AND a non-empty effective tag path AND an OPC UA connection
- /// (the only protocol routed through ReadTagValuesCommand today).
+ /// picked AND a non-empty effective tag path. Protocol-agnostic — every
+ /// data-connection adapter implements ReadBatchAsync , so the read
+ /// routes through ReadTagValuesCommand regardless of protocol
+ /// (OPC UA and MxGateway today).
///
private List BuildTestableRows()
{
@@ -626,10 +629,11 @@
var conn = _siteConnections.FirstOrDefault(c => c.Id == connId);
if (conn is null) continue;
- // OPC UA only — other protocols don't have a site-side
- // ReadTagValuesCommand handler wired up yet.
- if (!string.Equals(conn.Protocol, "OpcUa", StringComparison.OrdinalIgnoreCase))
- continue;
+ // Protocol-agnostic: ReadTagValuesCommand routes through the
+ // site-side IDataConnection.ReadBatchAsync contract, which every
+ // adapter implements (OPC UA and MxGateway today). A not-connected
+ // or unsupported connection short-circuits to a typed banner in the
+ // dialog rather than being filtered out here — mirrors IsBrowsable.
var effectivePath = _bindingOverrides.GetValueOrDefault(attr.Name)
?? GetTemplateDefault(attr.Name);
diff --git a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Deployment/InstanceConfigureAuditDrillinTests.cs b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Deployment/InstanceConfigureAuditDrillinTests.cs
index a33eb66b..069a693d 100644
--- a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Deployment/InstanceConfigureAuditDrillinTests.cs
+++ b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Deployment/InstanceConfigureAuditDrillinTests.cs
@@ -104,4 +104,63 @@ public class InstanceConfigureAuditDrillinTests : BunitContext
Assert.Contains("Recent audit activity", link.TextContent);
});
}
+
+ ///
+ /// Regression: the Test Bindings button must be enabled for an attribute
+ /// bound to an MxGateway connection. The site-side ReadTagValuesCommand
+ /// path is protocol-agnostic (routes through IDataConnection.ReadBatchAsync,
+ /// which MxGateway implements), so the UI must not gate the button on
+ /// protocol == "OpcUa". Previously BuildTestableRows filtered to OPC UA
+ /// only, leaving the button greyed for MxGateway bindings.
+ ///
+ [Theory]
+ [InlineData("MxGateway")]
+ [InlineData("OpcUa")]
+ public void TestBindingsButton_Enabled_ForReadableProtocol(string protocol)
+ {
+ var instance = new Instance("Pump-42")
+ {
+ Id = 42,
+ TemplateId = 1,
+ SiteId = 1,
+ State = ZB.MOM.WW.ScadaBridge.Commons.Types.Enums.InstanceState.NotDeployed,
+ };
+
+ _templateRepo.GetInstanceByIdAsync(42, Arg.Any()).Returns(instance);
+ _templateRepo.GetTemplateByIdAsync(1, Arg.Any())
+ .Returns(new Template("Pump") { Id = 1 });
+ _siteRepo.GetAllSitesAsync(Arg.Any())
+ .Returns(new List { new("Plant A", "plant-a") { Id = 1 } });
+ _templateRepo.GetAreasBySiteIdAsync(1, Arg.Any())
+ .Returns(new List ());
+ // One data-sourced attribute (non-empty DataSourceReference => testable row).
+ _templateRepo.GetAttributesByTemplateIdAsync(1, Arg.Any())
+ .Returns(new List
+ {
+ new("Speed") { Id = 1, DataSourceReference = "TestMachine_001.TestHistoryValue" },
+ });
+ // A connection on the attribute's chosen protocol.
+ _siteRepo.GetDataConnectionsBySiteIdAsync(1, Arg.Any())
+ .Returns(new List { new("Shared", protocol, 1) { Id = 7 } });
+ _templateRepo.GetBindingsByInstanceIdAsync(42, Arg.Any())
+ .Returns(new List
+ {
+ new("Speed") { Id = 1, InstanceId = 42, DataConnectionId = 7 },
+ });
+ _templateRepo.GetOverridesByInstanceIdAsync(42, Arg.Any())
+ .Returns(new List());
+ _templateRepo.GetAlarmsByTemplateIdAsync(1, Arg.Any())
+ .Returns(new List());
+ _templateRepo.GetAlarmOverridesByInstanceIdAsync(42, Arg.Any())
+ .Returns(new List());
+
+ var cut = Render(p => p.Add(c => c.Id, 42));
+
+ cut.WaitForAssertion(() =>
+ {
+ var testButton = cut.FindAll("button").Single(b => b.TextContent.Trim() == "Test Bindings");
+ Assert.False(testButton.HasAttribute("disabled"),
+ $"Test Bindings should be enabled for a {protocol} binding.");
+ });
+ }
}