From 952ba77924b98f0fb6c3f68e2af95bfef46883fc Mon Sep 17 00:00:00 2001 From: StellaOps Bot Date: Sat, 6 Dec 2025 16:25:04 +0000 Subject: [PATCH] cli: add system migrations command skeleton and tests --- .../StellaOps.Cli/Commands/CommandFactory.cs | 7 ++- .../Commands/SystemCommandBuilder.cs | 62 +++++++++++++++++++ .../Commands/SystemCommandBuilderTests.cs | 31 ++++++++++ 3 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 src/Cli/StellaOps.Cli/Commands/SystemCommandBuilder.cs create mode 100644 src/Cli/__Tests/StellaOps.Cli.Tests/Commands/SystemCommandBuilderTests.cs diff --git a/src/Cli/StellaOps.Cli/Commands/CommandFactory.cs b/src/Cli/StellaOps.Cli/Commands/CommandFactory.cs index b7d6d7f02..2ff78d002 100644 --- a/src/Cli/StellaOps.Cli/Commands/CommandFactory.cs +++ b/src/Cli/StellaOps.Cli/Commands/CommandFactory.cs @@ -72,9 +72,10 @@ internal static class CommandFactory root.Add(BuildRiskCommand(services, verboseOption, cancellationToken)); root.Add(BuildReachabilityCommand(services, verboseOption, cancellationToken)); root.Add(BuildApiCommand(services, verboseOption, cancellationToken)); - root.Add(BuildSdkCommand(services, verboseOption, cancellationToken)); - root.Add(BuildMirrorCommand(services, verboseOption, cancellationToken)); - root.Add(BuildAirgapCommand(services, verboseOption, cancellationToken)); + root.Add(BuildSdkCommand(services, verboseOption, cancellationToken)); + root.Add(BuildMirrorCommand(services, verboseOption, cancellationToken)); + root.Add(BuildAirgapCommand(services, verboseOption, cancellationToken)); + root.Add(SystemCommandBuilder.BuildSystemCommand()); var pluginLogger = loggerFactory.CreateLogger(); var pluginLoader = new CliCommandModuleLoader(services, options, pluginLogger); diff --git a/src/Cli/StellaOps.Cli/Commands/SystemCommandBuilder.cs b/src/Cli/StellaOps.Cli/Commands/SystemCommandBuilder.cs new file mode 100644 index 000000000..92d446b57 --- /dev/null +++ b/src/Cli/StellaOps.Cli/Commands/SystemCommandBuilder.cs @@ -0,0 +1,62 @@ +using System.CommandLine; +using System.Threading.Tasks; +using StellaOps.Cli.Services; + +namespace StellaOps.Cli.Commands; + +internal static class SystemCommandBuilder +{ + internal static Command BuildSystemCommand() + { + var moduleOption = new Option("--module", description: "Module name (Authority, Scheduler, Concelier, Policy, Notify, Excititor, all)"); + var categoryOption = new Option("--category", description: "Migration category (startup, release, seed, data)"); + var dryRunOption = new Option("--dry-run", description: "List migrations without executing"); + + var run = new Command("migrations-run", "Run migrations for the selected module(s)."); + run.AddOption(moduleOption); + run.AddOption(categoryOption); + run.AddOption(dryRunOption); + run.SetAction(async parseResult => + { + // Placeholder implementation; real execution is handled by migration host/CLI services. + var modules = MigrationModuleRegistry.GetModules(parseResult.GetValue(moduleOption)); + if (!modules.Any()) + { + throw new CommandLineException("No modules matched the filter; available: " + string.Join(", ", MigrationModuleRegistry.ModuleNames)); + } + await Task.CompletedTask; + }); + + var status = new Command("migrations-status", "Show migration status for the selected module(s)."); + status.AddOption(moduleOption); + status.AddOption(categoryOption); + status.SetAction(async parseResult => + { + var modules = MigrationModuleRegistry.GetModules(parseResult.GetValue(moduleOption)); + if (!modules.Any()) + { + throw new CommandLineException("No modules matched the filter; available: " + string.Join(", ", MigrationModuleRegistry.ModuleNames)); + } + await Task.CompletedTask; + }); + + var verify = new Command("migrations-verify", "Verify migration checksums for the selected module(s)."); + verify.AddOption(moduleOption); + verify.AddOption(categoryOption); + verify.SetAction(async parseResult => + { + var modules = MigrationModuleRegistry.GetModules(parseResult.GetValue(moduleOption)); + if (!modules.Any()) + { + throw new CommandLineException("No modules matched the filter; available: " + string.Join(", ", MigrationModuleRegistry.ModuleNames)); + } + await Task.CompletedTask; + }); + + var system = new Command("system", "System operations (migrations)."); + system.Add(run); + system.Add(status); + system.Add(verify); + return system; + } +} diff --git a/src/Cli/__Tests/StellaOps.Cli.Tests/Commands/SystemCommandBuilderTests.cs b/src/Cli/__Tests/StellaOps.Cli.Tests/Commands/SystemCommandBuilderTests.cs new file mode 100644 index 000000000..57947dd7d --- /dev/null +++ b/src/Cli/__Tests/StellaOps.Cli.Tests/Commands/SystemCommandBuilderTests.cs @@ -0,0 +1,31 @@ +using System.CommandLine; +using StellaOps.Cli.Commands; +using StellaOps.Cli.Services; +using Xunit; + +namespace StellaOps.Cli.Tests.Commands; + +public class SystemCommandBuilderTests +{ + [Fact] + public void BuildSystemCommand_AddsMigrationsSubcommands() + { + var system = SystemCommandBuilder.BuildSystemCommand(); + Assert.NotNull(system); + Assert.Equal("system", system.Name); + Assert.Contains(system.Subcommands, c => c.Name == "migrations-run"); + Assert.Contains(system.Subcommands, c => c.Name == "migrations-status"); + Assert.Contains(system.Subcommands, c => c.Name == "migrations-verify"); + } + + [Fact] + public void ModuleNames_Contains_All_Modules() + { + Assert.Contains("Authority", MigrationModuleRegistry.ModuleNames); + Assert.Contains("Scheduler", MigrationModuleRegistry.ModuleNames); + Assert.Contains("Concelier", MigrationModuleRegistry.ModuleNames); + Assert.Contains("Policy", MigrationModuleRegistry.ModuleNames); + Assert.Contains("Notify", MigrationModuleRegistry.ModuleNames); + Assert.Contains("Excititor", MigrationModuleRegistry.ModuleNames); + } +}