105 lines
4.5 KiB
C#
105 lines
4.5 KiB
C#
using System.CommandLine;
|
|
using ScadaLink.CLI.Commands;
|
|
using ScadaLink.Commons.Messages.Management;
|
|
|
|
namespace ScadaLink.CLI.Tests;
|
|
|
|
/// <summary>
|
|
/// Regression tests for CLI-013 — the command-tree wiring was 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();
|
|
|
|
private static IEnumerable<Command> AllCommandGroups() => new[]
|
|
{
|
|
TemplateCommands.Build(Url, Format, Username, Password),
|
|
InstanceCommands.Build(Url, Format, Username, Password),
|
|
SiteCommands.Build(Url, Format, Username, Password),
|
|
DeployCommands.Build(Url, Format, Username, Password),
|
|
DataConnectionCommands.Build(Url, Format, Username, Password),
|
|
ExternalSystemCommands.Build(Url, Format, Username, Password),
|
|
NotificationCommands.Build(Url, Format, Username, Password),
|
|
SecurityCommands.Build(Url, Format, Username, Password),
|
|
AuditLogCommands.Build(Url, Format, Username, Password),
|
|
HealthCommands.Build(Url, Format, Username, Password),
|
|
DebugCommands.Build(Url, Format, Username, Password),
|
|
SharedScriptCommands.Build(Url, Format, Username, Password),
|
|
DbConnectionCommands.Build(Url, Format, Username, Password),
|
|
ApiMethodCommands.Build(Url, Format, Username, Password),
|
|
};
|
|
|
|
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;
|
|
}
|
|
|
|
[Fact]
|
|
public void AllCommandGroups_Build_WithoutThrowing()
|
|
{
|
|
var groups = AllCommandGroups().ToList();
|
|
Assert.Equal(14, groups.Count);
|
|
Assert.All(groups, g => Assert.False(string.IsNullOrWhiteSpace(g.Name)));
|
|
}
|
|
|
|
[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."));
|
|
}
|
|
|
|
[Fact]
|
|
public void TemplateCompositionDelete_IsKeyedByIdOnly()
|
|
{
|
|
// CLI-015: 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);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(typeof(GetInstanceCommand))]
|
|
[InlineData(typeof(ListSitesCommand))]
|
|
[InlineData(typeof(CreateTemplateCommand))]
|
|
[InlineData(typeof(SetConnectionBindingsCommand))]
|
|
[InlineData(typeof(SetInstanceOverridesCommand))]
|
|
[InlineData(typeof(DebugSnapshotCommand))]
|
|
[InlineData(typeof(MgmtDeployInstanceCommand))]
|
|
[InlineData(typeof(QueryAuditLogCommand))]
|
|
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));
|
|
}
|
|
}
|