270 lines
9.1 KiB
C#
270 lines
9.1 KiB
C#
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
// Sprint: SPRINT_4100_0006_0001 - Crypto Plugin CLI Architecture
|
|
// Task: T11 - Integration tests for crypto commands
|
|
|
|
using System.CommandLine;
|
|
using System.CommandLine.Parsing;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Spectre.Console;
|
|
using Spectre.Console.Testing;
|
|
using Xunit;
|
|
using StellaOps.Cli.Commands;
|
|
using StellaOps.Cryptography;
|
|
|
|
using StellaOps.TestKit;
|
|
namespace StellaOps.Cli.Tests;
|
|
|
|
/// <summary>
|
|
/// Integration tests for crypto command group (sign, verify, profiles).
|
|
/// Tests regional crypto plugin architecture with build-time distribution selection.
|
|
/// </summary>
|
|
public class CryptoCommandTests
|
|
{
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void CryptoCommand_ShouldHaveExpectedSubcommands()
|
|
{
|
|
// Arrange
|
|
var services = new ServiceCollection();
|
|
services.AddLogging();
|
|
var serviceProvider = services.BuildServiceProvider();
|
|
var verboseOption = new Option<bool>("--verbose");
|
|
var cancellationToken = CancellationToken.None;
|
|
|
|
// Act
|
|
var command = CryptoCommandGroup.BuildCryptoCommand(serviceProvider, verboseOption, cancellationToken);
|
|
|
|
// Assert
|
|
Assert.NotNull(command);
|
|
Assert.Equal("crypto", command.Name);
|
|
Assert.Contains(command.Children, c => c.Name == "sign");
|
|
Assert.Contains(command.Children, c => c.Name == "verify");
|
|
Assert.Contains(command.Children, c => c.Name == "profiles");
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void CryptoSignCommand_ShouldRequireInputOption()
|
|
{
|
|
// Arrange
|
|
var services = new ServiceCollection();
|
|
services.AddLogging();
|
|
var serviceProvider = services.BuildServiceProvider();
|
|
var verboseOption = new Option<bool>("--verbose");
|
|
var cancellationToken = CancellationToken.None;
|
|
|
|
var command = CryptoCommandGroup.BuildCryptoCommand(serviceProvider, verboseOption, cancellationToken);
|
|
var signCommand = command.Children.OfType<Command>().First(c => c.Name == "sign");
|
|
|
|
// Act
|
|
var result = signCommand.Parse("");
|
|
|
|
// Assert
|
|
Assert.NotEmpty(result.Errors);
|
|
Assert.Contains(result.Errors, e => e.Message.Contains("--input"));
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void CryptoVerifyCommand_ShouldRequireInputOption()
|
|
{
|
|
// Arrange
|
|
var services = new ServiceCollection();
|
|
services.AddLogging();
|
|
var serviceProvider = services.BuildServiceProvider();
|
|
var verboseOption = new Option<bool>("--verbose");
|
|
var cancellationToken = CancellationToken.None;
|
|
|
|
var command = CryptoCommandGroup.BuildCryptoCommand(serviceProvider, verboseOption, cancellationToken);
|
|
var verifyCommand = command.Children.OfType<Command>().First(c => c.Name == "verify");
|
|
|
|
// Act
|
|
var result = verifyCommand.Parse("");
|
|
|
|
// Assert
|
|
Assert.NotEmpty(result.Errors);
|
|
Assert.Contains(result.Errors, e => e.Message.Contains("--input"));
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void CryptoProfilesCommand_ShouldAcceptDetailsOption()
|
|
{
|
|
// Arrange
|
|
var services = new ServiceCollection();
|
|
services.AddLogging();
|
|
var serviceProvider = services.BuildServiceProvider();
|
|
var verboseOption = new Option<bool>("--verbose");
|
|
var cancellationToken = CancellationToken.None;
|
|
|
|
var command = CryptoCommandGroup.BuildCryptoCommand(serviceProvider, verboseOption, cancellationToken);
|
|
var profilesCommand = command.Children.OfType<Command>().First(c => c.Name == "profiles");
|
|
|
|
// Act
|
|
var result = profilesCommand.Parse("--details");
|
|
|
|
// Assert
|
|
Assert.Empty(result.Errors);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public async Task CryptoSignCommand_WithMissingFile_ShouldReturnError()
|
|
{
|
|
// Arrange
|
|
var services = new ServiceCollection();
|
|
services.AddLogging();
|
|
|
|
// Add a stub crypto provider
|
|
services.AddSingleton<ICryptoProvider, StubCryptoProvider>();
|
|
|
|
var serviceProvider = services.BuildServiceProvider();
|
|
var verboseOption = new Option<bool>("--verbose");
|
|
var cancellationToken = CancellationToken.None;
|
|
|
|
var command = CryptoCommandGroup.BuildCryptoCommand(serviceProvider, verboseOption, cancellationToken);
|
|
|
|
// Act
|
|
var console = new TestConsole();
|
|
var originalConsole = AnsiConsole.Console;
|
|
int exitCode;
|
|
try
|
|
{
|
|
AnsiConsole.Console = console;
|
|
exitCode = await command.Parse("sign --input /nonexistent/file.txt").InvokeAsync(cancellationToken);
|
|
}
|
|
finally
|
|
{
|
|
AnsiConsole.Console = originalConsole;
|
|
}
|
|
|
|
// Assert
|
|
Assert.NotEqual(0, exitCode);
|
|
var output = console.Output.ToString();
|
|
Assert.Contains("not found", output, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public async Task CryptoProfilesCommand_WithNoCryptoProviders_ShouldReturnError()
|
|
{
|
|
// Arrange
|
|
var services = new ServiceCollection();
|
|
services.AddLogging();
|
|
// Intentionally not adding any crypto providers
|
|
|
|
var serviceProvider = services.BuildServiceProvider();
|
|
var verboseOption = new Option<bool>("--verbose");
|
|
var cancellationToken = CancellationToken.None;
|
|
|
|
var command = CryptoCommandGroup.BuildCryptoCommand(serviceProvider, verboseOption, cancellationToken);
|
|
|
|
// Act
|
|
var console = new TestConsole();
|
|
var originalConsole = AnsiConsole.Console;
|
|
int exitCode;
|
|
try
|
|
{
|
|
AnsiConsole.Console = console;
|
|
exitCode = await command.Parse("profiles").InvokeAsync(cancellationToken);
|
|
}
|
|
finally
|
|
{
|
|
AnsiConsole.Console = originalConsole;
|
|
}
|
|
|
|
// Assert
|
|
Assert.NotEqual(0, exitCode);
|
|
var output = console.Output.ToString();
|
|
Assert.Contains("No crypto providers available", output, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public async Task CryptoProfilesCommand_WithCryptoProviders_ShouldListThem()
|
|
{
|
|
// Arrange
|
|
var services = new ServiceCollection();
|
|
services.AddLogging();
|
|
services.AddSingleton<ICryptoProvider, StubCryptoProvider>();
|
|
|
|
var serviceProvider = services.BuildServiceProvider();
|
|
var verboseOption = new Option<bool>("--verbose");
|
|
var cancellationToken = CancellationToken.None;
|
|
|
|
var command = CryptoCommandGroup.BuildCryptoCommand(serviceProvider, verboseOption, cancellationToken);
|
|
|
|
// Act
|
|
var console = new TestConsole();
|
|
var originalConsole = AnsiConsole.Console;
|
|
int exitCode;
|
|
try
|
|
{
|
|
AnsiConsole.Console = console;
|
|
exitCode = await command.Parse("profiles").InvokeAsync(cancellationToken);
|
|
}
|
|
finally
|
|
{
|
|
AnsiConsole.Console = originalConsole;
|
|
}
|
|
|
|
// Assert
|
|
Assert.Equal(0, exitCode);
|
|
var output = console.Output.ToString();
|
|
Assert.Contains("StubCryptoProvider", output);
|
|
}
|
|
|
|
#if STELLAOPS_ENABLE_GOST
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void WithGostEnabled_ShouldShowGostInDistributionInfo()
|
|
{
|
|
// This test only runs when GOST is enabled at build time
|
|
// Verifies distribution-specific preprocessor directives work correctly
|
|
Assert.True(true, "GOST distribution is enabled");
|
|
}
|
|
#endif
|
|
|
|
#if STELLAOPS_ENABLE_EIDAS
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void WithEidasEnabled_ShouldShowEidasInDistributionInfo()
|
|
{
|
|
// This test only runs when eIDAS is enabled at build time
|
|
Assert.True(true, "eIDAS distribution is enabled");
|
|
}
|
|
#endif
|
|
|
|
#if STELLAOPS_ENABLE_SM
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void WithSmEnabled_ShouldShowSmInDistributionInfo()
|
|
{
|
|
// This test only runs when SM is enabled at build time
|
|
Assert.True(true, "SM distribution is enabled");
|
|
}
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Stub crypto provider for testing.
|
|
/// </summary>
|
|
private class StubCryptoProvider : ICryptoProvider
|
|
{
|
|
public string Name => "StubCryptoProvider";
|
|
|
|
public bool Supports(CryptoCapability capability, string algorithmId) => true;
|
|
|
|
public IPasswordHasher GetPasswordHasher(string algorithmId) => throw new NotSupportedException();
|
|
|
|
public ICryptoHasher GetHasher(string algorithmId) => throw new NotSupportedException();
|
|
|
|
public ICryptoSigner GetSigner(string algorithmId, CryptoKeyReference keyReference) => throw new NotSupportedException();
|
|
|
|
public void UpsertSigningKey(CryptoSigningKey signingKey) => throw new NotSupportedException();
|
|
|
|
public bool RemoveSigningKey(string keyId) => false;
|
|
|
|
public IReadOnlyCollection<CryptoSigningKey> GetSigningKeys() => Array.Empty<CryptoSigningKey>();
|
|
}
|
|
}
|