From 41d828e38e256d9e1390eda06e105f3e1b9bfc26 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Mon, 15 Jun 2026 13:27:26 -0400 Subject: [PATCH] =?UTF-8?q?fix(deploy):=20address=20M2.1=20review=20nits?= =?UTF-8?q?=20=E2=80=94=20comparer=20consistency=20+=20comments=20(#22)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - connection-name capable-set comparer kept as StringComparer.Ordinal: FlatteningService and SemanticValidator use all-ordinal name-keyed dictionaries throughout; OrdinalIgnoreCase would be inconsistent with the rest of the binding-resolution path — added comment documenting this - IsAlarmCapable protocol-match confirmed consistent with DataConnectionFactory (both OrdinalIgnoreCase); added case-insensitive InlineData variants (OPCUA, opcua, mxgateway, MXGATEWAY) to lock the contract - clarified FlatteningPipeline comment: "filters connections by alarm-capable protocol, then collects their names" (was "maps from the protocol string") - added DataConnectionLayer/DataConnectionFactory.cs path reference to AlarmCapableProtocols sync-risk comment --- .../Interfaces/Protocol/AlarmCapableProtocols.cs | 9 ++++++--- .../FlatteningPipeline.cs | 10 ++++++++-- .../FlatteningPipelineNativeAlarmCapabilityTests.cs | 6 ++++++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Protocol/AlarmCapableProtocols.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Protocol/AlarmCapableProtocols.cs index e7bfc151..f837f6d3 100644 --- a/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Protocol/AlarmCapableProtocols.cs +++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Protocol/AlarmCapableProtocols.cs @@ -6,8 +6,9 @@ namespace ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Protocol; /// mirror native alarms). /// /// The set MUST stay in sync with the protocols registered against an -/// alarm-subscribable adapter in the DCL DataConnectionFactory: today the -/// "OpcUa" adapter (OpcUaDataConnection) and the "MxGateway" adapter +/// alarm-subscribable adapter in +/// DataConnectionLayer/DataConnectionFactory.cs: today the "OpcUa" adapter +/// (OpcUaDataConnection) and the "MxGateway" adapter /// (MxGatewayDataConnection) both implement /// . The runtime decision is made in /// DataConnectionActor via _adapter is IAlarmSubscribableConnection; @@ -21,7 +22,9 @@ public static class AlarmCapableProtocols /// /// Determines whether a data connection's protocol string resolves to an /// alarm-capable adapter (one implementing ). - /// Case-insensitive; null/blank is not alarm-capable. + /// Case-insensitive to match DataConnectionFactory's own + /// OrdinalIgnoreCase protocol-key lookup; null/blank is not + /// alarm-capable. /// /// The data connection protocol string (e.g. "OpcUa"). /// true when the protocol's adapter can subscribe native alarms; otherwise false. diff --git a/src/ZB.MOM.WW.ScadaBridge.DeploymentManager/FlatteningPipeline.cs b/src/ZB.MOM.WW.ScadaBridge.DeploymentManager/FlatteningPipeline.cs index 39ccbf68..df78fe6c 100644 --- a/src/ZB.MOM.WW.ScadaBridge.DeploymentManager/FlatteningPipeline.cs +++ b/src/ZB.MOM.WW.ScadaBridge.DeploymentManager/FlatteningPipeline.cs @@ -114,8 +114,14 @@ public class FlatteningPipeline : IFlatteningPipeline // Compute the alarm-capable connection-name set so the semantic validator // can gate native-alarm-source bindings. "Alarm-capable" matches the DCL - // runtime decision (DataConnectionActor: _adapter is IAlarmSubscribableConnection), - // mapped from the protocol string via the shared AlarmCapableProtocols helper. + // runtime decision (DataConnectionActor: _adapter is IAlarmSubscribableConnection); + // here we filter connections by alarm-capable protocol, then collect their names. + // + // StringComparer.Ordinal is intentional: connection names are stored and + // matched as authored throughout the pipeline (all other name-keyed + // dictionaries in FlatteningService and SemanticValidator use the same + // case-sensitive semantics). OrdinalIgnoreCase would be inconsistent with + // the rest of the binding-resolution path. var alarmCapableConnectionNames = dataConnections.Values .Where(c => AlarmCapableProtocols.IsAlarmCapable(c.Protocol)) .Select(c => c.Name) diff --git a/tests/ZB.MOM.WW.ScadaBridge.DeploymentManager.Tests/FlatteningPipelineNativeAlarmCapabilityTests.cs b/tests/ZB.MOM.WW.ScadaBridge.DeploymentManager.Tests/FlatteningPipelineNativeAlarmCapabilityTests.cs index b0d6a4de..a49f35c3 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.DeploymentManager.Tests/FlatteningPipelineNativeAlarmCapabilityTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.DeploymentManager.Tests/FlatteningPipelineNativeAlarmCapabilityTests.cs @@ -83,6 +83,12 @@ public class FlatteningPipelineNativeAlarmCapabilityTests [Theory] [InlineData("OpcUa")] [InlineData("MxGateway")] + // Case variants: IsAlarmCapable uses OrdinalIgnoreCase, matching DataConnectionFactory's + // own OrdinalIgnoreCase protocol-key lookup; lock the contract with non-canonical casing. + [InlineData("OPCUA")] + [InlineData("opcua")] + [InlineData("mxgateway")] + [InlineData("MXGATEWAY")] public async Task FlattenAndValidate_NativeAlarmSourceOnAlarmCapableConnection_NoCapabilityError(string protocol) { Arrange(connectionName: "Boiler", connectionProtocol: protocol, boundConnectionName: "Boiler");