refactor: rename ScadaLink → ZB.MOM.WW.ScadaBridge (code + projects + namespaces)
Solution + 23 src projects + 26 test projects renamed; folders, csproj, namespaces, and ScadaLinkDbContext/ScadaBridgeDbContext class updated. ActorSystem "scadalink" → "scadabridge", Akka seed-node URLs migrated. SQL roles/logins, LDAP domains, CLI command name, and CLI config dir (~/.scadalink → ~/.scadabridge) also renamed. Build green; 5 Host.Tests fail awaiting SQL login rename in next commit. Pre-existing StaleTagMonitor timing flakes unchanged. Rename script committed at tools/rename-to-scadabridge.sh.
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
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 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();
|
||||
|
||||
// NOTE: this list MUST stay in sync with the rootCommand.Add(...) calls in
|
||||
// src/ZB.MOM.WW.ScadaBridge.CLI/Program.cs. When a new command group is added (or one is
|
||||
// removed/renamed), update this array and bump the count assertion in
|
||||
// AllCommandGroups_Build_WithoutThrowing accordingly.
|
||||
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),
|
||||
AuditCommands.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),
|
||||
BundleCommands.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();
|
||||
// CLI-022: bump this count whenever a new top-level command group is
|
||||
// registered in Program.cs. Current registered groups (16):
|
||||
// template, instance, site, deploy, data-connection, external-system,
|
||||
// notification, security, audit-config, audit, health, debug,
|
||||
// shared-script, db-connection, api-method, bundle.
|
||||
Assert.Equal(16, groups.Count);
|
||||
Assert.All(groups, g => Assert.False(string.IsNullOrWhiteSpace(g.Name)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllCommandGroups_Contains_AuditAndBundle()
|
||||
{
|
||||
// CLI-022: 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);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AuditCommandGroup_HasQueryExportAndVerifyChain()
|
||||
{
|
||||
// CLI-022: 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);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BundleCommandGroup_HasExportPreviewAndImport()
|
||||
{
|
||||
// CLI-022: 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);
|
||||
}
|
||||
|
||||
[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))]
|
||||
[InlineData(typeof(ExportBundleCommand))]
|
||||
[InlineData(typeof(PreviewBundleCommand))]
|
||||
[InlineData(typeof(ImportBundleCommand))]
|
||||
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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user