513fa1aa16
Comment/XML-doc changes only: - add summaries to ~10k test methods, fixtures, fakes and helpers (+ <param> on theories, <returns> on async Task methods, <inheritdoc/> on interface implementations the checker can resolve) - strip ~2.6k project-tracking references (WP*, P*-*, row-NN, Task N, #NN, Component-0NN) from comments, keeping the explanatory text; reword false positives in comments only - real docs on fakes implementing BCL/library interfaces (IDisposable, ILogger, IOptionsMonitor, IAsyncLifetime) - reword the last 5 src/ false positives (CWE-1236, failures-1, year-0001, M365) Only non-comment edits: four one-line nested classes split across lines so members can carry docs. Build 0 errors / 0 warnings.
490 lines
23 KiB
C#
490 lines
23 KiB
C#
using System.CommandLine;
|
|
using ZB.MOM.WW.ScadaBridge.CLI.Commands;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.CLI.Tests;
|
|
|
|
/// <summary>
|
|
/// Regression tests for the command-tree wiring, which was previously untested. These tests
|
|
/// build every command group and assert the tree is well-formed (every leaf has an
|
|
/// action, no group is empty), and that every management command record the CLI sends
|
|
/// resolves via <see cref="ManagementCommandRegistry"/> (so command-name derivation
|
|
/// never throws at runtime).
|
|
/// </summary>
|
|
public class CommandTreeTests
|
|
{
|
|
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();
|
|
|
|
// Exercise the production root: a separately maintained group list can drift.
|
|
private static IEnumerable<Command> AllCommandGroups() => CliApplication.BuildRoot().Subcommands;
|
|
|
|
/// <summary>Verifies that the registered operational command groups include the secured-writes and connection tools.</summary>
|
|
[Fact]
|
|
public void RegisteredOperationalGroups_IncludeSecuredWritesAndConnectionTools()
|
|
{
|
|
var groups = AllCommandGroups().ToList();
|
|
var secured = groups.Single(g => g.Name == "secured-write");
|
|
Assert.Equal(new[] { "approve", "list", "reject", "submit" }, secured.Subcommands.Select(c => c.Name).Order());
|
|
var connection = groups.Single(g => g.Name == "data-connection");
|
|
foreach (var name in new[] { "browse", "search", "verify-endpoint", "certs" })
|
|
Assert.Contains(connection.Subcommands, c => c.Name == name);
|
|
var certs = connection.Subcommands.Single(c => c.Name == "certs");
|
|
Assert.Equal(new[] { "list", "remove", "trust" }, certs.Subcommands.Select(c => c.Name).Order());
|
|
}
|
|
|
|
private static IEnumerable<Command> LeafCommands(Command command)
|
|
{
|
|
if (command.Subcommands.Count == 0)
|
|
{
|
|
yield return command;
|
|
yield break;
|
|
}
|
|
|
|
foreach (var sub in command.Subcommands)
|
|
foreach (var leaf in LeafCommands(sub))
|
|
yield return leaf;
|
|
}
|
|
|
|
/// <summary>Verifies that every command group builds without throwing.</summary>
|
|
[Fact]
|
|
public void AllCommandGroups_Build_WithoutThrowing()
|
|
{
|
|
var groups = AllCommandGroups().ToList();
|
|
Assert.Equal(20, groups.Count);
|
|
Assert.All(groups, g => Assert.False(string.IsNullOrWhiteSpace(g.Name)));
|
|
}
|
|
|
|
/// <summary>Verifies that the set of command groups contains the audit and bundle groups.</summary>
|
|
[Fact]
|
|
public void AllCommandGroups_Contains_AuditAndBundle()
|
|
{
|
|
// Explicit group-presence assertion so the harness does not
|
|
// silently drift back to excluding new groups. Use names because that
|
|
// is what users actually type at the prompt.
|
|
var groupNames = AllCommandGroups().Select(g => g.Name).ToHashSet();
|
|
Assert.Contains("audit", groupNames);
|
|
Assert.Contains("bundle", groupNames);
|
|
}
|
|
|
|
/// <summary>Verifies that the audit command group exposes the query, export and verify-chain subcommands.</summary>
|
|
[Fact]
|
|
public void AuditCommandGroup_HasQueryExportAndVerifyChain()
|
|
{
|
|
// Pin the audit sub-command surface so a rename / accidental
|
|
// removal of one of these is caught.
|
|
var audit = AuditCommands.Build(Url, Format, Username, Password);
|
|
var subNames = audit.Subcommands.Select(c => c.Name).ToHashSet();
|
|
Assert.Contains("query", subNames);
|
|
Assert.Contains("export", subNames);
|
|
Assert.Contains("verify-chain", subNames);
|
|
}
|
|
|
|
/// <summary>Verifies that the bundle command group exposes the export, preview and import subcommands.</summary>
|
|
[Fact]
|
|
public void BundleCommandGroup_HasExportPreviewAndImport()
|
|
{
|
|
// Pin the bundle sub-command surface.
|
|
var bundle = BundleCommands.Build(Url, Format, Username, Password);
|
|
var subNames = bundle.Subcommands.Select(c => c.Name).ToHashSet();
|
|
Assert.Contains("export", subNames);
|
|
Assert.Contains("preview", subNames);
|
|
Assert.Contains("import", subNames);
|
|
}
|
|
|
|
/// <summary>Verifies that every leaf command in the tree has an action attached.</summary>
|
|
[Fact]
|
|
public void EveryLeafCommand_HasAnAction()
|
|
{
|
|
// A leaf command with no action is dead wiring — invoking it would do nothing.
|
|
var leaves = AllCommandGroups().SelectMany(LeafCommands).ToList();
|
|
|
|
Assert.NotEmpty(leaves);
|
|
Assert.All(leaves, leaf =>
|
|
Assert.True(leaf.Action != null, $"Leaf command '{leaf.Name}' has no action."));
|
|
}
|
|
|
|
/// <summary>Verifies that template composition delete is keyed by the composition id alone.</summary>
|
|
[Fact]
|
|
public void TemplateCompositionDelete_IsKeyedByIdOnly()
|
|
{
|
|
// The in-repo README documented `template composition delete` with
|
|
// --template-id / --instance-name, but the implementation keys deletion by the
|
|
// composition's own integer ID via a single --id option. Pin the real surface.
|
|
var template = TemplateCommands.Build(Url, Format, Username, Password);
|
|
var composition = template.Subcommands.Single(c => c.Name == "composition");
|
|
var delete = composition.Subcommands.Single(c => c.Name == "delete");
|
|
|
|
var optionNames = delete.Options.Select(o => o.Name).ToList();
|
|
Assert.Contains("--id", optionNames);
|
|
Assert.DoesNotContain("--template-id", optionNames);
|
|
Assert.DoesNotContain("--instance-name", optionNames);
|
|
}
|
|
|
|
/// <summary>Verifies that the template native alarm source group has add, list and remove subcommands.</summary>
|
|
[Fact]
|
|
public void TemplateNativeAlarmSource_HasAddListRemove()
|
|
{
|
|
var template = TemplateCommands.Build(Url, Format, Username, Password);
|
|
var group = template.Subcommands.Single(c => c.Name == "native-alarm-source");
|
|
var subNames = group.Subcommands.Select(c => c.Name).ToHashSet();
|
|
Assert.Contains("add", subNames);
|
|
Assert.Contains("list", subNames);
|
|
Assert.Contains("remove", subNames);
|
|
}
|
|
|
|
/// <summary>Verifies that the deploy group has a site verb that requires a site id.</summary>
|
|
[Fact]
|
|
public void Deploy_HasSiteVerb_WithRequiredSiteId()
|
|
{
|
|
// Bulk site deploy. Named `deploy site` to sit alongside
|
|
// `deploy instance` / `deploy artifacts` — the group's convention is that
|
|
// the verb names the SCOPE of the deploy.
|
|
var deploy = DeployCommands.Build(Url, Format, Username, Password);
|
|
var site = deploy.Subcommands.Single(c => c.Name == "site");
|
|
|
|
// --site-id is REQUIRED here, unlike `deploy artifacts` where omitting it
|
|
// means fleet-wide. A bulk instance deploy must never be accidentally
|
|
// fleet-wide.
|
|
Assert.NotEmpty(site.Parse([]).Errors);
|
|
Assert.Empty(site.Parse(["--site-id", "3"]).Errors);
|
|
}
|
|
|
|
/// <summary>Verifies that deploy site has a force flag that defaults to the non-destructive behaviour.</summary>
|
|
[Fact]
|
|
public void DeploySite_HasForceFlag_DefaultingToTheNonDestructiveBehaviour()
|
|
{
|
|
// Bulk deploy skips instances whose config is unchanged, because a
|
|
// redeploy resets their static overrides and native alarm state at the
|
|
// site. --force is the operator escape hatch (restored volume, rebuilt
|
|
// node) and must be a FLAG — absent means skip-unchanged, which is the
|
|
// safe default a scripted `deploy site` keeps getting.
|
|
var deploy = DeployCommands.Build(Url, Format, Username, Password);
|
|
var site = deploy.Subcommands.Single(c => c.Name == "site");
|
|
var forceOption = (Option<bool>)site.Options.Single(o => o.Name == "--force");
|
|
|
|
var withoutForce = site.Parse(["--site-id", "3"]);
|
|
Assert.Empty(withoutForce.Errors);
|
|
Assert.False(withoutForce.GetValue(forceOption));
|
|
|
|
// A bare --force (no value) must parse: it is a flag, not a value option.
|
|
var withForce = site.Parse(["--site-id", "3", "--force"]);
|
|
Assert.Empty(withForce.Errors);
|
|
Assert.True(withForce.GetValue(forceOption));
|
|
}
|
|
|
|
/// <summary>Verifies that the instance native alarm source group has set and clear subcommands.</summary>
|
|
[Fact]
|
|
public void InstanceNativeAlarmSource_HasSetAndClear()
|
|
{
|
|
var instance = InstanceCommands.Build(Url, Format, Username, Password);
|
|
var group = instance.Subcommands.Single(c => c.Name == "native-alarm-source");
|
|
var subNames = group.Subcommands.Select(c => c.Name).ToHashSet();
|
|
Assert.Contains("set", subNames);
|
|
Assert.Contains("clear", subNames);
|
|
}
|
|
|
|
/// <summary>Verifies that the template folder group covers every folder management command.</summary>
|
|
[Fact]
|
|
public void TemplateFolder_HasFullManagementCommandParity()
|
|
{
|
|
// CLI/management parity follow-up (docs/plans/2026-05-11-templates-folder-hierarchy.md):
|
|
// the folder management commands shipped without any CLI surface. Pin the
|
|
// 1:1 verb set so a verb cannot silently disappear again.
|
|
var template = TemplateCommands.Build(Url, Format, Username, Password);
|
|
var folder = template.Subcommands.Single(c => c.Name == "folder");
|
|
var subNames = folder.Subcommands.Select(c => c.Name).ToHashSet();
|
|
Assert.Contains("list", subNames);
|
|
Assert.Contains("create", subNames);
|
|
Assert.Contains("rename", subNames);
|
|
Assert.Contains("move", subNames);
|
|
Assert.Contains("reorder", subNames);
|
|
Assert.Contains("delete", subNames);
|
|
}
|
|
|
|
/// <summary>Verifies that the parent id is optional on template folder create so the root folder is reachable.</summary>
|
|
[Fact]
|
|
public void TemplateFolderCreate_ParentIdIsOptional_SoRootIsReachable()
|
|
{
|
|
// Omitting --parent-id must parse cleanly — that is how a folder is created
|
|
// at the tree root (null ParentFolderId on CreateTemplateFolderCommand).
|
|
var template = TemplateCommands.Build(Url, Format, Username, Password);
|
|
var folder = template.Subcommands.Single(c => c.Name == "folder");
|
|
var create = folder.Subcommands.Single(c => c.Name == "create");
|
|
var parentOption = create.Options.Single(o => o.Name == "--parent-id");
|
|
|
|
var rootParse = create.Parse(new[] { "--name", "Dev" });
|
|
Assert.Empty(rootParse.Errors);
|
|
Assert.Null(rootParse.GetValue((Option<int?>)parentOption));
|
|
|
|
var nestedParse = create.Parse(new[] { "--name", "Sub", "--parent-id", "7" });
|
|
Assert.Empty(nestedParse.Errors);
|
|
Assert.Equal(7, nestedParse.GetValue((Option<int?>)parentOption));
|
|
}
|
|
|
|
/// <summary>Verifies that the parent id is optional on template folder move so the root folder is reachable.</summary>
|
|
[Fact]
|
|
public void TemplateFolderMove_ParentIdIsOptional_SoRootIsReachable()
|
|
{
|
|
var template = TemplateCommands.Build(Url, Format, Username, Password);
|
|
var folder = template.Subcommands.Single(c => c.Name == "folder");
|
|
var move = folder.Subcommands.Single(c => c.Name == "move");
|
|
var parentOption = move.Options.Single(o => o.Name == "--parent-id");
|
|
|
|
var parse = move.Parse(new[] { "--id", "3" });
|
|
Assert.Empty(parse.Errors);
|
|
Assert.Null(parse.GetValue((Option<int?>)parentOption));
|
|
}
|
|
|
|
/// <summary>Verifies that template folder reorder accepts the up and down directions.</summary>
|
|
/// <param name="direction">The reorder direction under test.</param>
|
|
[Theory]
|
|
[InlineData("up")]
|
|
[InlineData("down")]
|
|
public void TemplateFolderReorder_AcceptsUpAndDown(string direction)
|
|
{
|
|
var template = TemplateCommands.Build(Url, Format, Username, Password);
|
|
var folder = template.Subcommands.Single(c => c.Name == "folder");
|
|
var reorder = folder.Subcommands.Single(c => c.Name == "reorder");
|
|
|
|
var parse = reorder.Parse(new[] { "--id", "3", "--direction", direction });
|
|
Assert.Empty(parse.Errors);
|
|
}
|
|
|
|
/// <summary>Verifies that the folder reorder command rejects any direction other than the accepted literals.</summary>
|
|
/// <param name="direction">The rejected direction value.</param>
|
|
[Theory]
|
|
[InlineData("sideways")]
|
|
// AcceptOnlyFromAmong is case-SENSITIVE, so the literals are lowercase only.
|
|
// The audit --channel/--kind/--status options USED to share this contract; they
|
|
// dropped AcceptOnlyFromAmong in register row 99 precisely because the
|
|
// case-sensitivity rejected values the server accepts. --direction keeps it: it is
|
|
// a pure client-side literal that never reaches a server-side parse, so there is no
|
|
// authoritative check behind it to defer to.
|
|
/// <summary>Verifies that template folder reorder rejects any direction other than up or down.</summary>
|
|
/// <param name="direction">The invalid reorder direction under test.</param>
|
|
[InlineData("Up")]
|
|
public void TemplateFolderReorder_RejectsAnythingElse(string direction)
|
|
{
|
|
var template = TemplateCommands.Build(Url, Format, Username, Password);
|
|
var folder = template.Subcommands.Single(c => c.Name == "folder");
|
|
var reorder = folder.Subcommands.Single(c => c.Name == "reorder");
|
|
|
|
var parse = reorder.Parse(new[] { "--id", "3", "--direction", direction });
|
|
Assert.NotEmpty(parse.Errors);
|
|
}
|
|
|
|
/// <summary>Verifies that template folder reorder requires a direction.</summary>
|
|
[Fact]
|
|
public void TemplateFolderReorder_RequiresDirection()
|
|
{
|
|
var template = TemplateCommands.Build(Url, Format, Username, Password);
|
|
var folder = template.Subcommands.Single(c => c.Name == "folder");
|
|
var reorder = folder.Subcommands.Single(c => c.Name == "reorder");
|
|
|
|
var parse = reorder.Parse(new[] { "--id", "3" });
|
|
Assert.NotEmpty(parse.Errors);
|
|
}
|
|
|
|
// ========================================================================
|
|
// Register row 99: the audit filter options must NOT be case-sensitively
|
|
// gated client-side.
|
|
//
|
|
// `audit query --status delivered` used to fail at the CLI parse layer while
|
|
// the server would have accepted it — AcceptOnlyFromAmong matches ordinally
|
|
// against the capitalised literals. The valid set now lives in the help text
|
|
// and the server's strict, case-insensitive check is authoritative (the same
|
|
// treatment row 88 gave `deploy status`). These tests fail before that change.
|
|
// ========================================================================
|
|
|
|
/// <summary>Provides the audit filter option names and sample values used by the theory data.</summary>
|
|
/// <returns>Theory data of subcommand, option and value triples.</returns>
|
|
public static TheoryData<string, string, string> AuditFilterValues() => new()
|
|
{
|
|
// subcommand, option, value — lowercase, uppercase and mixed case of a real member.
|
|
{ "query", "--status", "delivered" },
|
|
{ "query", "--status", "DELIVERED" },
|
|
{ "query", "--channel", "apioutbound" },
|
|
{ "query", "--kind", "apicall" },
|
|
{ "export", "--status", "parked" },
|
|
{ "export", "--channel", "notification" },
|
|
{ "export", "--kind", "dbwrite" },
|
|
};
|
|
|
|
/// <summary>Verifies that audit filter options accept values in any letter case.</summary>
|
|
/// <param name="subcommand">The audit subcommand under test.</param>
|
|
/// <param name="option">The filter option name.</param>
|
|
/// <param name="value">The option value whose casing varies.</param>
|
|
[Theory]
|
|
[MemberData(nameof(AuditFilterValues))]
|
|
public void AuditFilterOptions_AcceptValuesInAnyCase(string subcommand, string option, string value)
|
|
{
|
|
var audit = AuditCommands.Build(Url, Format, Username, Password);
|
|
var cmd = audit.Subcommands.Single(c => c.Name == subcommand);
|
|
|
|
// `export` has three required options; supply them so the only thing under
|
|
// test is the filter value's own acceptance.
|
|
var args = subcommand == "export"
|
|
? new[] { "--since", "1h", "--until", "now", "--format", "csv", "--output", "out.csv", option, value }
|
|
: new[] { option, value };
|
|
|
|
var parse = cmd.Parse(args);
|
|
|
|
Assert.Empty(parse.Errors);
|
|
}
|
|
|
|
/// <summary>Verifies that the audit export format option accepts values in any letter case.</summary>
|
|
/// <param name="format">The export format value under test.</param>
|
|
[Theory]
|
|
[InlineData("csv")]
|
|
[InlineData("CSV")]
|
|
[InlineData("jsonl")]
|
|
public void AuditExport_FormatOption_AcceptsValuesInAnyCase(string format)
|
|
{
|
|
// The server lowercases --format before matching and returns a 400 naming
|
|
// csv/jsonl/parquet for anything else, so the client-side literal gate was
|
|
// pure friction (register row 99).
|
|
var audit = AuditCommands.Build(Url, Format, Username, Password);
|
|
var export = audit.Subcommands.Single(c => c.Name == "export");
|
|
|
|
var parse = export.Parse(new[]
|
|
{
|
|
"--since", "1h", "--until", "now", "--format", format, "--output", "out.csv",
|
|
});
|
|
|
|
Assert.Empty(parse.Errors);
|
|
}
|
|
|
|
/// <summary>Verifies that an unrecognised audit query filter value is not rejected on the client side.</summary>
|
|
[Fact]
|
|
public void AuditQuery_UnrecognisedFilterValue_IsNotRejectedClientSide()
|
|
{
|
|
// Deliberate: the CLI no longer second-guesses the filter vocabulary. A bad
|
|
// value must reach the server, which answers 400 naming the valid set —
|
|
// one authoritative check instead of two that can drift apart.
|
|
var audit = AuditCommands.Build(Url, Format, Username, Password);
|
|
var query = audit.Subcommands.Single(c => c.Name == "query");
|
|
|
|
var parse = query.Parse(new[] { "--status", "NotAStatus" });
|
|
|
|
Assert.Empty(parse.Errors);
|
|
}
|
|
|
|
/// <summary>Verifies that the site area group has a move verb.</summary>
|
|
[Fact]
|
|
public void SiteArea_HasMoveVerb()
|
|
{
|
|
// CLI/management parity follow-up (docs/plans/2026-05-11-deployment-topology-page-design.md):
|
|
// AreaService.MoveAreaAsync had no management command and no CLI verb.
|
|
var site = SiteCommands.Build(Url, Format, Username, Password);
|
|
var area = site.Subcommands.Single(c => c.Name == "area");
|
|
var subNames = area.Subcommands.Select(c => c.Name).ToHashSet();
|
|
Assert.Contains("move", subNames);
|
|
}
|
|
|
|
/// <summary>Verifies that the parent id is optional on site area move so the site root is reachable.</summary>
|
|
[Fact]
|
|
public void SiteAreaMove_ParentIdIsOptional_SoSiteRootIsReachable()
|
|
{
|
|
// Omitting --parent-id is how an area is moved to the site root
|
|
// (null NewParentAreaId on MoveAreaCommand).
|
|
var site = SiteCommands.Build(Url, Format, Username, Password);
|
|
var area = site.Subcommands.Single(c => c.Name == "area");
|
|
var move = area.Subcommands.Single(c => c.Name == "move");
|
|
var parentOption = move.Options.Single(o => o.Name == "--parent-id");
|
|
|
|
var rootParse = move.Parse(new[] { "--id", "3" });
|
|
Assert.Empty(rootParse.Errors);
|
|
Assert.Null(rootParse.GetValue((Option<int?>)parentOption));
|
|
|
|
var reparentParse = move.Parse(new[] { "--id", "3", "--parent-id", "2" });
|
|
Assert.Empty(reparentParse.Errors);
|
|
Assert.Equal(2, reparentParse.GetValue((Option<int?>)parentOption));
|
|
}
|
|
|
|
/// <summary>Verifies that site area move requires an area id.</summary>
|
|
[Fact]
|
|
public void SiteAreaMove_RequiresId()
|
|
{
|
|
var site = SiteCommands.Build(Url, Format, Username, Password);
|
|
var area = site.Subcommands.Single(c => c.Name == "area");
|
|
var move = area.Subcommands.Single(c => c.Name == "move");
|
|
|
|
var parse = move.Parse(Array.Empty<string>());
|
|
Assert.NotEmpty(parse.Errors);
|
|
}
|
|
|
|
/// <summary>Verifies that template list has skip and take paging options.</summary>
|
|
[Fact]
|
|
public void TemplateList_HasSkipAndTakePagingOptions()
|
|
{
|
|
// arch-review P2: `template list` gained additive --skip/--take offset paging.
|
|
var template = TemplateCommands.Build(Url, Format, Username, Password);
|
|
var list = template.Subcommands.Single(c => c.Name == "list");
|
|
var optionNames = list.Options.Select(o => o.Name).ToList();
|
|
Assert.Contains("--skip", optionNames);
|
|
Assert.Contains("--take", optionNames);
|
|
}
|
|
|
|
/// <summary>Verifies that instance list has skip and take paging options.</summary>
|
|
[Fact]
|
|
public void InstanceList_HasSkipAndTakePagingOptions()
|
|
{
|
|
// arch-review P2: `instance list` gained additive --skip/--take offset paging.
|
|
var instance = InstanceCommands.Build(Url, Format, Username, Password);
|
|
var list = instance.Subcommands.Single(c => c.Name == "list");
|
|
var optionNames = list.Options.Select(o => o.Name).ToList();
|
|
Assert.Contains("--skip", optionNames);
|
|
Assert.Contains("--take", optionNames);
|
|
}
|
|
|
|
/// <summary>Verifies that instance list parses the value of the take option.</summary>
|
|
[Fact]
|
|
public void InstanceList_ParsesTakeOptionValue()
|
|
{
|
|
// Parse-level check that --take binds to an int value on the list command.
|
|
var instance = InstanceCommands.Build(Url, Format, Username, Password);
|
|
var list = instance.Subcommands.Single(c => c.Name == "list");
|
|
var takeOption = list.Options.Single(o => o.Name == "--take");
|
|
|
|
var parse = list.Parse(new[] { "--skip", "10", "--take", "5" });
|
|
Assert.Empty(parse.Errors);
|
|
Assert.Equal(5, parse.GetValue((Option<int?>)takeOption));
|
|
}
|
|
|
|
/// <summary>Verifies that each command payload type resolves through the management command registry.</summary>
|
|
/// <param name="commandType">The command payload type under test.</param>
|
|
[Theory]
|
|
[InlineData(typeof(GetInstanceCommand))]
|
|
[InlineData(typeof(ListSitesCommand))]
|
|
[InlineData(typeof(CreateTemplateCommand))]
|
|
[InlineData(typeof(SetConnectionBindingsCommand))]
|
|
[InlineData(typeof(SetInstanceOverridesCommand))]
|
|
[InlineData(typeof(DebugSnapshotCommand))]
|
|
[InlineData(typeof(MgmtDeployInstanceCommand))]
|
|
[InlineData(typeof(MgmtDeploySiteCommand))]
|
|
[InlineData(typeof(QueryAuditLogCommand))]
|
|
[InlineData(typeof(ExportBundleCommand))]
|
|
[InlineData(typeof(PreviewBundleCommand))]
|
|
[InlineData(typeof(ImportBundleCommand))]
|
|
[InlineData(typeof(AddTemplateNativeAlarmSourceCommand))]
|
|
[InlineData(typeof(SetInstanceNativeAlarmSourceOverrideCommand))]
|
|
[InlineData(typeof(MoveAreaCommand))]
|
|
[InlineData(typeof(ListTemplateFoldersCommand))]
|
|
[InlineData(typeof(CreateTemplateFolderCommand))]
|
|
[InlineData(typeof(RenameTemplateFolderCommand))]
|
|
[InlineData(typeof(MoveTemplateFolderCommand))]
|
|
[InlineData(typeof(ReorderTemplateFolderCommand))]
|
|
[InlineData(typeof(DeleteTemplateFolderCommand))]
|
|
public void CommandPayloadTypes_ResolveViaRegistry(Type commandType)
|
|
{
|
|
// GetCommandName throws ArgumentException for an unregistered type — the CLI
|
|
// calls it for every command it sends, so each must round-trip.
|
|
var name = ManagementCommandRegistry.GetCommandName(commandType);
|
|
Assert.False(string.IsNullOrWhiteSpace(name));
|
|
Assert.Equal(commandType, ManagementCommandRegistry.Resolve(name));
|
|
}
|
|
}
|