fix(m9/T28b): forward --trigger-kind on alarm-update/script-add/script-update; fix default-selector test
Three dead-code bugs: --trigger-kind was registered but never read or forwarded on the
alarm-update, script-add, and script-update paths. Introduced TriggerConfigJson.InjectAnalysisKind
helper that rewrites any raw --trigger-config JSON blob, writing "analysisKind":"Strict" when
the flag is strict (case-insensitive) and stripping the key for any other value. Wired the
helper into all three handlers alongside the existing alarm-add path (which already used
AlarmTriggerConfigJson.Build). Added 6 unit tests for the new helper in TemplateTriggerKindTests.
Also fixed a false-positive bUnit test (AlarmTriggerEditor_Expression_NoAnalysisKindInConfig_
SelectorDefaultsAdvisory) that passed because "Advisory" appeared anywhere in the HTML; now
asserts select.GetAttribute("value") == "Advisory". Added the missing equivalent test for
ScriptTriggerEditor (ScriptTriggerEditor_Expression_NoAnalysisKindInConfig_SelectorDefaultsAdvisory).
This commit is contained in:
@@ -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)));
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.CLI;
|
||||
|
||||
/// <summary>
|
||||
/// Shared trigger-config JSON helpers for CLI paths that receive a raw
|
||||
/// <c>--trigger-config</c> JSON blob and need to inject or strip the
|
||||
/// <c>"analysisKind"</c> key based on <c>--trigger-kind</c>.
|
||||
/// Used by <c>alarm update</c>, <c>script add</c>, and <c>script update</c>
|
||||
/// (alarm add uses <see cref="AlarmTriggerConfigJson.Build"/> instead).
|
||||
/// </summary>
|
||||
internal static class TriggerConfigJson
|
||||
{
|
||||
/// <summary>
|
||||
/// Copies every property from <paramref name="json"/> into a new object,
|
||||
/// adding or replacing <c>"analysisKind"</c> when
|
||||
/// <paramref name="analysisKind"/> is <c>"strict"</c> (case-insensitive),
|
||||
/// or omitting it for any other value (including <see langword="null"/>).
|
||||
/// Returns <see langword="null"/> when <paramref name="json"/> is
|
||||
/// <see langword="null"/> or empty, preserving the caller's null-means-no-config
|
||||
/// semantics.
|
||||
/// </summary>
|
||||
/// <param name="json">Raw trigger-config JSON object, or null.</param>
|
||||
/// <param name="analysisKind">
|
||||
/// Value of <c>--trigger-kind</c>: <c>"strict"</c> → write
|
||||
/// <c>"analysisKind":"Strict"</c>; anything else → omit the key.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The rewritten JSON string, or <see langword="null"/> when
|
||||
/// <paramref name="json"/> is null/empty.
|
||||
/// </returns>
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -13,18 +13,78 @@ namespace ZB.MOM.WW.ScadaBridge.CLI.Tests.Commands;
|
||||
/// </summary>
|
||||
public class TemplateTriggerKindTests
|
||||
{
|
||||
private static readonly Option<string> Url = new("--url") { Recursive = true };
|
||||
private static readonly Option<string> Username = new("--username") { Recursive = true };
|
||||
private static readonly Option<string> Password = new("--password") { Recursive = true };
|
||||
private static readonly Option<string> 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<string> Url = new("--url") { Recursive = true };
|
||||
private static readonly Option<string> Username = new("--username") { Recursive = true };
|
||||
private static readonly Option<string> Password = new("--password") { Recursive = true };
|
||||
private static readonly Option<string> 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");
|
||||
}
|
||||
|
||||
@@ -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<ScriptTriggerEditor>(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
|
||||
|
||||
Reference in New Issue
Block a user