diff --git a/.github/workflows/security-gate.yml b/.github/workflows/security-gate.yml index dae330d76..afec0004a 100644 --- a/.github/workflows/security-gate.yml +++ b/.github/workflows/security-gate.yml @@ -13,6 +13,8 @@ on: - "src/Nexo.Tests.Infrastructure/Tests/Safety/**" - "application/src/Nexo.API/Security/**" - "application/src/Nexo.CLI/Commands/TrustCommand.cs" + - "application/src/Nexo.CLI/Program.TrustCommands.cs" + - "application/src/Nexo.CLI/Program.CommandRegistration.cs" - "scripts/security-gate*.sh" - "Makefile" - ".github/workflows/security-gate.yml" diff --git a/application/src/Nexo.CLI/Program.CommandRegistration.cs b/application/src/Nexo.CLI/Program.CommandRegistration.cs index 59b08e005..d2c5c42e3 100644 --- a/application/src/Nexo.CLI/Program.CommandRegistration.cs +++ b/application/src/Nexo.CLI/Program.CommandRegistration.cs @@ -10,7 +10,12 @@ namespace Nexo.CLI; /// Program. static partial class Program { - private static RootCommand BuildRootCommand() + /// + /// Builds the CLI root command tree. Internal so Nexo.Tests.CLI can assert the + /// registered top-level commands without spawning the host (guards against a + /// built-but-never-added command, which is how `trust` went missing in #162). + /// + internal static RootCommand BuildRootCommand() { var root = new RootCommand("Nexo command-line interface") { @@ -609,6 +614,7 @@ private static RootCommand BuildRootCommand() var meshCmd = new MeshCommand(); root.AddCommand(meshCmd); root.AddCommand(backgroundAgentCmd); + root.AddCommand(trustCmd); root.AddCommand(testCmd); root.AddCommand(escalateCmd); root.AddCommand(metricsCmd); diff --git a/application/src/Nexo.Tests.CLI/Tests/Commands/RootCommandRegistrationTests.cs b/application/src/Nexo.Tests.CLI/Tests/Commands/RootCommandRegistrationTests.cs new file mode 100644 index 000000000..c77d805ac --- /dev/null +++ b/application/src/Nexo.Tests.CLI/Tests/Commands/RootCommandRegistrationTests.cs @@ -0,0 +1,39 @@ +using System.CommandLine; +using FluentAssertions; +using Xunit; + +namespace Nexo.Tests.CLI.Tests.Commands; + +/// +/// Guards the root command registration list in Program.CommandRegistration.cs. +/// A command can be built (BuildXxxCommand) and then silently never added to the root; +/// that is how `nexo trust` disappeared in #162 while README/GettingStarted and the +/// Tier C security gate kept invoking it. +/// +[Trait("Category", "CLI")] +public sealed class RootCommandRegistrationTests +{ + [Fact(Timeout = 15000)] + public async Task RootCommand_RegistersTrustCommand() + { + await Task.CompletedTask; + var root = Nexo.CLI.Program.BuildRootCommand(); + var subcommands = root.Subcommands.Select(s => s.Name).ToList(); + + subcommands.Should().Contain("trust"); + } + + [Fact(Timeout = 15000)] + public async Task TrustCommand_HasDocumentedSubcommands() + { + await Task.CompletedTask; + var root = Nexo.CLI.Program.BuildRootCommand(); + var trust = root.Subcommands.Single(s => s.Name == "trust"); + var subcommands = trust.Subcommands.Select(s => s.Name).ToList(); + + // Named by README.md, docs/GettingStarted.md and scripts/security-gate-tier-c.sh. + subcommands.Should().Contain(new[] { "dashboard", "boundary", "pause", "resume", "pack" }); + trust.Subcommands.Single(s => s.Name == "pack").Subcommands.Select(s => s.Name) + .Should().Contain("apply"); + } +}