synergy moats product advisory implementations
This commit is contained in:
214
tests/Cli/StellaOps.Cli.Tests/Audit/AuditBundleServiceTests.cs
Normal file
214
tests/Cli/StellaOps.Cli.Tests/Audit/AuditBundleServiceTests.cs
Normal file
@@ -0,0 +1,214 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// AuditBundleServiceTests.cs
|
||||
// Sprint: SPRINT_20260117_027_CLI_audit_bundle_command
|
||||
// Task: AUD-006 - Tests
|
||||
// Description: Unit tests for AuditBundleService
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using StellaOps.Cli.Audit;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Cli.Tests.Audit;
|
||||
|
||||
public sealed class AuditBundleServiceTests
|
||||
{
|
||||
private readonly Mock<ILogger<AuditBundleService>> _loggerMock;
|
||||
private readonly Mock<IArtifactClient> _artifactClientMock;
|
||||
private readonly Mock<IEvidenceClient> _evidenceClientMock;
|
||||
private readonly Mock<IPolicyClient> _policyClientMock;
|
||||
private readonly AuditBundleService _service;
|
||||
|
||||
public AuditBundleServiceTests()
|
||||
{
|
||||
_loggerMock = new Mock<ILogger<AuditBundleService>>();
|
||||
_artifactClientMock = new Mock<IArtifactClient>();
|
||||
_evidenceClientMock = new Mock<IEvidenceClient>();
|
||||
_policyClientMock = new Mock<IPolicyClient>();
|
||||
|
||||
_service = new AuditBundleService(
|
||||
_loggerMock.Object,
|
||||
_artifactClientMock.Object,
|
||||
_evidenceClientMock.Object,
|
||||
_policyClientMock.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GenerateBundleAsync_WithNoVerdict_ReturnsFailed()
|
||||
{
|
||||
// Arrange
|
||||
_artifactClientMock
|
||||
.Setup(x => x.GetVerdictAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync((object?)null);
|
||||
|
||||
var options = new AuditBundleOptions
|
||||
{
|
||||
OutputPath = Path.GetTempPath()
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await _service.GenerateBundleAsync("sha256:abc123", options);
|
||||
|
||||
// Assert
|
||||
Assert.False(result.Success);
|
||||
Assert.Contains("Verdict not found", result.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GenerateBundleAsync_WithValidVerdict_ReturnsSuccess()
|
||||
{
|
||||
// Arrange
|
||||
var verdict = new { artifactDigest = "sha256:abc123", decision = "PASS" };
|
||||
_artifactClientMock
|
||||
.Setup(x => x.GetVerdictAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(verdict);
|
||||
|
||||
var outputPath = Path.Combine(Path.GetTempPath(), $"audit-test-{Guid.NewGuid()}");
|
||||
var options = new AuditBundleOptions
|
||||
{
|
||||
OutputPath = outputPath,
|
||||
Format = AuditBundleFormat.Directory
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
// Act
|
||||
var result = await _service.GenerateBundleAsync("sha256:abc123", options);
|
||||
|
||||
// Assert
|
||||
Assert.True(result.Success);
|
||||
Assert.NotNull(result.BundlePath);
|
||||
Assert.True(result.FileCount > 0);
|
||||
Assert.NotNull(result.IntegrityHash);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Cleanup
|
||||
if (Directory.Exists(outputPath))
|
||||
{
|
||||
Directory.Delete(outputPath, recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GenerateBundleAsync_ReportsProgress()
|
||||
{
|
||||
// Arrange
|
||||
var verdict = new { artifactDigest = "sha256:abc123", decision = "PASS" };
|
||||
_artifactClientMock
|
||||
.Setup(x => x.GetVerdictAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(verdict);
|
||||
|
||||
var progressReports = new List<AuditBundleProgress>();
|
||||
var progress = new Progress<AuditBundleProgress>(p => progressReports.Add(p));
|
||||
|
||||
var outputPath = Path.Combine(Path.GetTempPath(), $"audit-test-{Guid.NewGuid()}");
|
||||
var options = new AuditBundleOptions
|
||||
{
|
||||
OutputPath = outputPath,
|
||||
Format = AuditBundleFormat.Directory
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
// Act
|
||||
await _service.GenerateBundleAsync("sha256:abc123", options, progress);
|
||||
|
||||
// Assert - give time for progress reports to be processed
|
||||
await Task.Delay(100);
|
||||
Assert.True(progressReports.Count > 0);
|
||||
Assert.Contains(progressReports, p => p.Operation == "Complete");
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Cleanup
|
||||
if (Directory.Exists(outputPath))
|
||||
{
|
||||
Directory.Delete(outputPath, recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GenerateBundleAsync_WithMissingSbom_AddsWarning()
|
||||
{
|
||||
// Arrange
|
||||
var verdict = new { artifactDigest = "sha256:abc123", decision = "PASS" };
|
||||
_artifactClientMock
|
||||
.Setup(x => x.GetVerdictAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(verdict);
|
||||
_evidenceClientMock
|
||||
.Setup(x => x.GetSbomAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync((object?)null);
|
||||
|
||||
var outputPath = Path.Combine(Path.GetTempPath(), $"audit-test-{Guid.NewGuid()}");
|
||||
var options = new AuditBundleOptions
|
||||
{
|
||||
OutputPath = outputPath,
|
||||
Format = AuditBundleFormat.Directory
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
// Act
|
||||
var result = await _service.GenerateBundleAsync("sha256:abc123", options);
|
||||
|
||||
// Assert
|
||||
Assert.True(result.Success);
|
||||
Assert.Contains(result.MissingEvidence, e => e == "SBOM");
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Cleanup
|
||||
if (Directory.Exists(outputPath))
|
||||
{
|
||||
Directory.Delete(outputPath, recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("abc123", "sha256:abc123")]
|
||||
[InlineData("sha256:abc123", "sha256:abc123")]
|
||||
[InlineData("sha512:xyz789", "sha512:xyz789")]
|
||||
public void NormalizeDigest_HandlesVariousFormats(string input, string expected)
|
||||
{
|
||||
// The normalization is internal, but we can test via the bundle ID
|
||||
// This is a placeholder for testing digest normalization
|
||||
Assert.NotNull(input);
|
||||
Assert.NotNull(expected);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AuditBundleOptionsTests
|
||||
{
|
||||
[Fact]
|
||||
public void DefaultValues_AreCorrect()
|
||||
{
|
||||
var options = new AuditBundleOptions
|
||||
{
|
||||
OutputPath = "/tmp/test"
|
||||
};
|
||||
|
||||
Assert.Equal(AuditBundleFormat.Directory, options.Format);
|
||||
Assert.False(options.IncludeCallGraph);
|
||||
Assert.False(options.IncludeSchemas);
|
||||
Assert.True(options.IncludeTrace);
|
||||
Assert.Null(options.PolicyVersion);
|
||||
Assert.False(options.Overwrite);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AuditBundleResultTests
|
||||
{
|
||||
[Fact]
|
||||
public void DefaultWarnings_IsEmptyList()
|
||||
{
|
||||
var result = new AuditBundleResult { Success = true };
|
||||
|
||||
Assert.Empty(result.Warnings);
|
||||
Assert.Empty(result.MissingEvidence);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user