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:
@@ -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
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user