fix(deploy): address M2.1 review nits — comparer consistency + comments (#22)

- 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
This commit is contained in:
Joseph Doherty
2026-06-15 13:27:26 -04:00
parent d6909207a8
commit 41d828e38e
3 changed files with 20 additions and 5 deletions
@@ -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 <c>DataConnectionFactory</c>: today the
/// "OpcUa" adapter (<c>OpcUaDataConnection</c>) and the "MxGateway" adapter
/// alarm-subscribable adapter in
/// <c>DataConnectionLayer/DataConnectionFactory.cs</c>: today the "OpcUa" adapter
/// (<c>OpcUaDataConnection</c>) and the "MxGateway" adapter
/// (<c>MxGatewayDataConnection</c>) both implement
/// <see cref="IAlarmSubscribableConnection"/>. The runtime decision is made in
/// <c>DataConnectionActor</c> via <c>_adapter is IAlarmSubscribableConnection</c>;
@@ -21,7 +22,9 @@ public static class AlarmCapableProtocols
/// <summary>
/// Determines whether a data connection's protocol string resolves to an
/// alarm-capable adapter (one implementing <see cref="IAlarmSubscribableConnection"/>).
/// Case-insensitive; <c>null</c>/blank is not alarm-capable.
/// Case-insensitive to match <c>DataConnectionFactory</c>'s own
/// <c>OrdinalIgnoreCase</c> protocol-key lookup; <c>null</c>/blank is not
/// alarm-capable.
/// </summary>
/// <param name="protocol">The data connection protocol string (e.g. "OpcUa").</param>
/// <returns><c>true</c> when the protocol's adapter can subscribe native alarms; otherwise <c>false</c>.</returns>
@@ -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)
@@ -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");