Merge branch 'main' of https://git.stella-ops.org/stella-ops.org/git.stella-ops.org
This commit is contained in:
@@ -0,0 +1,241 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// DriftCommandTests.cs
|
||||
// Sprint: SPRINT_20260105_002_004_CLI (CLI-010)
|
||||
// Description: Unit tests for facet drift analysis CLI command.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.CommandLine;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using StellaOps.Cli.Commands;
|
||||
|
||||
namespace StellaOps.Cli.Tests.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for the drift command structure.
|
||||
/// </summary>
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class DriftCommandTests
|
||||
{
|
||||
[Fact]
|
||||
public void DriftCommand_HasCorrectName()
|
||||
{
|
||||
// Arrange & Act
|
||||
var command = BuildDriftCommand();
|
||||
|
||||
// Assert
|
||||
Assert.Equal("drift", command.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DriftCommand_HasCorrectDescription()
|
||||
{
|
||||
// Arrange & Act
|
||||
var command = BuildDriftCommand();
|
||||
|
||||
// Assert
|
||||
Assert.Contains("drift", command.Description);
|
||||
Assert.Contains("baseline", command.Description);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DriftCommand_HasImageArgument()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildDriftCommand();
|
||||
|
||||
// Act
|
||||
var imageArg = command.Arguments.FirstOrDefault(a => a.Name == "image");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(imageArg);
|
||||
// Arguments are required by default in System.CommandLine
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DriftCommand_HasBaselineOption()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildDriftCommand();
|
||||
|
||||
// Act
|
||||
var baselineOpt = command.Options.FirstOrDefault(o =>
|
||||
o.Name == "baseline" || o.Name == "--baseline" || o.Aliases.Contains("--baseline"));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(baselineOpt);
|
||||
Assert.True(baselineOpt.Aliases.Contains("-b") || baselineOpt.Aliases.Contains("--baseline"));
|
||||
Assert.Contains("Baseline seal", baselineOpt.Description);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DriftCommand_HasFormatOption()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildDriftCommand();
|
||||
|
||||
// Act
|
||||
var formatOpt = command.Options.FirstOrDefault(o =>
|
||||
o.Name == "format" || o.Name == "--format" || o.Aliases.Contains("--format"));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(formatOpt);
|
||||
Assert.Contains("table", formatOpt.Description);
|
||||
Assert.Contains("json", formatOpt.Description);
|
||||
Assert.Contains("yaml", formatOpt.Description);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DriftCommand_HasVerboseFilesOption()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildDriftCommand();
|
||||
|
||||
// Act
|
||||
var verboseFilesOpt = command.Options.FirstOrDefault(o =>
|
||||
o.Name == "verbose-files" || o.Name == "--verbose-files" || o.Aliases.Contains("--verbose-files"));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(verboseFilesOpt);
|
||||
Assert.Contains("file changes", verboseFilesOpt.Description);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DriftCommand_HasFailOnBreachOption()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildDriftCommand();
|
||||
|
||||
// Act
|
||||
var failOnBreachOpt = command.Options.FirstOrDefault(o =>
|
||||
o.Name == "fail-on-breach" || o.Name == "--fail-on-breach" || o.Aliases.Contains("--fail-on-breach"));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(failOnBreachOpt);
|
||||
Assert.Contains("error code", failOnBreachOpt.Description);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DriftCommand_HasOutputOption()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildDriftCommand();
|
||||
|
||||
// Act
|
||||
var outputOpt = command.Options.FirstOrDefault(o =>
|
||||
o.Name == "output" || o.Name == "--output" || o.Aliases.Contains("--output"));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(outputOpt);
|
||||
Assert.True(outputOpt.Aliases.Contains("-o") || outputOpt.Aliases.Contains("--output"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DriftCommand_HasVerboseOption()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildDriftCommand();
|
||||
|
||||
// Act
|
||||
var verboseOpt = command.Options.FirstOrDefault(o =>
|
||||
o.Name == "verbose" || o.Name == "--verbose" || o.Aliases.Contains("--verbose"));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(verboseOpt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DriftCommand_AllOptionsAreConfigured()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildDriftCommand();
|
||||
var expectedOptions = new[]
|
||||
{
|
||||
"baseline", "format", "verbose-files", "fail-on-breach", "output", "verbose"
|
||||
};
|
||||
|
||||
// Act - normalize all option names by stripping leading dashes
|
||||
var actualOptionNames = command.Options
|
||||
.SelectMany(o => new[] { o.Name.TrimStart('-') }.Concat(o.Aliases.Select(a => a.TrimStart('-'))))
|
||||
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
var actualArgs = command.Arguments.Select(a => a.Name).ToHashSet();
|
||||
|
||||
// Assert - image is now an argument, not an option
|
||||
Assert.Contains("image", actualArgs);
|
||||
foreach (var expected in expectedOptions)
|
||||
{
|
||||
Assert.Contains(expected, actualOptionNames);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DriftCommand_ImageArgument_HasCorrectDescription()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildDriftCommand();
|
||||
var imageArg = command.Arguments.First(a => a.Name == "image");
|
||||
|
||||
// Assert
|
||||
Assert.Contains("Image", imageArg.Description);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DriftCommand_BaselineOption_HasCorrectAliases()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildDriftCommand();
|
||||
var baselineOpt = command.Options.First(o =>
|
||||
o.Name == "baseline" || o.Name == "--baseline" || o.Aliases.Contains("--baseline"));
|
||||
|
||||
// Assert
|
||||
Assert.True(baselineOpt.Aliases.Contains("--baseline") || baselineOpt.Aliases.Contains("-b"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DriftCommand_OutputOption_HasCorrectAliases()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildDriftCommand();
|
||||
var outputOpt = command.Options.First(o =>
|
||||
o.Name == "output" || o.Name == "--output" || o.Aliases.Contains("--output"));
|
||||
|
||||
// Assert
|
||||
Assert.True(outputOpt.Aliases.Contains("--output") || outputOpt.Aliases.Contains("-o"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DriftCommand_FormatOption_SupportsAllFormats()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildDriftCommand();
|
||||
var formatOpt = command.Options.First(o =>
|
||||
o.Name == "format" || o.Name == "--format" || o.Aliases.Contains("--format"));
|
||||
|
||||
// Assert - verify description mentions all supported formats
|
||||
Assert.Contains("table", formatOpt.Description);
|
||||
Assert.Contains("json", formatOpt.Description);
|
||||
Assert.Contains("yaml", formatOpt.Description);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DriftCommand_FailOnBreachOption_ExplainsBehavior()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildDriftCommand();
|
||||
var failOnBreachOpt = command.Options.First(o =>
|
||||
o.Name == "fail-on-breach" || o.Name == "--fail-on-breach" || o.Aliases.Contains("--fail-on-breach"));
|
||||
|
||||
// Assert
|
||||
Assert.Contains("quota", failOnBreachOpt.Description);
|
||||
}
|
||||
|
||||
private static Command BuildDriftCommand()
|
||||
{
|
||||
var mockServices = new Mock<IServiceProvider>();
|
||||
var verboseOption = new Option<bool>("--verbose");
|
||||
return DriftCommandGroup.BuildDriftCommand(
|
||||
mockServices.Object,
|
||||
verboseOption,
|
||||
CancellationToken.None);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// EvidenceCommandTests.cs
|
||||
// Sprint: SPRINT_20260106_003_003_EVIDENCE_export_bundle
|
||||
// Task: T028
|
||||
// Description: Unit tests for evidence bundle CLI commands.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.CommandLine;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Cli.Tests.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for evidence CLI commands.
|
||||
/// </summary>
|
||||
public sealed class EvidenceCommandTests
|
||||
{
|
||||
[Fact]
|
||||
public void EvidenceCommand_HasExportSubcommand()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildEvidenceCommand();
|
||||
|
||||
// Act
|
||||
var exportCmd = command.Subcommands.FirstOrDefault(c => c.Name == "export");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(exportCmd);
|
||||
Assert.Equal("Export an evidence bundle to a tar.gz archive", exportCmd.Description);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EvidenceCommand_HasVerifySubcommand()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildEvidenceCommand();
|
||||
|
||||
// Act
|
||||
var verifyCmd = command.Subcommands.FirstOrDefault(c => c.Name == "verify");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(verifyCmd);
|
||||
Assert.Equal("Verify an exported evidence bundle", verifyCmd.Description);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EvidenceCommand_HasStatusSubcommand()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildEvidenceCommand();
|
||||
|
||||
// Act
|
||||
var statusCmd = command.Subcommands.FirstOrDefault(c => c.Name == "status");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(statusCmd);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExportCommand_HasBundleIdArgument()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildEvidenceCommand();
|
||||
var exportCmd = command.Subcommands.First(c => c.Name == "export");
|
||||
|
||||
// Act
|
||||
var bundleArg = exportCmd.Arguments.FirstOrDefault(a => a.Name == "bundle-id");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(bundleArg);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExportCommand_HasOutputOption()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildEvidenceCommand();
|
||||
var exportCmd = command.Subcommands.First(c => c.Name == "export");
|
||||
|
||||
// Act
|
||||
var outputOpt = exportCmd.Options.FirstOrDefault(o => o.Aliases.Contains("--output") || o.Aliases.Contains("-o"));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(outputOpt);
|
||||
Assert.Contains("-o", outputOpt.Aliases);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExportCommand_HasIncludeLayersOption()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildEvidenceCommand();
|
||||
var exportCmd = command.Subcommands.First(c => c.Name == "export");
|
||||
|
||||
// Act
|
||||
var includeLayersOpt = exportCmd.Options.FirstOrDefault(o => o.Name == "--include-layers" || o.Name == "include-layers" || o.Aliases.Contains("--include-layers"));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(includeLayersOpt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExportCommand_HasCompressionOption()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildEvidenceCommand();
|
||||
var exportCmd = command.Subcommands.First(c => c.Name == "export");
|
||||
|
||||
// Act
|
||||
var compressionOpt = exportCmd.Options.FirstOrDefault(o => o.Aliases.Contains("--compression") || o.Aliases.Contains("-c"));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(compressionOpt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VerifyCommand_HasPathArgument()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildEvidenceCommand();
|
||||
var verifyCmd = command.Subcommands.First(c => c.Name == "verify");
|
||||
|
||||
// Act
|
||||
var pathArg = verifyCmd.Arguments.FirstOrDefault(a => a.Name == "path");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(pathArg);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VerifyCommand_HasOfflineOption()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildEvidenceCommand();
|
||||
var verifyCmd = command.Subcommands.First(c => c.Name == "verify");
|
||||
|
||||
// Act
|
||||
var offlineOpt = verifyCmd.Options.FirstOrDefault(o => o.Name == "--offline" || o.Name == "offline" || o.Aliases.Contains("--offline"));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(offlineOpt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VerifyCommand_HasVerboseOption()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildEvidenceCommand();
|
||||
var verifyCmd = command.Subcommands.First(c => c.Name == "verify");
|
||||
|
||||
// Act
|
||||
var verboseOpt = verifyCmd.Options.FirstOrDefault(o => o.Aliases.Contains("--verbose") || o.Aliases.Contains("-v"));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(verboseOpt);
|
||||
Assert.Contains("-v", verboseOpt.Aliases);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StatusCommand_HasExportIdArgument()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildEvidenceCommand();
|
||||
var statusCmd = command.Subcommands.First(c => c.Name == "status");
|
||||
|
||||
// Act
|
||||
var exportIdArg = statusCmd.Arguments.FirstOrDefault(a => a.Name == "export-id");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(exportIdArg);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExportCommand_HasIncludeRekorProofsOption()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildEvidenceCommand();
|
||||
var exportCmd = command.Subcommands.First(c => c.Name == "export");
|
||||
|
||||
// Act
|
||||
var rekorOpt = exportCmd.Options.FirstOrDefault(o => o.Name == "--include-rekor-proofs" || o.Name == "include-rekor-proofs" || o.Aliases.Contains("--include-rekor-proofs"));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(rekorOpt);
|
||||
}
|
||||
|
||||
private static Command BuildEvidenceCommand()
|
||||
{
|
||||
// Build evidence command group
|
||||
var bundleIdArg = new Argument<string>("bundle-id") { Description = "Bundle ID to export" };
|
||||
|
||||
// Create options with explicit aliases array for better compatibility
|
||||
var outputOption = new Option<string>("--output", "-o") { Description = "Output file path" };
|
||||
var includeLayersOption = new Option<bool>("--include-layers") { Description = "Include per-layer SBOMs" };
|
||||
var includeRekorOption = new Option<bool>("--include-rekor-proofs") { Description = "Include Rekor transparency proofs" };
|
||||
var compressionOption = new Option<int>("--compression", "-c") { Description = "Compression level (1-9)" };
|
||||
|
||||
var exportCmd = new Command("export", "Export an evidence bundle to a tar.gz archive")
|
||||
{
|
||||
bundleIdArg,
|
||||
outputOption,
|
||||
includeLayersOption,
|
||||
includeRekorOption,
|
||||
compressionOption
|
||||
};
|
||||
|
||||
var pathArg = new Argument<string>("path") { Description = "Path to the bundle archive" };
|
||||
var offlineOption = new Option<bool>("--offline") { Description = "Skip Rekor verification" };
|
||||
var verboseOption = new Option<bool>("--verbose", "-v") { Description = "Verbose output" };
|
||||
|
||||
var verifyCmd = new Command("verify", "Verify an exported evidence bundle")
|
||||
{
|
||||
pathArg,
|
||||
offlineOption,
|
||||
verboseOption
|
||||
};
|
||||
|
||||
var exportIdArg = new Argument<string>("export-id") { Description = "Export job ID to check" };
|
||||
var statusCmd = new Command("status", "Check export job status")
|
||||
{
|
||||
exportIdArg
|
||||
};
|
||||
|
||||
var evidenceCmd = new Command("evidence", "Evidence bundle management commands")
|
||||
{
|
||||
exportCmd,
|
||||
verifyCmd,
|
||||
statusCmd
|
||||
};
|
||||
|
||||
return evidenceCmd;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
238
src/Cli/__Tests/StellaOps.Cli.Tests/Commands/SealCommandTests.cs
Normal file
238
src/Cli/__Tests/StellaOps.Cli.Tests/Commands/SealCommandTests.cs
Normal file
@@ -0,0 +1,238 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// SealCommandTests.cs
|
||||
// Sprint: SPRINT_20260105_002_004_CLI (CLI-006)
|
||||
// Description: Unit tests for facet seal CLI command.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.CommandLine;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using StellaOps.Cli.Commands;
|
||||
|
||||
namespace StellaOps.Cli.Tests.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for the seal command structure.
|
||||
/// </summary>
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class SealCommandTests
|
||||
{
|
||||
[Fact]
|
||||
public void SealCommand_HasCorrectName()
|
||||
{
|
||||
// Arrange & Act
|
||||
var command = BuildSealCommand();
|
||||
|
||||
// Assert
|
||||
Assert.Equal("seal", command.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SealCommand_HasCorrectDescription()
|
||||
{
|
||||
// Arrange & Act
|
||||
var command = BuildSealCommand();
|
||||
|
||||
// Assert
|
||||
Assert.Contains("facet seal", command.Description);
|
||||
Assert.Contains("drift detection", command.Description);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SealCommand_HasImageArgument()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildSealCommand();
|
||||
|
||||
// Act
|
||||
var imageArg = command.Arguments.FirstOrDefault(a => a.Name == "image");
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(imageArg);
|
||||
// Arguments are required by default in System.CommandLine
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SealCommand_HasOutputOption()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildSealCommand();
|
||||
|
||||
// Act
|
||||
var outputOpt = command.Options.FirstOrDefault(o =>
|
||||
o.Name == "output" || o.Name == "--output" || o.Aliases.Contains("--output"));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(outputOpt);
|
||||
Assert.True(outputOpt.Aliases.Contains("-o") || outputOpt.Aliases.Contains("--output"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SealCommand_HasStoreOption()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildSealCommand();
|
||||
|
||||
// Act
|
||||
var storeOpt = command.Options.FirstOrDefault(o =>
|
||||
o.Name == "store" || o.Name == "--store" || o.Aliases.Contains("--store"));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(storeOpt);
|
||||
Assert.Contains("Store seal", storeOpt.Description);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SealCommand_HasSignOption()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildSealCommand();
|
||||
|
||||
// Act
|
||||
var signOpt = command.Options.FirstOrDefault(o =>
|
||||
o.Name == "sign" || o.Name == "--sign" || o.Aliases.Contains("--sign"));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(signOpt);
|
||||
Assert.Contains("Sign", signOpt.Description);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SealCommand_HasKeyOption()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildSealCommand();
|
||||
|
||||
// Act
|
||||
var keyOpt = command.Options.FirstOrDefault(o =>
|
||||
o.Name == "key" || o.Name == "--key" || o.Aliases.Contains("--key"));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(keyOpt);
|
||||
Assert.True(keyOpt.Aliases.Contains("-k") || keyOpt.Aliases.Contains("--key"));
|
||||
Assert.Contains("Private key", keyOpt.Description);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SealCommand_HasFacetsOption()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildSealCommand();
|
||||
|
||||
// Act
|
||||
var facetsOpt = command.Options.FirstOrDefault(o =>
|
||||
o.Name == "facets" || o.Name == "--facets" || o.Aliases.Contains("--facets"));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(facetsOpt);
|
||||
Assert.True(facetsOpt.Aliases.Contains("-f") || facetsOpt.Aliases.Contains("--facets"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SealCommand_HasFormatOption()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildSealCommand();
|
||||
|
||||
// Act
|
||||
var formatOpt = command.Options.FirstOrDefault(o =>
|
||||
o.Name == "format" || o.Name == "--format" || o.Aliases.Contains("--format"));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(formatOpt);
|
||||
Assert.Contains("json", formatOpt.Description);
|
||||
Assert.Contains("yaml", formatOpt.Description);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SealCommand_HasVerboseOption()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildSealCommand();
|
||||
|
||||
// Act
|
||||
var verboseOpt = command.Options.FirstOrDefault(o =>
|
||||
o.Name == "verbose" || o.Name == "--verbose" || o.Aliases.Contains("--verbose"));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(verboseOpt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SealCommand_AllOptionsAreConfigured()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildSealCommand();
|
||||
var expectedOptions = new[] { "output", "store", "sign", "key", "facets", "format", "verbose" };
|
||||
|
||||
// Act - normalize all option names by stripping leading dashes
|
||||
var actualOptionNames = command.Options
|
||||
.SelectMany(o => new[] { o.Name.TrimStart('-') }.Concat(o.Aliases.Select(a => a.TrimStart('-'))))
|
||||
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
var actualArgs = command.Arguments.Select(a => a.Name).ToHashSet();
|
||||
|
||||
// Assert - image is an argument
|
||||
Assert.Contains("image", actualArgs);
|
||||
foreach (var expected in expectedOptions)
|
||||
{
|
||||
Assert.Contains(expected, actualOptionNames);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SealCommand_ImageArgument_HasCorrectDescription()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildSealCommand();
|
||||
var imageArg = command.Arguments.First(a => a.Name == "image");
|
||||
|
||||
// Assert
|
||||
Assert.Contains("Image", imageArg.Description);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SealCommand_OutputOption_HasCorrectAliases()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildSealCommand();
|
||||
var outputOpt = command.Options.First(o =>
|
||||
o.Name == "output" || o.Name == "--output" || o.Aliases.Contains("--output"));
|
||||
|
||||
// Assert
|
||||
Assert.True(outputOpt.Aliases.Contains("--output") || outputOpt.Aliases.Contains("-o"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SealCommand_KeyOption_HasCorrectAliases()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildSealCommand();
|
||||
var keyOpt = command.Options.First(o =>
|
||||
o.Name == "key" || o.Name == "--key" || o.Aliases.Contains("--key"));
|
||||
|
||||
// Assert
|
||||
Assert.True(keyOpt.Aliases.Contains("--key") || keyOpt.Aliases.Contains("-k"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SealCommand_FacetsOption_HasCorrectAliases()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildSealCommand();
|
||||
var facetsOpt = command.Options.First(o =>
|
||||
o.Name == "facets" || o.Name == "--facets" || o.Aliases.Contains("--facets"));
|
||||
|
||||
// Assert
|
||||
Assert.True(facetsOpt.Aliases.Contains("--facets") || facetsOpt.Aliases.Contains("-f"));
|
||||
}
|
||||
|
||||
private static Command BuildSealCommand()
|
||||
{
|
||||
var mockServices = new Mock<IServiceProvider>();
|
||||
var verboseOption = new Option<bool>("--verbose");
|
||||
return SealCommandGroup.BuildSealCommand(
|
||||
mockServices.Object,
|
||||
verboseOption,
|
||||
CancellationToken.None);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// VexGenCommandTests.cs
|
||||
// Sprint: SPRINT_20260105_002_004_CLI (CLI-014)
|
||||
// Description: Unit tests for VEX generation CLI command.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.CommandLine;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using StellaOps.Cli.Commands;
|
||||
|
||||
namespace StellaOps.Cli.Tests.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for the vex gen command structure.
|
||||
/// </summary>
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class VexGenCommandTests
|
||||
{
|
||||
[Fact]
|
||||
public void VexGenCommand_HasCorrectName()
|
||||
{
|
||||
// Arrange & Act
|
||||
var command = BuildVexGenCommand();
|
||||
|
||||
// Assert
|
||||
Assert.Equal("gen", command.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VexGenCommand_HasCorrectDescription()
|
||||
{
|
||||
// Arrange & Act
|
||||
var command = BuildVexGenCommand();
|
||||
|
||||
// Assert
|
||||
Assert.Contains("VEX", command.Description);
|
||||
Assert.Contains("drift", command.Description);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VexGenCommand_HasFromDriftOption()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildVexGenCommand();
|
||||
|
||||
// Act
|
||||
var fromDriftOpt = command.Options.FirstOrDefault(o =>
|
||||
o.Name == "from-drift" || o.Name == "--from-drift" || o.Aliases.Contains("--from-drift"));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(fromDriftOpt);
|
||||
Assert.Contains("drift", fromDriftOpt.Description);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VexGenCommand_HasImageOption()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildVexGenCommand();
|
||||
|
||||
// Act
|
||||
var imageOpt = command.Options.FirstOrDefault(o =>
|
||||
o.Name == "image" || o.Name == "--image" || o.Aliases.Contains("--image"));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(imageOpt);
|
||||
Assert.True(imageOpt.Aliases.Contains("-i") || imageOpt.Aliases.Contains("--image"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VexGenCommand_HasBaselineOption()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildVexGenCommand();
|
||||
|
||||
// Act
|
||||
var baselineOpt = command.Options.FirstOrDefault(o =>
|
||||
o.Name == "baseline" || o.Name == "--baseline" || o.Aliases.Contains("--baseline"));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(baselineOpt);
|
||||
Assert.True(baselineOpt.Aliases.Contains("-b") || baselineOpt.Aliases.Contains("--baseline"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VexGenCommand_HasOutputOption()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildVexGenCommand();
|
||||
|
||||
// Act
|
||||
var outputOpt = command.Options.FirstOrDefault(o =>
|
||||
o.Name == "output" || o.Name == "--output" || o.Aliases.Contains("--output"));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(outputOpt);
|
||||
Assert.True(outputOpt.Aliases.Contains("-o") || outputOpt.Aliases.Contains("--output"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VexGenCommand_HasFormatOption()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildVexGenCommand();
|
||||
|
||||
// Act
|
||||
var formatOpt = command.Options.FirstOrDefault(o =>
|
||||
o.Name == "format" || o.Name == "--format" || o.Aliases.Contains("--format"));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(formatOpt);
|
||||
Assert.Contains("openvex", formatOpt.Description);
|
||||
Assert.Contains("csaf", formatOpt.Description);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VexGenCommand_HasStatusOption()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildVexGenCommand();
|
||||
|
||||
// Act
|
||||
var statusOpt = command.Options.FirstOrDefault(o =>
|
||||
o.Name == "status" || o.Name == "--status" || o.Aliases.Contains("--status"));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(statusOpt);
|
||||
Assert.Contains("under_investigation", statusOpt.Description);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VexGenCommand_HasVerboseOption()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildVexGenCommand();
|
||||
|
||||
// Act
|
||||
var verboseOpt = command.Options.FirstOrDefault(o =>
|
||||
o.Name == "verbose" || o.Name == "--verbose" || o.Aliases.Contains("--verbose"));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(verboseOpt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VexGenCommand_AllOptionsAreConfigured()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildVexGenCommand();
|
||||
var expectedOptions = new[]
|
||||
{
|
||||
"from-drift", "image", "baseline", "output", "format", "status", "verbose"
|
||||
};
|
||||
|
||||
// Act - normalize all option names by stripping leading dashes
|
||||
var actualOptionNames = command.Options
|
||||
.SelectMany(o => new[] { o.Name.TrimStart('-') }.Concat(o.Aliases.Select(a => a.TrimStart('-'))))
|
||||
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// Assert
|
||||
foreach (var expected in expectedOptions)
|
||||
{
|
||||
Assert.Contains(expected, actualOptionNames);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VexGenCommand_ImageOption_HasRequiredSet()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildVexGenCommand();
|
||||
var imageOpt = command.Options.First(o =>
|
||||
o.Name == "image" || o.Name == "--image" || o.Aliases.Contains("--image"));
|
||||
|
||||
// Assert - verify it's configured as required (the Required property exists)
|
||||
Assert.NotNull(imageOpt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VexGenCommand_FormatOption_SupportsAllFormats()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildVexGenCommand();
|
||||
var formatOpt = command.Options.First(o =>
|
||||
o.Name == "format" || o.Name == "--format" || o.Aliases.Contains("--format"));
|
||||
|
||||
// Assert - verify description mentions supported formats
|
||||
Assert.Contains("openvex", formatOpt.Description);
|
||||
Assert.Contains("csaf", formatOpt.Description);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VexGenCommand_StatusOption_SupportsAllStatuses()
|
||||
{
|
||||
// Arrange
|
||||
var command = BuildVexGenCommand();
|
||||
var statusOpt = command.Options.First(o =>
|
||||
o.Name == "status" || o.Name == "--status" || o.Aliases.Contains("--status"));
|
||||
|
||||
// Assert - verify description mentions supported statuses
|
||||
Assert.Contains("under_investigation", statusOpt.Description);
|
||||
Assert.Contains("not_affected", statusOpt.Description);
|
||||
Assert.Contains("affected", statusOpt.Description);
|
||||
}
|
||||
|
||||
private static Command BuildVexGenCommand()
|
||||
{
|
||||
var mockServices = new Mock<IServiceProvider>();
|
||||
var verboseOption = new Option<bool>("--verbose");
|
||||
return VexGenCommandGroup.BuildVexGenCommand(
|
||||
mockServices.Object,
|
||||
verboseOption,
|
||||
CancellationToken.None);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user