diff --git a/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Scripts/ScriptTriggerAttribute.cs b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Scripts/ScriptTriggerAttribute.cs new file mode 100644 index 00000000..c9f371b6 --- /dev/null +++ b/src/ZB.MOM.WW.ScadaBridge.Commons/Types/Scripts/ScriptTriggerAttribute.cs @@ -0,0 +1,49 @@ +using System.Text.Json; + +namespace ZB.MOM.WW.ScadaBridge.Commons.Types.Scripts; + +/// +/// Reads the single monitored attribute of a ValueChange or Conditional script +/// trigger (Flow Debugger script entry, design 2026-09-08 §2). The config keys are the ones the +/// Central UI's ScriptTriggerConfigCodec writes (attributeName) and SiteRuntime's +/// TriggerRouting also accepts (attribute). Pure; never throws on bad JSON. +/// +public static class ScriptTriggerAttribute +{ + /// True for the trigger kinds the Flow Debugger fires by WRITING the monitored attribute. + public static bool IsWriteKind(string? triggerType) => + string.Equals(triggerType, "ValueChange", StringComparison.OrdinalIgnoreCase) + || string.Equals(triggerType, "Conditional", StringComparison.OrdinalIgnoreCase); + + /// + /// Returns the monitored attribute for a write-kind trigger with a readable configuration; + /// false (and null) for every other kind, and for a write kind whose config is missing, + /// malformed, not an object, or names no attribute. + /// + public static bool TryRead(string? triggerType, string? triggerConfigurationJson, out string? attribute) + { + attribute = null; + if (!IsWriteKind(triggerType) || string.IsNullOrWhiteSpace(triggerConfigurationJson)) + return false; + try + { + using var doc = JsonDocument.Parse(triggerConfigurationJson); + var root = doc.RootElement; + if (root.ValueKind != JsonValueKind.Object) return false; + foreach (var key in new[] { "attributeName", "attribute" }) + { + if (root.TryGetProperty(key, out var v) && v.ValueKind == JsonValueKind.String + && v.GetString() is { Length: > 0 } name) + { + attribute = name; + return true; + } + } + return false; + } + catch (JsonException) + { + return false; + } + } +} diff --git a/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Scripts/ScriptTriggerAttributeTests.cs b/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Scripts/ScriptTriggerAttributeTests.cs new file mode 100644 index 00000000..2b2d14f2 --- /dev/null +++ b/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Scripts/ScriptTriggerAttributeTests.cs @@ -0,0 +1,46 @@ +using ZB.MOM.WW.ScadaBridge.Commons.Types.Scripts; + +namespace ZB.MOM.WW.ScadaBridge.Commons.Tests.Scripts; + +/// Flow Debugger script entry (design 2026-09-08 §2): the monitored attribute of a ValueChange/Conditional trigger. +public class ScriptTriggerAttributeTests +{ + [Theory] + [InlineData("ValueChange", """{"attributeName":"Level"}""", "Level")] + [InlineData("valuechange", """{"attribute":"Level"}""", "Level")] + [InlineData("Conditional", """{"attributeName":"Level","operator":">","threshold":"5"}""", "Level")] + [InlineData("CONDITIONAL", """{"attribute":"Flag"}""", "Flag")] + public void WriteKinds_ReturnTheMonitoredAttribute(string kind, string config, string expected) + { + Assert.True(ScriptTriggerAttribute.TryRead(kind, config, out var attribute)); + Assert.Equal(expected, attribute); + Assert.True(ScriptTriggerAttribute.IsWriteKind(kind)); + } + + [Theory] + [InlineData("Call", """{"attributeName":"Level"}""")] + [InlineData("Interval", """{"intervalMs":1000}""")] + [InlineData("Expression", """{"expression":"true"}""")] + [InlineData(null, null)] + [InlineData("", "")] + public void OtherKinds_ReturnFalse_EvenWhenTheConfigNamesAnAttribute(string? kind, string? config) + { + Assert.False(ScriptTriggerAttribute.TryRead(kind, config, out var attribute)); + Assert.Null(attribute); + Assert.False(ScriptTriggerAttribute.IsWriteKind(kind)); + } + + [Theory] + [InlineData("ValueChange", null)] + [InlineData("ValueChange", "")] + [InlineData("ValueChange", "not json")] + [InlineData("ValueChange", "[1,2]")] + [InlineData("ValueChange", """{"attributeName":""}""")] + [InlineData("ValueChange", """{"other":"x"}""")] + public void WriteKind_WithoutAReadableAttribute_ReturnsFalse_ButIsStillAWriteKind(string kind, string? config) + { + Assert.False(ScriptTriggerAttribute.TryRead(kind, config, out var attribute)); + Assert.Null(attribute); + Assert.True(ScriptTriggerAttribute.IsWriteKind(kind)); + } +}