sprints and audit work

This commit is contained in:
StellaOps Bot
2026-01-07 09:36:16 +02:00
parent 05833e0af2
commit ab364c6032
377 changed files with 64534 additions and 1627 deletions

View File

@@ -0,0 +1,292 @@
// <copyright file="ProveCommandTests.cs" company="Stella Operations">
// Copyright (c) Stella Operations. Licensed under AGPL-3.0-or-later.
// </copyright>
// -----------------------------------------------------------------------------
// ProveCommandTests.cs
// Sprint: SPRINT_20260105_002_001_REPLAY
// Task: RPL-019 - Integration tests for stella prove command
// Description: Tests for the prove command structure and local bundle mode.
// -----------------------------------------------------------------------------
using System.CommandLine;
using System.Text;
using System.Text.Json;
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;
using StellaOps.Cli.Commands;
namespace StellaOps.Cli.Tests.Commands;
/// <summary>
/// Tests for ProveCommandGroup and related functionality.
/// </summary>
[Trait("Category", "Unit")]
public sealed class ProveCommandTests : IDisposable
{
private readonly string _testDir;
public ProveCommandTests()
{
_testDir = Path.Combine(Path.GetTempPath(), $"prove-test-{Guid.NewGuid():N}");
Directory.CreateDirectory(_testDir);
}
public void Dispose()
{
if (Directory.Exists(_testDir))
{
Directory.Delete(_testDir, recursive: true);
}
}
#region Command Structure Tests
[Fact]
public void BuildProveCommand_ReturnsCommandWithCorrectName()
{
// Arrange
var services = new ServiceCollection().BuildServiceProvider();
var verboseOption = new Option<bool>("--verbose");
// Act
var command = ProveCommandGroup.BuildProveCommand(services, verboseOption, CancellationToken.None);
// Assert
command.Name.Should().Be("prove");
command.Description.Should().Contain("replay proof");
}
[Fact]
public void BuildProveCommand_HasRequiredImageOption()
{
// Arrange
var services = new ServiceCollection().BuildServiceProvider();
var verboseOption = new Option<bool>("--verbose");
// Act
var command = ProveCommandGroup.BuildProveCommand(services, verboseOption, CancellationToken.None);
// Assert
var imageOption = command.Options.FirstOrDefault(o => o.Name == "image");
imageOption.Should().NotBeNull();
imageOption!.Required.Should().BeTrue();
}
[Fact]
public void BuildProveCommand_HasOptionalAtOption()
{
// Arrange
var services = new ServiceCollection().BuildServiceProvider();
var verboseOption = new Option<bool>("--verbose");
// Act
var command = ProveCommandGroup.BuildProveCommand(services, verboseOption, CancellationToken.None);
// Assert
var atOption = command.Options.FirstOrDefault(o => o.Name == "at");
atOption.Should().NotBeNull();
atOption!.Required.Should().BeFalse();
}
[Fact]
public void BuildProveCommand_HasOptionalSnapshotOption()
{
// Arrange
var services = new ServiceCollection().BuildServiceProvider();
var verboseOption = new Option<bool>("--verbose");
// Act
var command = ProveCommandGroup.BuildProveCommand(services, verboseOption, CancellationToken.None);
// Assert
var snapshotOption = command.Options.FirstOrDefault(o => o.Name == "snapshot");
snapshotOption.Should().NotBeNull();
snapshotOption!.Required.Should().BeFalse();
}
[Fact]
public void BuildProveCommand_HasOptionalBundleOption()
{
// Arrange
var services = new ServiceCollection().BuildServiceProvider();
var verboseOption = new Option<bool>("--verbose");
// Act
var command = ProveCommandGroup.BuildProveCommand(services, verboseOption, CancellationToken.None);
// Assert
var bundleOption = command.Options.FirstOrDefault(o => o.Name == "bundle");
bundleOption.Should().NotBeNull();
bundleOption!.Required.Should().BeFalse();
}
[Fact]
public void BuildProveCommand_HasOutputOptionWithValidValues()
{
// Arrange
var services = new ServiceCollection().BuildServiceProvider();
var verboseOption = new Option<bool>("--verbose");
// Act
var command = ProveCommandGroup.BuildProveCommand(services, verboseOption, CancellationToken.None);
// Assert
var outputOption = command.Options.FirstOrDefault(o => o.Name == "output");
outputOption.Should().NotBeNull();
}
#endregion
#region Exit Code Tests
[Fact]
public void ProveExitCodes_SuccessIsZero()
{
ProveExitCodes.Success.Should().Be(0);
}
[Fact]
public void ProveExitCodes_CancelledIs130()
{
ProveExitCodes.Cancelled.Should().Be(130);
}
[Fact]
public void ProveExitCodes_AllCodesAreUnique()
{
var codes = new[]
{
ProveExitCodes.Success,
ProveExitCodes.InvalidInput,
ProveExitCodes.SnapshotNotFound,
ProveExitCodes.BundleNotFound,
ProveExitCodes.ReplayFailed,
ProveExitCodes.VerdictMismatch,
ProveExitCodes.ServiceUnavailable,
ProveExitCodes.FileNotFound,
ProveExitCodes.InvalidBundle,
ProveExitCodes.SystemError,
ProveExitCodes.Cancelled
};
codes.Should().OnlyHaveUniqueItems();
}
#endregion
#region Adapter Interface Tests
[Fact]
public void SnapshotInfo_CanBeCreated()
{
// Arrange & Act
var snapshot = new SnapshotInfo(
SnapshotId: "snap-123",
ImageDigest: "sha256:abc123",
CreatedAt: DateTimeOffset.UtcNow,
PolicyVersion: "v1.0.0");
// Assert
snapshot.SnapshotId.Should().Be("snap-123");
snapshot.ImageDigest.Should().Be("sha256:abc123");
snapshot.PolicyVersion.Should().Be("v1.0.0");
}
[Fact]
public void BundleInfo_CanBeCreated()
{
// Arrange & Act
var bundle = new BundleInfo(
SnapshotId: "snap-123",
BundlePath: "/tmp/bundle",
BundleHash: "sha256:bundlehash",
PolicyVersion: "v1.0.0",
SizeBytes: 1024);
// Assert
bundle.SnapshotId.Should().Be("snap-123");
bundle.BundlePath.Should().Be("/tmp/bundle");
bundle.BundleHash.Should().Be("sha256:bundlehash");
bundle.SizeBytes.Should().Be(1024);
}
#endregion
#region Helper Methods
private string CreateTestBundle(string bundleId = "test-bundle-001")
{
var bundlePath = Path.Combine(_testDir, bundleId);
Directory.CreateDirectory(bundlePath);
Directory.CreateDirectory(Path.Combine(bundlePath, "inputs"));
Directory.CreateDirectory(Path.Combine(bundlePath, "outputs"));
// Create SBOM
var sbomPath = Path.Combine(bundlePath, "inputs", "sbom.json");
var sbomContent = """
{
"bomFormat": "CycloneDX",
"specVersion": "1.6",
"version": 1,
"components": []
}
""";
File.WriteAllText(sbomPath, sbomContent, Encoding.UTF8);
// Calculate SBOM hash
using var sha256 = System.Security.Cryptography.SHA256.Create();
var sbomBytes = Encoding.UTF8.GetBytes(sbomContent);
var sbomHash = Convert.ToHexString(sha256.ComputeHash(sbomBytes)).ToLowerInvariant();
// Create verdict output
var verdictPath = Path.Combine(bundlePath, "outputs", "verdict.json");
var verdictContent = """
{
"decision": "pass",
"score": 0.95,
"findings": []
}
""";
File.WriteAllText(verdictPath, verdictContent, Encoding.UTF8);
var verdictBytes = Encoding.UTF8.GetBytes(verdictContent);
var verdictHash = Convert.ToHexString(sha256.ComputeHash(verdictBytes)).ToLowerInvariant();
// Create manifest
var manifest = new
{
schemaVersion = "2.0.0",
bundleId = bundleId,
createdAt = DateTimeOffset.UtcNow.ToString("O"),
scan = new
{
id = "scan-001",
imageDigest = "sha256:testimage123",
policyDigest = "sha256:policy123",
scorePolicyDigest = "sha256:scorepolicy123",
feedSnapshotDigest = "sha256:feeds123",
toolchain = "stellaops-1.0.0",
analyzerSetDigest = "sha256:analyzers123"
},
inputs = new
{
sbom = new { path = "inputs/sbom.json", sha256 = sbomHash }
},
expectedOutputs = new
{
verdict = new { path = "outputs/verdict.json", sha256 = verdictHash },
verdictHash = $"cgs:sha256:{verdictHash}"
}
};
var manifestPath = Path.Combine(bundlePath, "manifest.json");
File.WriteAllText(manifestPath, JsonSerializer.Serialize(manifest, new JsonSerializerOptions { WriteIndented = true }));
return bundlePath;
}
#endregion
}

