using System.CommandLine; using System.Text.Json; using ZB.MOM.WW.ScadaBridge.CLI; using ZB.MOM.WW.ScadaBridge.CLI.Commands; namespace ZB.MOM.WW.ScadaBridge.CLI.Tests.Commands; /// /// M9-T28b: template alarm add/update and template script add/update /// must expose a --trigger-kind option (advisory|strict) that writes /// "analysisKind":"Strict" into the trigger-config JSON for expression /// triggers, matching the T28a backend contract exactly. /// 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(); 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"); // ── Option surface: alarm add + update have --trigger-kind ─────────────── [Fact] public void AlarmAdd_HasTriggerKindOption() { var add = AlarmGroup().Subcommands.Single(c => c.Name == "add"); Assert.Contains("--trigger-kind", add.Options.Select(o => o.Name)); } [Fact] public void AlarmUpdate_HasTriggerKindOption() { var update = AlarmGroup().Subcommands.Single(c => c.Name == "update"); Assert.Contains("--trigger-kind", update.Options.Select(o => o.Name)); } // ── Option surface: script add + update have --trigger-kind ───────────── [Fact] public void ScriptAdd_HasTriggerKindOption() { var add = ScriptGroup().Subcommands.Single(c => c.Name == "add"); Assert.Contains("--trigger-kind", add.Options.Select(o => o.Name)); } [Fact] public void ScriptUpdate_HasTriggerKindOption() { var update = ScriptGroup().Subcommands.Single(c => c.Name == "update"); Assert.Contains("--trigger-kind", update.Options.Select(o => o.Name)); } // ── AlarmTriggerConfigJson.Build — analysisKind injection ──────────────── [Fact] public void AlarmConfigBuild_Expression_StrictKind_InjectsAnalysisKindStrict() { // --trigger-kind strict with --expression set → config carries analysisKind:"Strict" var json = AlarmTriggerConfigJson.Build( triggerType: "Expression", attribute: null, matchValue: null, notEquals: false, min: null, max: null, thresholdPerSecond: null, windowSeconds: null, direction: null, loLo: null, lo: null, hi: null, hiHi: null, expression: "Temp > 80", analysisKind: "strict"); Assert.NotNull(json); using var doc = JsonDocument.Parse(json!); Assert.Equal("Strict", doc.RootElement.GetProperty("analysisKind").GetString()); } [Fact] public void AlarmConfigBuild_Expression_AdvisoryKind_OmitsAnalysisKindKey() { // --trigger-kind advisory (default) → key omitted var json = AlarmTriggerConfigJson.Build( triggerType: "Expression", attribute: null, matchValue: null, notEquals: false, min: null, max: null, thresholdPerSecond: null, windowSeconds: null, direction: null, loLo: null, lo: null, hi: null, hiHi: null, expression: "Temp > 80", analysisKind: "advisory"); Assert.NotNull(json); using var doc = JsonDocument.Parse(json!); Assert.False(doc.RootElement.TryGetProperty("analysisKind", out _)); } [Fact] public void AlarmConfigBuild_Expression_NullKind_OmitsAnalysisKindKey() { // Omitted --trigger-kind → null → Advisory default → key omitted var json = AlarmTriggerConfigJson.Build( triggerType: "Expression", attribute: null, matchValue: null, notEquals: false, min: null, max: null, thresholdPerSecond: null, windowSeconds: null, direction: null, loLo: null, lo: null, hi: null, hiHi: null, expression: "Temp > 80", analysisKind: null); Assert.NotNull(json); using var doc = JsonDocument.Parse(json!); Assert.False(doc.RootElement.TryGetProperty("analysisKind", out _)); } [Fact] public void AlarmConfigBuild_NonExpression_StrictKind_DoesNotEmitAnalysisKind() { // analysisKind is meaningless for non-expression triggers — must be suppressed var json = AlarmTriggerConfigJson.Build( triggerType: "RangeViolation", attribute: "Temp", matchValue: null, notEquals: false, min: 0, max: 100, thresholdPerSecond: null, windowSeconds: null, direction: null, loLo: null, lo: null, hi: null, hiHi: null, expression: null, analysisKind: "strict"); Assert.NotNull(json); using var doc = JsonDocument.Parse(json!); Assert.False(doc.RootElement.TryGetProperty("analysisKind", out _)); } [Fact] public void AlarmConfigBuild_Expression_StrictKind_CaseInsensitive() { // "STRICT" (uppercase) must also produce the canonical "Strict" value var json = AlarmTriggerConfigJson.Build( triggerType: "Expression", attribute: null, matchValue: null, notEquals: false, min: null, max: null, thresholdPerSecond: null, windowSeconds: null, direction: null, loLo: null, lo: null, hi: null, hiHi: null, expression: "x > 0", analysisKind: "STRICT"); Assert.NotNull(json); using var doc = JsonDocument.Parse(json!); Assert.Equal("Strict", doc.RootElement.GetProperty("analysisKind").GetString()); } }