using System.CommandLine; using ScadaLink.CLI.Commands; namespace ScadaLink.CLI.Tests; /// /// Regression tests for CLI-014. The Update*Command records in Commons carry /// non-nullable "core" fields (e.g. string Name, string Protocol, /// string Script) — an update is a whole-entity replace, not a sparse /// patch. The CLI must therefore mark those core flags as Required: making them /// optional would let an omitted flag send null/empty and silently blank the /// field server-side. These tests pin that contract so the documented surface and the /// implemented surface stay aligned. /// public class UpdateCommandContractTests { 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 UpdateCommand(Command group, params string[] path) { var current = group; foreach (var segment in path) current = current.Subcommands.Single(c => c.Name == segment); return current; } private static void AssertRequired(Command command, params string[] requiredOptionNames) { foreach (var name in requiredOptionNames) { var option = command.Options.SingleOrDefault(o => o.Name == name); Assert.True(option != null, $"'{command.Name}' is missing expected option '{name}'."); Assert.True(option!.Required, $"'{command.Name}' option '{name}' must be Required (whole-replace contract)."); } } [Fact] public void TemplateUpdate_CoreFieldsRequired() => AssertRequired(UpdateCommand(TemplateCommands.Build(Url, Format, Username, Password), "update"), "--name"); [Fact] public void TemplateAttributeUpdate_CoreFieldsRequired() => AssertRequired(UpdateCommand(TemplateCommands.Build(Url, Format, Username, Password), "attribute", "update"), "--name", "--data-type"); [Fact] public void TemplateAlarmUpdate_CoreFieldsRequired() => AssertRequired(UpdateCommand(TemplateCommands.Build(Url, Format, Username, Password), "alarm", "update"), "--name", "--trigger-type", "--priority"); [Fact] public void TemplateScriptUpdate_CoreFieldsRequired() => AssertRequired(UpdateCommand(TemplateCommands.Build(Url, Format, Username, Password), "script", "update"), "--name", "--code"); [Fact] public void SiteUpdate_CoreFieldsRequired() => AssertRequired(UpdateCommand(SiteCommands.Build(Url, Format, Username, Password), "update"), "--name"); [Fact] public void DataConnectionUpdate_CoreFieldsRequired() => AssertRequired(UpdateCommand(DataConnectionCommands.Build(Url, Format, Username, Password), "update"), "--name", "--protocol"); [Fact] public void ExternalSystemUpdate_CoreFieldsRequired() => AssertRequired(UpdateCommand(ExternalSystemCommands.Build(Url, Format, Username, Password), "update"), "--name", "--endpoint-url", "--auth-type"); [Fact] public void NotificationUpdate_CoreFieldsRequired() => AssertRequired(UpdateCommand(NotificationCommands.Build(Url, Format, Username, Password), "update"), "--name", "--emails"); [Fact] public void ApiMethodUpdate_CoreFieldsRequired() => AssertRequired(UpdateCommand(ApiMethodCommands.Build(Url, Format, Username, Password), "update"), "--script"); [Fact] public void ExternalSystemMethodUpdate_IsGenuinelySparse_CoreFieldsOptional() { // UpdateExternalSystemMethodCommand is the one update record whose fields are // genuinely all-nullable, so its flags are correctly optional. Pin that too so // it is not mistakenly forced to Required. var update = UpdateCommand(ExternalSystemCommands.Build(Url, Format, Username, Password), "method", "update"); foreach (var name in new[] { "--name", "--http-method", "--path" }) { var option = update.Options.SingleOrDefault(o => o.Name == name); Assert.True(option != null, $"method update is missing option '{name}'."); Assert.False(option!.Required, $"method update option '{name}' should be optional (sparse-patch record)."); } } }