Add Layer SBOM endpoints and CLI command tests for integration
This commit is contained in:
@@ -0,0 +1,401 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// LayerSbomCommandTests.cs
|
||||
// Sprint: SPRINT_20260106_003_001_SCANNER_perlayer_sbom_api
|
||||
// Task: T020 - CLI integration tests for layer SBOM commands
|
||||
// Description: Unit tests for per-layer SBOM and composition recipe CLI commands
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.CommandLine;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Cli.Commands;
|
||||
using StellaOps.Cli.Configuration;
|
||||
using StellaOps.TestKit;
|
||||
|
||||
namespace StellaOps.Cli.Tests.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for layer SBOM CLI commands under the scan command.
|
||||
/// </summary>
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
public sealed class LayerSbomCommandTests
|
||||
{
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly StellaOpsCliOptions _options;
|
||||
private readonly Option<bool> _verboseOption;
|
||||
|
||||
public LayerSbomCommandTests()
|
||||
{
|
||||
var serviceCollection = new ServiceCollection();
|
||||
serviceCollection.AddSingleton(NullLoggerFactory.Instance);
|
||||
_services = serviceCollection.BuildServiceProvider();
|
||||
|
||||
_options = new StellaOpsCliOptions
|
||||
{
|
||||
BackendUrl = "http://localhost:5070",
|
||||
};
|
||||
|
||||
_verboseOption = new Option<bool>("--verbose", "-v") { Description = "Enable verbose output" };
|
||||
}
|
||||
|
||||
#region layers Command Tests
|
||||
|
||||
[Fact]
|
||||
public void BuildLayersCommand_CreatesLayersCommand()
|
||||
{
|
||||
// Act
|
||||
var command = LayerSbomCommandGroup.BuildLayersCommand(
|
||||
_services, _options, _verboseOption, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("layers", command.Name);
|
||||
Assert.Contains("layers", command.Description, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LayersCommand_HasScanIdArgument()
|
||||
{
|
||||
// Arrange
|
||||
var command = LayerSbomCommandGroup.BuildLayersCommand(
|
||||
_services, _options, _verboseOption, CancellationToken.None);
|
||||
|
||||
// Act
|
||||
var scanIdArg = command.Arguments.FirstOrDefault(a => a.Name == "scan-id");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(scanIdArg);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LayersCommand_HasOutputOption()
|
||||
{
|
||||
// Arrange
|
||||
var command = LayerSbomCommandGroup.BuildLayersCommand(
|
||||
_services, _options, _verboseOption, CancellationToken.None);
|
||||
|
||||
// Act
|
||||
var outputOption = command.Options.FirstOrDefault(o =>
|
||||
o.Aliases.Contains("--output") || o.Aliases.Contains("-o"));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(outputOption);
|
||||
Assert.Contains("table", outputOption!.Description, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.Contains("json", outputOption.Description, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LayersCommand_HasVerboseOption()
|
||||
{
|
||||
// Arrange
|
||||
var command = LayerSbomCommandGroup.BuildLayersCommand(
|
||||
_services, _options, _verboseOption, CancellationToken.None);
|
||||
|
||||
// Act
|
||||
var verboseOption = command.Options.FirstOrDefault(o =>
|
||||
o.Aliases.Contains("--verbose") || o.Aliases.Contains("-v"));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(verboseOption);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region layer-sbom Command Tests
|
||||
|
||||
[Fact]
|
||||
public void BuildLayerSbomCommand_CreatesLayerSbomCommand()
|
||||
{
|
||||
// Act
|
||||
var command = LayerSbomCommandGroup.BuildLayerSbomCommand(
|
||||
_services, _options, _verboseOption, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("layer-sbom", command.Name);
|
||||
Assert.Contains("layer", command.Description, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.Contains("sbom", command.Description, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LayerSbomCommand_HasScanIdArgument()
|
||||
{
|
||||
// Arrange
|
||||
var command = LayerSbomCommandGroup.BuildLayerSbomCommand(
|
||||
_services, _options, _verboseOption, CancellationToken.None);
|
||||
|
||||
// Act
|
||||
var scanIdArg = command.Arguments.FirstOrDefault(a => a.Name == "scan-id");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(scanIdArg);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LayerSbomCommand_HasLayerOption()
|
||||
{
|
||||
// Arrange
|
||||
var command = LayerSbomCommandGroup.BuildLayerSbomCommand(
|
||||
_services, _options, _verboseOption, CancellationToken.None);
|
||||
|
||||
// Act
|
||||
var layerOption = command.Options.FirstOrDefault(o =>
|
||||
o.Aliases.Contains("--layer") || o.Aliases.Contains("-l"));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(layerOption);
|
||||
Assert.Contains("sha256", layerOption!.Description);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LayerSbomCommand_LayerOptionIsRequired()
|
||||
{
|
||||
// Arrange
|
||||
var command = LayerSbomCommandGroup.BuildLayerSbomCommand(
|
||||
_services, _options, _verboseOption, CancellationToken.None);
|
||||
|
||||
// Act
|
||||
var layerOption = command.Options.First(o =>
|
||||
o.Aliases.Contains("--layer") || o.Aliases.Contains("-l"));
|
||||
|
||||
// Assert - Check via arity (required options have min arity of 1)
|
||||
Assert.Equal(1, layerOption.Arity.MinimumNumberOfValues);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LayerSbomCommand_HasFormatOption()
|
||||
{
|
||||
// Arrange
|
||||
var command = LayerSbomCommandGroup.BuildLayerSbomCommand(
|
||||
_services, _options, _verboseOption, CancellationToken.None);
|
||||
|
||||
// Act
|
||||
var formatOption = command.Options.FirstOrDefault(o =>
|
||||
o.Aliases.Contains("--format") || o.Aliases.Contains("-f"));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(formatOption);
|
||||
Assert.Contains("cdx", formatOption!.Description, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.Contains("spdx", formatOption.Description, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LayerSbomCommand_HasOutputOption()
|
||||
{
|
||||
// Arrange
|
||||
var command = LayerSbomCommandGroup.BuildLayerSbomCommand(
|
||||
_services, _options, _verboseOption, CancellationToken.None);
|
||||
|
||||
// Act
|
||||
var outputOption = command.Options.FirstOrDefault(o =>
|
||||
o.Aliases.Contains("--output") || o.Aliases.Contains("-o"));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(outputOption);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region recipe Command Tests
|
||||
|
||||
[Fact]
|
||||
public void BuildRecipeCommand_CreatesRecipeCommand()
|
||||
{
|
||||
// Act
|
||||
var command = LayerSbomCommandGroup.BuildRecipeCommand(
|
||||
_services, _options, _verboseOption, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("recipe", command.Name);
|
||||
Assert.Contains("composition", command.Description, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.Contains("recipe", command.Description, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RecipeCommand_HasScanIdArgument()
|
||||
{
|
||||
// Arrange
|
||||
var command = LayerSbomCommandGroup.BuildRecipeCommand(
|
||||
_services, _options, _verboseOption, CancellationToken.None);
|
||||
|
||||
// Act
|
||||
var scanIdArg = command.Arguments.FirstOrDefault(a => a.Name == "scan-id");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(scanIdArg);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RecipeCommand_HasVerifyOption()
|
||||
{
|
||||
// Arrange
|
||||
var command = LayerSbomCommandGroup.BuildRecipeCommand(
|
||||
_services, _options, _verboseOption, CancellationToken.None);
|
||||
|
||||
// Act - search by name containing "verify"
|
||||
var verifyOption = command.Options.FirstOrDefault(o =>
|
||||
o.Name.Contains("verify", StringComparison.OrdinalIgnoreCase) ||
|
||||
o.Aliases.Any(a => a.Contains("verify", StringComparison.OrdinalIgnoreCase)));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(verifyOption);
|
||||
Assert.Contains("Merkle", verifyOption!.Description);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RecipeCommand_HasOutputOption()
|
||||
{
|
||||
// Arrange
|
||||
var command = LayerSbomCommandGroup.BuildRecipeCommand(
|
||||
_services, _options, _verboseOption, CancellationToken.None);
|
||||
|
||||
// Act
|
||||
var outputOption = command.Options.FirstOrDefault(o =>
|
||||
o.Aliases.Contains("--output") || o.Aliases.Contains("-o"));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(outputOption);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RecipeCommand_HasFormatOption()
|
||||
{
|
||||
// Arrange
|
||||
var command = LayerSbomCommandGroup.BuildRecipeCommand(
|
||||
_services, _options, _verboseOption, CancellationToken.None);
|
||||
|
||||
// Act
|
||||
var formatOption = command.Options.FirstOrDefault(o =>
|
||||
o.Aliases.Contains("--format") || o.Aliases.Contains("-f"));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(formatOption);
|
||||
Assert.Contains("json", formatOption!.Description, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.Contains("summary", formatOption.Description, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RecipeCommand_HasVerboseOption()
|
||||
{
|
||||
// Arrange
|
||||
var command = LayerSbomCommandGroup.BuildRecipeCommand(
|
||||
_services, _options, _verboseOption, CancellationToken.None);
|
||||
|
||||
// Act
|
||||
var verboseOption = command.Options.FirstOrDefault(o =>
|
||||
o.Aliases.Contains("--verbose") || o.Aliases.Contains("-v"));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(verboseOption);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Command Hierarchy Tests
|
||||
|
||||
[Fact]
|
||||
public void LayersCommand_ShouldBeAddableToParentCommand()
|
||||
{
|
||||
// Arrange
|
||||
var scanCommand = new Command("scan", "Scanner operations");
|
||||
var layersCommand = LayerSbomCommandGroup.BuildLayersCommand(
|
||||
_services, _options, _verboseOption, CancellationToken.None);
|
||||
|
||||
// Act
|
||||
scanCommand.Add(layersCommand);
|
||||
|
||||
// Assert
|
||||
Assert.Contains(scanCommand.Subcommands, c => c.Name == "layers");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LayerSbomCommand_ShouldBeAddableToParentCommand()
|
||||
{
|
||||
// Arrange
|
||||
var scanCommand = new Command("scan", "Scanner operations");
|
||||
var layerSbomCommand = LayerSbomCommandGroup.BuildLayerSbomCommand(
|
||||
_services, _options, _verboseOption, CancellationToken.None);
|
||||
|
||||
// Act
|
||||
scanCommand.Add(layerSbomCommand);
|
||||
|
||||
// Assert
|
||||
Assert.Contains(scanCommand.Subcommands, c => c.Name == "layer-sbom");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RecipeCommand_ShouldBeAddableToParentCommand()
|
||||
{
|
||||
// Arrange
|
||||
var scanCommand = new Command("scan", "Scanner operations");
|
||||
var recipeCommand = LayerSbomCommandGroup.BuildRecipeCommand(
|
||||
_services, _options, _verboseOption, CancellationToken.None);
|
||||
|
||||
// Act
|
||||
scanCommand.Add(recipeCommand);
|
||||
|
||||
// Assert
|
||||
Assert.Contains(scanCommand.Subcommands, c => c.Name == "recipe");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllCommands_CanCoexistUnderSameParent()
|
||||
{
|
||||
// Arrange
|
||||
var scanCommand = new Command("scan", "Scanner operations");
|
||||
var layersCommand = LayerSbomCommandGroup.BuildLayersCommand(
|
||||
_services, _options, _verboseOption, CancellationToken.None);
|
||||
var layerSbomCommand = LayerSbomCommandGroup.BuildLayerSbomCommand(
|
||||
_services, _options, _verboseOption, CancellationToken.None);
|
||||
var recipeCommand = LayerSbomCommandGroup.BuildRecipeCommand(
|
||||
_services, _options, _verboseOption, CancellationToken.None);
|
||||
|
||||
// Act
|
||||
scanCommand.Add(layersCommand);
|
||||
scanCommand.Add(layerSbomCommand);
|
||||
scanCommand.Add(recipeCommand);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(3, scanCommand.Subcommands.Count);
|
||||
Assert.Contains(scanCommand.Subcommands, c => c.Name == "layers");
|
||||
Assert.Contains(scanCommand.Subcommands, c => c.Name == "layer-sbom");
|
||||
Assert.Contains(scanCommand.Subcommands, c => c.Name == "recipe");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Handler Existence Tests
|
||||
|
||||
[Fact]
|
||||
public void LayersCommand_HasHandler()
|
||||
{
|
||||
// Arrange
|
||||
var command = LayerSbomCommandGroup.BuildLayersCommand(
|
||||
_services, _options, _verboseOption, CancellationToken.None);
|
||||
|
||||
// Assert - Handler is set via SetAction
|
||||
Assert.NotNull(command);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LayerSbomCommand_HasHandler()
|
||||
{
|
||||
// Arrange
|
||||
var command = LayerSbomCommandGroup.BuildLayerSbomCommand(
|
||||
_services, _options, _verboseOption, CancellationToken.None);
|
||||
|
||||
// Assert - Handler is set via SetAction
|
||||
Assert.NotNull(command);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RecipeCommand_HasHandler()
|
||||
{
|
||||
// Arrange
|
||||
var command = LayerSbomCommandGroup.BuildRecipeCommand(
|
||||
_services, _options, _verboseOption, CancellationToken.None);
|
||||
|
||||
// Assert - Handler is set via SetAction
|
||||
Assert.NotNull(command);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user