Add integration tests for Proof Chain and Reachability workflows

- Implement ProofChainTestFixture for PostgreSQL-backed integration tests.
- Create StellaOps.Integration.ProofChain project with necessary dependencies.
- Add ReachabilityIntegrationTests to validate call graph extraction and reachability analysis.
- Introduce ReachabilityTestFixture for managing corpus and fixture paths.
- Establish StellaOps.Integration.Reachability project with required references.
- Develop UnknownsWorkflowTests to cover the unknowns lifecycle: detection, ranking, escalation, and resolution.
- Create StellaOps.Integration.Unknowns project with dependencies for unknowns workflow.
This commit is contained in:
StellaOps Bot
2025-12-20 22:19:26 +02:00
parent 3c6e14fca5
commit efe9bd8cfe
86 changed files with 9616 additions and 323 deletions

View File

@@ -0,0 +1,494 @@
// -----------------------------------------------------------------------------
// Sprint3500_0004_0001_CommandTests.cs
// Sprint: SPRINT_3500_0004_0001_cli_verbs
// Task: T6 - Unit Tests
// Description: Unit tests for CLI commands implemented in this sprint
// -----------------------------------------------------------------------------
using System.CommandLine;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Xunit;
using StellaOps.Cli.Commands;
using StellaOps.Cli.Commands.Proof;
namespace StellaOps.Cli.Tests.Commands;
/// <summary>
/// Unit tests for Sprint 3500.0004.0001 CLI commands.
/// </summary>
public class Sprint3500_0004_0001_CommandTests
{
private readonly IServiceProvider _services;
private readonly Option<bool> _verboseOption;
private readonly CancellationToken _cancellationToken;
public Sprint3500_0004_0001_CommandTests()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddLogging(builder => builder.AddProvider(NullLoggerProvider.Instance));
_services = serviceCollection.BuildServiceProvider();
_verboseOption = new Option<bool>("--verbose", "-v") { Description = "Verbose output" };
_cancellationToken = CancellationToken.None;
}
#region ScoreReplayCommandGroup Tests
[Fact]
public void ScoreCommand_CreatesCommandTree()
{
// Act
var command = ScoreReplayCommandGroup.BuildScoreCommand(_services, _verboseOption, _cancellationToken);
// Assert
Assert.Equal("score", command.Name);
Assert.Equal("Score computation and replay operations", command.Description);
}
[Fact]
public void ScoreCommand_HasReplaySubcommand()
{
// Act
var command = ScoreReplayCommandGroup.BuildScoreCommand(_services, _verboseOption, _cancellationToken);
var replayCommand = command.Subcommands.FirstOrDefault(c => c.Name == "replay");
// Assert
Assert.NotNull(replayCommand);
}
[Fact]
public void ScoreCommand_HasBundleSubcommand()
{
// Act
var command = ScoreReplayCommandGroup.BuildScoreCommand(_services, _verboseOption, _cancellationToken);
var bundleCommand = command.Subcommands.FirstOrDefault(c => c.Name == "bundle");
// Assert
Assert.NotNull(bundleCommand);
}
[Fact]
public void ScoreCommand_HasVerifySubcommand()
{
// Act
var command = ScoreReplayCommandGroup.BuildScoreCommand(_services, _verboseOption, _cancellationToken);
var verifyCommand = command.Subcommands.FirstOrDefault(c => c.Name == "verify");
// Assert
Assert.NotNull(verifyCommand);
}
[Fact]
public void ScoreReplay_ParsesWithScanOption()
{
// Arrange
var command = ScoreReplayCommandGroup.BuildScoreCommand(_services, _verboseOption, _cancellationToken);
var root = new RootCommand { command };
// Act
var result = root.Parse("score replay --scan test-scan-id");
// Assert
Assert.Empty(result.Errors);
}
[Fact]
public void ScoreReplay_ParsesWithOutputOption()
{
// Arrange
var command = ScoreReplayCommandGroup.BuildScoreCommand(_services, _verboseOption, _cancellationToken);
var root = new RootCommand { command };
// Act
var result = root.Parse("score replay --scan test-scan-id --output json");
// Assert
Assert.Empty(result.Errors);
}
[Fact]
public void ScoreReplay_RequiresScanOption()
{
// Arrange
var command = ScoreReplayCommandGroup.BuildScoreCommand(_services, _verboseOption, _cancellationToken);
var root = new RootCommand { command };
// Act
var result = root.Parse("score replay");
// Assert - should have error for missing required option
Assert.NotEmpty(result.Errors);
}
#endregion
#region UnknownsCommandGroup Tests
[Fact]
public void UnknownsCommand_CreatesCommandTree()
{
// Act
var command = UnknownsCommandGroup.BuildUnknownsCommand(_services, _verboseOption, _cancellationToken);
// Assert
Assert.Equal("unknowns", command.Name);
Assert.Contains("Unknowns registry", command.Description);
}
[Fact]
public void UnknownsCommand_HasListSubcommand()
{
// Act
var command = UnknownsCommandGroup.BuildUnknownsCommand(_services, _verboseOption, _cancellationToken);
var listCommand = command.Subcommands.FirstOrDefault(c => c.Name == "list");
// Assert
Assert.NotNull(listCommand);
}
[Fact]
public void UnknownsCommand_HasEscalateSubcommand()
{
// Act
var command = UnknownsCommandGroup.BuildUnknownsCommand(_services, _verboseOption, _cancellationToken);
var escalateCommand = command.Subcommands.FirstOrDefault(c => c.Name == "escalate");
// Assert
Assert.NotNull(escalateCommand);
}
[Fact]
public void UnknownsCommand_HasResolveSubcommand()
{
// Act
var command = UnknownsCommandGroup.BuildUnknownsCommand(_services, _verboseOption, _cancellationToken);
var resolveCommand = command.Subcommands.FirstOrDefault(c => c.Name == "resolve");
// Assert
Assert.NotNull(resolveCommand);
}
[Fact]
public void UnknownsList_ParsesWithBandOption()
{
// Arrange
var command = UnknownsCommandGroup.BuildUnknownsCommand(_services, _verboseOption, _cancellationToken);
var root = new RootCommand { command };
// Act
var result = root.Parse("unknowns list --band HOT");
// Assert
Assert.Empty(result.Errors);
}
[Fact]
public void UnknownsList_ParsesWithLimitOption()
{
// Arrange
var command = UnknownsCommandGroup.BuildUnknownsCommand(_services, _verboseOption, _cancellationToken);
var root = new RootCommand { command };
// Act
var result = root.Parse("unknowns list --limit 100");
// Assert
Assert.Empty(result.Errors);
}
[Fact]
public void UnknownsEscalate_RequiresIdOption()
{
// Arrange
var command = UnknownsCommandGroup.BuildUnknownsCommand(_services, _verboseOption, _cancellationToken);
var root = new RootCommand { command };
// Act
var result = root.Parse("unknowns escalate");
// Assert
Assert.NotEmpty(result.Errors);
}
#endregion
#region ScanGraphCommandGroup Tests
[Fact]
public void ScanGraphCommand_CreatesCommand()
{
// Act
var command = ScanGraphCommandGroup.BuildScanGraphCommand(_services, _verboseOption, _cancellationToken);
// Assert
Assert.Equal("graph", command.Name);
Assert.Contains("call graph", command.Description, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public void ScanGraph_HasLangOption()
{
// Arrange
var command = ScanGraphCommandGroup.BuildScanGraphCommand(_services, _verboseOption, _cancellationToken);
// Act
var langOption = command.Options.FirstOrDefault(o =>
o.Aliases.Contains("--lang") || o.Aliases.Contains("-l"));
// Assert
Assert.NotNull(langOption);
}
[Fact]
public void ScanGraph_HasTargetOption()
{
// Arrange
var command = ScanGraphCommandGroup.BuildScanGraphCommand(_services, _verboseOption, _cancellationToken);
// Act
var targetOption = command.Options.FirstOrDefault(o =>
o.Aliases.Contains("--target") || o.Aliases.Contains("-t"));
// Assert
Assert.NotNull(targetOption);
}
[Fact]
public void ScanGraph_HasOutputOption()
{
// Arrange
var command = ScanGraphCommandGroup.BuildScanGraphCommand(_services, _verboseOption, _cancellationToken);
// Act
var outputOption = command.Options.FirstOrDefault(o =>
o.Aliases.Contains("--output") || o.Aliases.Contains("-o"));
// Assert
Assert.NotNull(outputOption);
}
[Fact]
public void ScanGraph_HasUploadOption()
{
// Arrange
var command = ScanGraphCommandGroup.BuildScanGraphCommand(_services, _verboseOption, _cancellationToken);
// Act
var uploadOption = command.Options.FirstOrDefault(o =>
o.Aliases.Contains("--upload") || o.Aliases.Contains("-u"));
// Assert
Assert.NotNull(uploadOption);
}
[Fact]
public void ScanGraph_ParsesWithRequiredOptions()
{
// Arrange
var command = ScanGraphCommandGroup.BuildScanGraphCommand(_services, _verboseOption, _cancellationToken);
var root = new RootCommand { command };
// Act
var result = root.Parse("graph --lang dotnet --target ./src");
// Assert
Assert.Empty(result.Errors);
}
[Fact]
public void ScanGraph_RequiresLangOption()
{
// Arrange
var command = ScanGraphCommandGroup.BuildScanGraphCommand(_services, _verboseOption, _cancellationToken);
var root = new RootCommand { command };
// Act
var result = root.Parse("graph --target ./src");
// Assert
Assert.NotEmpty(result.Errors);
}
[Fact]
public void ScanGraph_RequiresTargetOption()
{
// Arrange
var command = ScanGraphCommandGroup.BuildScanGraphCommand(_services, _verboseOption, _cancellationToken);
var root = new RootCommand { command };
// Act
var result = root.Parse("graph --lang dotnet");
// Assert
Assert.NotEmpty(result.Errors);
}
#endregion
#region ProofCommandGroup Tests
[Fact]
public void ProofCommand_CreatesCommandTree()
{
// Act
var command = ProofCommandGroup.BuildProofCommand(_services, _verboseOption, _cancellationToken);
// Assert
Assert.Equal("proof", command.Name);
Assert.Contains("verification", command.Description, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public void ProofCommand_HasVerifySubcommand()
{
// Act
var command = ProofCommandGroup.BuildProofCommand(_services, _verboseOption, _cancellationToken);
var verifyCommand = command.Subcommands.FirstOrDefault(c => c.Name == "verify");
// Assert
Assert.NotNull(verifyCommand);
}
[Fact]
public void ProofCommand_HasSpineSubcommand()
{
// Act
var command = ProofCommandGroup.BuildProofCommand(_services, _verboseOption, _cancellationToken);
var spineCommand = command.Subcommands.FirstOrDefault(c => c.Name == "spine");
// Assert
Assert.NotNull(spineCommand);
}
[Fact]
public void ProofVerify_HasBundleOption()
{
// Arrange
var command = ProofCommandGroup.BuildProofCommand(_services, _verboseOption, _cancellationToken);
var verifyCommand = command.Subcommands.First(c => c.Name == "verify");
// Act
var bundleOption = verifyCommand.Options.FirstOrDefault(o =>
o.Aliases.Contains("--bundle") || o.Aliases.Contains("-b"));
// Assert
Assert.NotNull(bundleOption);
}
[Fact]
public void ProofVerify_HasOfflineOption()
{
// Arrange
var command = ProofCommandGroup.BuildProofCommand(_services, _verboseOption, _cancellationToken);
var verifyCommand = command.Subcommands.First(c => c.Name == "verify");
// Act
var offlineOption = verifyCommand.Options.FirstOrDefault(o =>
o.Name == "--offline" || o.Aliases.Contains("--offline"));
// Assert
Assert.NotNull(offlineOption);
}
[Fact]
public void ProofVerify_HasOutputOption()
{
// Arrange
var command = ProofCommandGroup.BuildProofCommand(_services, _verboseOption, _cancellationToken);
var verifyCommand = command.Subcommands.First(c => c.Name == "verify");
// Act
var outputOption = verifyCommand.Options.FirstOrDefault(o =>
o.Aliases.Contains("--output") || o.Aliases.Contains("-o"));
// Assert
Assert.NotNull(outputOption);
}
[Fact]
public void ProofVerify_ParsesWithBundleOption()
{
// Arrange
var command = ProofCommandGroup.BuildProofCommand(_services, _verboseOption, _cancellationToken);
var root = new RootCommand { command };
// Act
var result = root.Parse("proof verify --bundle ./bundle.tar.gz");
// Assert
Assert.Empty(result.Errors);
}
[Fact]
public void ProofVerify_ParsesWithOfflineOption()
{
// Arrange
var command = ProofCommandGroup.BuildProofCommand(_services, _verboseOption, _cancellationToken);
var root = new RootCommand { command };
// Act
var result = root.Parse("proof verify --bundle ./bundle.tar.gz --offline");
// Assert
Assert.Empty(result.Errors);
}
[Fact]
public void ProofVerify_ParsesWithJsonOutput()
{
// Arrange
var command = ProofCommandGroup.BuildProofCommand(_services, _verboseOption, _cancellationToken);
var root = new RootCommand { command };
// Act
var result = root.Parse("proof verify --bundle ./bundle.tar.gz --output json");
// Assert
Assert.Empty(result.Errors);
}
[Fact]
public void ProofVerify_RequiresBundleOption()
{
// Arrange
var command = ProofCommandGroup.BuildProofCommand(_services, _verboseOption, _cancellationToken);
var root = new RootCommand { command };
// Act
var result = root.Parse("proof verify");
// Assert
Assert.NotEmpty(result.Errors);
}
#endregion
#region Exit Codes Tests
[Theory]
[InlineData(0, "Success")]
[InlineData(1, "PolicyViolation")]
[InlineData(2, "SystemError")]
[InlineData(3, "VerificationFailed")]
[InlineData(8, "InputError")]
public void ProofExitCodes_HaveCorrectValues(int expectedCode, string codeName)
{
// Act
var actualCode = codeName switch
{
"Success" => ProofExitCodes.Success,
"PolicyViolation" => ProofExitCodes.PolicyViolation,
"SystemError" => ProofExitCodes.SystemError,
"VerificationFailed" => ProofExitCodes.VerificationFailed,
"InputError" => ProofExitCodes.InputError,
_ => throw new ArgumentException($"Unknown exit code: {codeName}")
};
// Assert
Assert.Equal(expectedCode, actualCode);
}
#endregion
}

View File

@@ -133,7 +133,7 @@ public class WitnessCommandGroupTests
// Act
var noColorOption = showCommand.Options.FirstOrDefault(o =>
o.Aliases.Contains("--no-color"));
o.Name == "--no-color" || o.Aliases.Contains("--no-color"));
// Assert
Assert.NotNull(noColorOption);
@@ -148,7 +148,7 @@ public class WitnessCommandGroupTests
// Act
var pathOnlyOption = showCommand.Options.FirstOrDefault(o =>
o.Aliases.Contains("--path-only"));
o.Name == "--path-only" || o.Aliases.Contains("--path-only"));
// Assert
Assert.NotNull(pathOnlyOption);
@@ -227,7 +227,7 @@ public class WitnessCommandGroupTests
// Act
var offlineOption = verifyCommand.Options.FirstOrDefault(o =>
o.Aliases.Contains("--offline"));
o.Name == "--offline" || o.Aliases.Contains("--offline"));
// Assert
Assert.NotNull(offlineOption);
@@ -276,7 +276,7 @@ public class WitnessCommandGroupTests
// Act
var reachableOption = listCommand.Options.FirstOrDefault(o =>
o.Aliases.Contains("--reachable-only"));
o.Name == "--reachable-only" || o.Aliases.Contains("--reachable-only"));
// Assert
Assert.NotNull(reachableOption);