diff --git a/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/TemplateCommands.cs b/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/TemplateCommands.cs
index f67ce854..5fd6b3a3 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/TemplateCommands.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.CLI/Commands/TemplateCommands.cs
@@ -421,6 +421,9 @@ public static class TemplateCommands
updateCmd.Add(updateTriggerKindOption);
updateCmd.SetAction(async (ParseResult result) =>
{
+ var triggerConfig = TriggerConfigJson.InjectAnalysisKind(
+ result.GetValue(updateTriggerConfigOption),
+ result.GetValue(updateTriggerKindOption));
return await CommandHelpers.ExecuteCommandAsync(
result, urlOption, formatOption, usernameOption, passwordOption,
new UpdateTemplateAlarmCommand(
@@ -429,7 +432,7 @@ public static class TemplateCommands
result.GetValue(updateTriggerTypeOption)!,
result.GetValue(updatePriorityOption)!,
result.GetValue(updateDescOption),
- result.GetValue(updateTriggerConfigOption),
+ triggerConfig,
result.GetValue(updateLockedOption)));
});
group.Add(updateCmd);
@@ -549,6 +552,9 @@ public static class TemplateCommands
addCmd.Add(scriptTriggerKindOption);
addCmd.SetAction(async (ParseResult result) =>
{
+ var triggerConfig = TriggerConfigJson.InjectAnalysisKind(
+ result.GetValue(triggerConfigOption),
+ result.GetValue(scriptTriggerKindOption));
return await CommandHelpers.ExecuteCommandAsync(
result, urlOption, formatOption, usernameOption, passwordOption,
new AddTemplateScriptCommand(
@@ -556,7 +562,7 @@ public static class TemplateCommands
result.GetValue(nameOption)!,
result.GetValue(codeOption)!,
result.GetValue(triggerTypeOption)!,
- result.GetValue(triggerConfigOption),
+ triggerConfig,
result.GetValue(lockedOption),
result.GetValue(paramsOption),
result.GetValue(returnOption)));
@@ -591,6 +597,9 @@ public static class TemplateCommands
updateCmd.Add(updateScriptTriggerKindOption);
updateCmd.SetAction(async (ParseResult result) =>
{
+ var triggerConfig = TriggerConfigJson.InjectAnalysisKind(
+ result.GetValue(updateTriggerConfigOption),
+ result.GetValue(updateScriptTriggerKindOption));
return await CommandHelpers.ExecuteCommandAsync(
result, urlOption, formatOption, usernameOption, passwordOption,
new UpdateTemplateScriptCommand(
@@ -598,7 +607,7 @@ public static class TemplateCommands
result.GetValue(updateNameOption)!,
result.GetValue(updateCodeOption)!,
result.GetValue(updateTriggerTypeOption)!,
- result.GetValue(updateTriggerConfigOption),
+ triggerConfig,
result.GetValue(updateLockedOption),
result.GetValue(updateParamsOption),
result.GetValue(updateReturnOption)));
diff --git a/src/ZB.MOM.WW.ScadaBridge.CLI/TriggerConfigJson.cs b/src/ZB.MOM.WW.ScadaBridge.CLI/TriggerConfigJson.cs
new file mode 100644
index 00000000..f1be4900
--- /dev/null
+++ b/src/ZB.MOM.WW.ScadaBridge.CLI/TriggerConfigJson.cs
@@ -0,0 +1,61 @@
+using System.Text;
+using System.Text.Json;
+
+namespace ZB.MOM.WW.ScadaBridge.CLI;
+
+///
+/// Shared trigger-config JSON helpers for CLI paths that receive a raw
+/// --trigger-config JSON blob and need to inject or strip the
+/// "analysisKind" key based on --trigger-kind.
+/// Used by alarm update, script add, and script update
+/// (alarm add uses instead).
+///
+internal static class TriggerConfigJson
+{
+ ///
+ /// Copies every property from into a new object,
+ /// adding or replacing "analysisKind" when
+ /// is "strict" (case-insensitive),
+ /// or omitting it for any other value (including ).
+ /// Returns when is
+ /// or empty, preserving the caller's null-means-no-config
+ /// semantics.
+ ///
+ /// Raw trigger-config JSON object, or null.
+ ///
+ /// Value of --trigger-kind: "strict" → write
+ /// "analysisKind":"Strict"; anything else → omit the key.
+ ///
+ ///
+ /// The rewritten JSON string, or when
+ /// is null/empty.
+ ///
+ internal static string? InjectAnalysisKind(string? json, string? analysisKind)
+ {
+ if (string.IsNullOrWhiteSpace(json))
+ return null;
+
+ var isStrict = string.Equals(analysisKind?.Trim(), "Strict", StringComparison.OrdinalIgnoreCase);
+
+ using var inputDoc = JsonDocument.Parse(json);
+ using var stream = new MemoryStream();
+ using (var writer = new Utf8JsonWriter(stream))
+ {
+ writer.WriteStartObject();
+
+ // Copy all existing properties except "analysisKind" (we'll re-add it below if needed)
+ foreach (var prop in inputDoc.RootElement.EnumerateObject())
+ {
+ if (!prop.NameEquals("analysisKind"))
+ prop.WriteTo(writer);
+ }
+
+ if (isStrict)
+ writer.WriteString("analysisKind", "Strict");
+
+ writer.WriteEndObject();
+ }
+
+ return Encoding.UTF8.GetString(stream.ToArray());
+ }
+}
diff --git a/tests/ZB.MOM.WW.ScadaBridge.CLI.Tests/Commands/TemplateTriggerKindTests.cs b/tests/ZB.MOM.WW.ScadaBridge.CLI.Tests/Commands/TemplateTriggerKindTests.cs
index 2b831e78..a1a6a39a 100644
--- a/tests/ZB.MOM.WW.ScadaBridge.CLI.Tests/Commands/TemplateTriggerKindTests.cs
+++ b/tests/ZB.MOM.WW.ScadaBridge.CLI.Tests/Commands/TemplateTriggerKindTests.cs
@@ -13,18 +13,78 @@ namespace ZB.MOM.WW.ScadaBridge.CLI.Tests.Commands;
///
public class TemplateTriggerKindTests
{
- private static readonly Option Url = new("--url") { Recursive = true };
- private static readonly Option Username = new("--username") { Recursive = true };
- private static readonly Option Password = new("--password") { Recursive = true };
- private static readonly Option Format = CliOptions.CreateFormatOption();
+ // ── TriggerConfigJson.InjectAnalysisKind — the shared helper used by
+ // alarm-update / script-add / script-update ──────────────────────────────
- private static Command AlarmGroup()
- => TemplateCommands.Build(Url, Format, Username, Password)
- .Subcommands.Single(c => c.Name == "alarm");
+ // Inject strict into an Expression config that has no prior analysisKind
+ [Fact]
+ public void InjectAnalysisKind_Strict_AddsKey()
+ {
+ var json = "{\"expression\":\"x > 0\"}";
+ var result = TriggerConfigJson.InjectAnalysisKind(json, "strict");
- private static Command ScriptGroup()
- => TemplateCommands.Build(Url, Format, Username, Password)
- .Subcommands.Single(c => c.Name == "script");
+ Assert.NotNull(result);
+ using var doc = JsonDocument.Parse(result!);
+ Assert.Equal("Strict", doc.RootElement.GetProperty("analysisKind").GetString());
+ }
+
+ // Inject advisory (null) into config that had Strict → key removed
+ [Fact]
+ public void InjectAnalysisKind_Advisory_RemovesKey()
+ {
+ var json = "{\"expression\":\"x > 0\",\"analysisKind\":\"Strict\"}";
+ var result = TriggerConfigJson.InjectAnalysisKind(json, null);
+
+ Assert.NotNull(result);
+ using var doc = JsonDocument.Parse(result!);
+ Assert.False(doc.RootElement.TryGetProperty("analysisKind", out _));
+ }
+
+ // Inject strict into config that already had Strict → still Strict (idempotent)
+ [Fact]
+ public void InjectAnalysisKind_Strict_Idempotent()
+ {
+ var json = "{\"expression\":\"x > 0\",\"analysisKind\":\"Strict\"}";
+ var result = TriggerConfigJson.InjectAnalysisKind(json, "strict");
+
+ Assert.NotNull(result);
+ using var doc = JsonDocument.Parse(result!);
+ Assert.Equal("Strict", doc.RootElement.GetProperty("analysisKind").GetString());
+ }
+
+ // Null config → returns null (no config to inject into)
+ [Fact]
+ public void InjectAnalysisKind_NullConfig_ReturnsNull()
+ {
+ var result = TriggerConfigJson.InjectAnalysisKind(null, "strict");
+ Assert.Null(result);
+ }
+
+ // Case-insensitive: "STRICT" → canonical "Strict"
+ [Fact]
+ public void InjectAnalysisKind_Strict_CaseInsensitive()
+ {
+ var json = "{\"expression\":\"x > 0\"}";
+ var result = TriggerConfigJson.InjectAnalysisKind(json, "STRICT");
+
+ Assert.NotNull(result);
+ using var doc = JsonDocument.Parse(result!);
+ Assert.Equal("Strict", doc.RootElement.GetProperty("analysisKind").GetString());
+ }
+
+ // Other keys preserved when injecting
+ [Fact]
+ public void InjectAnalysisKind_OtherKeysPreserved()
+ {
+ var json = "{\"expression\":\"x > 0\",\"mode\":\"OnTrue\"}";
+ var result = TriggerConfigJson.InjectAnalysisKind(json, "strict");
+
+ Assert.NotNull(result);
+ using var doc = JsonDocument.Parse(result!);
+ Assert.Equal("x > 0", doc.RootElement.GetProperty("expression").GetString());
+ Assert.Equal("OnTrue", doc.RootElement.GetProperty("mode").GetString());
+ Assert.Equal("Strict", doc.RootElement.GetProperty("analysisKind").GetString());
+ }
// ── Option surface: alarm add + update have --trigger-kind ───────────────
@@ -154,4 +214,17 @@ public class TemplateTriggerKindTests
using var doc = JsonDocument.Parse(json!);
Assert.Equal("Strict", doc.RootElement.GetProperty("analysisKind").GetString());
}
+
+ private static readonly Option Url = new("--url") { Recursive = true };
+ private static readonly Option Username = new("--username") { Recursive = true };
+ private static readonly Option Password = new("--password") { Recursive = true };
+ private static readonly Option Format = CliOptions.CreateFormatOption();
+
+ private static Command AlarmGroup()
+ => TemplateCommands.Build(Url, Format, Username, Password)
+ .Subcommands.Single(c => c.Name == "alarm");
+
+ private static Command ScriptGroup()
+ => TemplateCommands.Build(Url, Format, Username, Password)
+ .Subcommands.Single(c => c.Name == "script");
}
diff --git a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Shared/TriggerAnalysisKindTests.cs b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Shared/TriggerAnalysisKindTests.cs
index b04e40c6..eab61f80 100644
--- a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Shared/TriggerAnalysisKindTests.cs
+++ b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Shared/TriggerAnalysisKindTests.cs
@@ -240,8 +240,21 @@ public class AlarmTriggerEditorAnalysisKindTests : BunitContext
.Add(p => p.Value, @"{""expression"":""x > 0""}"));
var select = cut.Find("#alarm-trigger-kind");
- // The selected option must be Advisory (the default)
- Assert.Contains("Advisory", select.InnerHtml);
+ // The bound value must be "Advisory" — not just present in the HTML
+ Assert.Equal("Advisory", select.GetAttribute("value"));
+ }
+
+ // Selector defaults to Advisory when config has no analysisKind (ScriptTriggerEditor)
+ [Fact]
+ public void ScriptTriggerEditor_Expression_NoAnalysisKindInConfig_SelectorDefaultsAdvisory()
+ {
+ var cut = Render(ps => ps
+ .Add(p => p.TriggerType, "Expression")
+ .Add(p => p.TriggerConfig, @"{""expression"":""x > 0"",""mode"":""OnTrue""}"));
+
+ var select = cut.Find("#script-trigger-kind");
+ // The bound value must be "Advisory" — not just present in the HTML
+ Assert.Equal("Advisory", select.GetAttribute("value"));
}
// Choosing Strict writes analysisKind:"Strict" into emitted config