using System.CommandLine; using Microsoft.Extensions.DependencyInjection; using StellaOps.Cli.Commands.Scan; using Xunit; namespace StellaOps.Cli.Tests.Commands; public sealed class BinaryDiffCommandTests { private readonly IServiceProvider _services; private readonly Option _verboseOption; private readonly CancellationToken _cancellationToken; public BinaryDiffCommandTests() { _services = new ServiceCollection().BuildServiceProvider(); _verboseOption = new Option("--verbose", new[] { "-v" }) { Description = "Enable verbose output" }; _cancellationToken = CancellationToken.None; } [Fact] public void BuildDiffCommand_HasRequiredOptions() { var command = BuildDiffCommand(); Assert.Contains(command.Options, option => HasAlias(option, "--base", "-b")); Assert.Contains(command.Options, option => HasAlias(option, "--target", "-t")); Assert.Contains(command.Options, option => HasAlias(option, "--mode", "-m")); Assert.Contains(command.Options, option => HasAlias(option, "--emit-dsse", "-d")); Assert.Contains(command.Options, option => HasAlias(option, "--signing-key")); Assert.Contains(command.Options, option => HasAlias(option, "--format", "-f")); Assert.Contains(command.Options, option => HasAlias(option, "--platform", "-p")); Assert.Contains(command.Options, option => HasAlias(option, "--include-unchanged")); Assert.Contains(command.Options, option => HasAlias(option, "--sections")); Assert.Contains(command.Options, option => HasAlias(option, "--registry-auth")); Assert.Contains(command.Options, option => HasAlias(option, "--timeout")); Assert.Contains(command.Options, option => HasAlias(option, "--verbose", "-v")); } [Fact] public void BuildDiffCommand_RequiresBaseAndTarget() { var command = BuildDiffCommand(); var baseOption = FindOption(command, "--base"); var targetOption = FindOption(command, "--target"); Assert.NotNull(baseOption); Assert.NotNull(targetOption); Assert.Equal(1, baseOption!.Arity.MinimumNumberOfValues); Assert.Equal(1, targetOption!.Arity.MinimumNumberOfValues); } [Fact] public void DiffCommand_ParsesMinimalArgs() { var root = BuildRoot(out _); var result = root.Parse("scan diff --base registry.example.com/app:1 --target registry.example.com/app:2"); Assert.Empty(result.Errors); } [Fact] public void DiffCommand_FailsWhenBaseMissing() { var root = BuildRoot(out _); var result = root.Parse("scan diff --target registry.example.com/app:2"); Assert.NotEmpty(result.Errors); } [Fact] public void DiffCommand_AcceptsSectionsTokens() { var root = BuildRoot(out var diffCommand); var result = root.Parse("scan diff --base registry.example.com/app:1 --target registry.example.com/app:2 --sections .text,.rodata --sections .data"); Assert.Empty(result.Errors); var sectionsOption = diffCommand.Options .OfType>() .Single(option => HasAlias(option, "--sections")); Assert.True(sectionsOption.AllowMultipleArgumentsPerToken); } private Command BuildDiffCommand() { return BinaryDiffCommandGroup.BuildDiffCommand(_services, _verboseOption, _cancellationToken); } private RootCommand BuildRoot(out Command diffCommand) { diffCommand = BuildDiffCommand(); var scan = new Command("scan", "Scanner operations") { diffCommand }; return new RootCommand { scan }; } private static Option? FindOption(Command command, string alias) { return command.Options.FirstOrDefault(option => option.Name.Equals(alias, StringComparison.OrdinalIgnoreCase) || option.Name.Equals(alias.TrimStart('-'), StringComparison.OrdinalIgnoreCase) || option.Aliases.Contains(alias)); } private static bool HasAlias(Option option, params string[] aliases) { foreach (var alias in aliases) { if (option.Name.Equals(alias, StringComparison.OrdinalIgnoreCase) || option.Name.Equals(alias.TrimStart('-'), StringComparison.OrdinalIgnoreCase) || option.Aliases.Contains(alias)) { return true; } } return false; } }