View File

@@ -0,0 +1,257 @@
// -----------------------------------------------------------------------------
// VexGateCommandTests.cs
// Sprint: SPRINT_20260106_003_002_SCANNER_vex_gate_service
// Task: T029 - CLI integration tests
// Description: Unit tests for VEX gate CLI commands
// -----------------------------------------------------------------------------
using System.CommandLine;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using StellaOps.Cli.Commands;
using StellaOps.Cli.Configuration;
using StellaOps.TestKit;
using Xunit;
namespace StellaOps.Cli.Tests.Commands;
/// <summary>
/// Unit tests for VEX gate CLI commands under the scan command.
/// </summary>
[Trait("Category", TestCategories.Unit)]
public class VexGateCommandTests
{
private readonly IServiceProvider _services;
private readonly StellaOpsCliOptions _options;
private readonly Option<bool> _verboseOption;
public VexGateCommandTests()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton<ILogger<VexGateCommandTests>>(NullLogger<VexGateCommandTests>.Instance);
_services = serviceCollection.BuildServiceProvider();
_options = new StellaOpsCliOptions
{
BackendUrl = "http://localhost:5070",
};
_verboseOption = new Option<bool>("--verbose", "-v") { Description = "Enable verbose output" };
}
#region gate-policy Command Tests
[Fact]
public void BuildVexGateCommand_CreatesGatePolicyCommandTree()
{
// Act
var command = VexGateScanCommandGroup.BuildVexGateCommand(
_services, _options, _verboseOption, CancellationToken.None);
// Assert
Assert.Equal("gate-policy", command.Name);
Assert.Contains("VEX gate policy", command.Description);
}
[Fact]
public void BuildVexGateCommand_HasShowSubcommand()
{
// Act
var command = VexGateScanCommandGroup.BuildVexGateCommand(
_services, _options, _verboseOption, CancellationToken.None);
var showCommand = command.Subcommands.FirstOrDefault(c => c.Name == "show");
// Assert
Assert.NotNull(showCommand);
Assert.Contains("policy", showCommand.Description, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public void ShowCommand_HasTenantOption()
{
// Arrange
var command = VexGateScanCommandGroup.BuildVexGateCommand(
_services, _options, _verboseOption, CancellationToken.None);
var showCommand = command.Subcommands.First(c => c.Name == "show");
// Act - look for tenant option by -t alias
var tenantOption = showCommand.Options.FirstOrDefault(o =>
o.Aliases.Contains("-t"));
// Assert
Assert.NotNull(tenantOption);
}
[Fact]
public void ShowCommand_HasOutputOption()
{
// Arrange
var command = VexGateScanCommandGroup.BuildVexGateCommand(
_services, _options, _verboseOption, CancellationToken.None);
var showCommand = command.Subcommands.First(c => c.Name == "show");
// Act
var outputOption = showCommand.Options.FirstOrDefault(o =>
o.Aliases.Contains("--output") || o.Aliases.Contains("-o"));
// Assert
Assert.NotNull(outputOption);
}
#endregion
#region gate-results Command Tests
[Fact]
public void BuildGateResultsCommand_CreatesGateResultsCommand()
{
// Act
var command = VexGateScanCommandGroup.BuildGateResultsCommand(
_services, _options, _verboseOption, CancellationToken.None);
// Assert
Assert.Equal("gate-results", command.Name);
Assert.Contains("gate results", command.Description, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public void GateResultsCommand_HasScanIdOption()
{
// Arrange
var command = VexGateScanCommandGroup.BuildGateResultsCommand(
_services, _options, _verboseOption, CancellationToken.None);
// Act
var scanIdOption = command.Options.FirstOrDefault(o =>
o.Aliases.Contains("--scan-id") || o.Aliases.Contains("-s"));
// Assert
Assert.NotNull(scanIdOption);
}
[Fact]
public void GateResultsCommand_ScanIdIsRequired()
{
// Arrange
var command = VexGateScanCommandGroup.BuildGateResultsCommand(
_services, _options, _verboseOption, CancellationToken.None);
// Act
var scanIdOption = command.Options.First(o =>
o.Aliases.Contains("--scan-id") || o.Aliases.Contains("-s"));
// Assert - Check via arity (required options have min arity of 1)
Assert.Equal(1, scanIdOption.Arity.MinimumNumberOfValues);
}
[Fact]
public void GateResultsCommand_HasDecisionFilterOption()
{
// Arrange
var command = VexGateScanCommandGroup.BuildGateResultsCommand(
_services, _options, _verboseOption, CancellationToken.None);
// Act
var decisionOption = command.Options.FirstOrDefault(o =>
o.Aliases.Contains("--decision") || o.Aliases.Contains("-d"));
// Assert
Assert.NotNull(decisionOption);
Assert.Contains("Pass", decisionOption.Description);
Assert.Contains("Warn", decisionOption.Description);
Assert.Contains("Block", decisionOption.Description);
}
[Fact]
public void GateResultsCommand_HasOutputOption()
{
// Arrange
var command = VexGateScanCommandGroup.BuildGateResultsCommand(
_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 GateResultsCommand_HasLimitOption()
{
// Arrange
var command = VexGateScanCommandGroup.BuildGateResultsCommand(
_services, _options, _verboseOption, CancellationToken.None);
// Act - look for limit option by -l alias
var limitOption = command.Options.FirstOrDefault(o =>
o.Aliases.Contains("-l"));
// Assert
Assert.NotNull(limitOption);
}
#endregion
#region Command Structure Tests
[Fact]
public void GatePolicyCommand_ShouldBeAddableToParentCommand()
{
// Arrange
var scanCommand = new Command("scan", "Scanner operations");
var gatePolicyCommand = VexGateScanCommandGroup.BuildVexGateCommand(
_services, _options, _verboseOption, CancellationToken.None);
// Act
scanCommand.Add(gatePolicyCommand);
// Assert
Assert.Contains(scanCommand.Subcommands, c => c.Name == "gate-policy");
}
[Fact]
public void GateResultsCommand_ShouldBeAddableToParentCommand()
{
// Arrange
var scanCommand = new Command("scan", "Scanner operations");
var gateResultsCommand = VexGateScanCommandGroup.BuildGateResultsCommand(
_services, _options, _verboseOption, CancellationToken.None);
// Act
scanCommand.Add(gateResultsCommand);
// Assert
Assert.Contains(scanCommand.Subcommands, c => c.Name == "gate-results");
}
[Fact]
public void GatePolicyCommand_HasHandler()
{
// Arrange
var command = VexGateScanCommandGroup.BuildVexGateCommand(
_services, _options, _verboseOption, CancellationToken.None);
var showCommand = command.Subcommands.First(c => c.Name == "show");
// Assert - Handler is set via SetHandler in BuildGatePolicyShowCommand
Assert.NotNull(showCommand);
}
[Fact]
public void GateResultsCommand_HasHandler()
{
// Arrange
var command = VexGateScanCommandGroup.BuildGateResultsCommand(
_services, _options, _verboseOption, CancellationToken.None);
// Assert - Handler is set via SetHandler
Assert.NotNull(command);
}
#endregion
}