Files
git.stella-ops.org/src/__Libraries/__Tests/StellaOps.AuditPack.Tests/AuditBundleWriterTests.cs
StellaOps Bot 56e2dc01ee Add unit tests for AST parsing and security sink detection
- Created `StellaOps.AuditPack.Tests.csproj` for unit testing the AuditPack library.
- Implemented comprehensive unit tests in `index.test.js` for AST parsing, covering various JavaScript and TypeScript constructs including functions, classes, decorators, and JSX.
- Added `sink-detect.test.js` to test security sink detection patterns, validating command injection, SQL injection, file write, deserialization, SSRF, NoSQL injection, and more.
- Included tests for taint source detection in various contexts such as Express, Koa, and AWS Lambda.
2025-12-23 09:23:42 +02:00

277 lines
7.8 KiB
C#

// -----------------------------------------------------------------------------
// AuditBundleWriterTests.cs
// Sprint: SPRINT_4300_0001_0002 (One-Command Audit Replay CLI)
// Description: Unit tests for AuditBundleWriter.
// -----------------------------------------------------------------------------
using System.Text;
using System.Text.Json;
using StellaOps.AuditPack.Services;
namespace StellaOps.AuditPack.Tests;
public class AuditBundleWriterTests : IDisposable
{
private readonly string _tempDir;
public AuditBundleWriterTests()
{
_tempDir = Path.Combine(Path.GetTempPath(), $"audit-test-{Guid.NewGuid():N}");
Directory.CreateDirectory(_tempDir);
}
public void Dispose()
{
if (Directory.Exists(_tempDir))
{
Directory.Delete(_tempDir, recursive: true);
}
}
[Fact]
public async Task WriteAsync_CreatesValidBundle()
{
// Arrange
var writer = new AuditBundleWriter();
var outputPath = Path.Combine(_tempDir, "test-bundle.tar.gz");
var request = CreateValidRequest(outputPath);
// Act
var result = await writer.WriteAsync(request);
// Assert
Assert.True(result.Success, result.Error);
Assert.True(File.Exists(outputPath));
Assert.NotNull(result.BundleId);
Assert.NotNull(result.MerkleRoot);
Assert.NotNull(result.BundleDigest);
Assert.True(result.TotalSizeBytes > 0);
Assert.True(result.FileCount > 0);
}
[Fact]
public async Task WriteAsync_ComputesMerkleRoot()
{
// Arrange
var writer = new AuditBundleWriter();
var outputPath = Path.Combine(_tempDir, "merkle-test.tar.gz");
var request = CreateValidRequest(outputPath);
// Act
var result = await writer.WriteAsync(request);
// Assert
Assert.True(result.Success);
Assert.NotNull(result.MerkleRoot);
Assert.StartsWith("sha256:", result.MerkleRoot);
Assert.Equal(71, result.MerkleRoot.Length); // sha256: + 64 hex chars
}
[Fact]
public async Task WriteAsync_SignsManifest_WhenSignIsTrue()
{
// Arrange
var writer = new AuditBundleWriter();
var outputPath = Path.Combine(_tempDir, "signed-test.tar.gz");
var request = CreateValidRequest(outputPath) with { Sign = true };
// Act
var result = await writer.WriteAsync(request);
// Assert
Assert.True(result.Success);
Assert.True(result.Signed);
Assert.NotNull(result.SigningKeyId);
Assert.NotNull(result.SigningAlgorithm);
}
[Fact]
public async Task WriteAsync_DoesNotSign_WhenSignIsFalse()
{
// Arrange
var writer = new AuditBundleWriter();
var outputPath = Path.Combine(_tempDir, "unsigned-test.tar.gz");
var request = CreateValidRequest(outputPath) with { Sign = false };
// Act
var result = await writer.WriteAsync(request);
// Assert
Assert.True(result.Success);
Assert.False(result.Signed);
Assert.Null(result.SigningKeyId);
}
[Fact]
public async Task WriteAsync_FailsWithoutSbom()
{
// Arrange
var writer = new AuditBundleWriter();
var outputPath = Path.Combine(_tempDir, "no-sbom.tar.gz");
var request = new AuditBundleWriteRequest
{
OutputPath = outputPath,
ScanId = "scan-001",
ImageRef = "test:latest",
ImageDigest = "sha256:abc123",
Decision = "pass",
Sbom = null!,
FeedsSnapshot = CreateFeedsSnapshot(),
PolicyBundle = CreatePolicyBundle(),
Verdict = CreateVerdict()
};
// Act
var result = await writer.WriteAsync(request);
// Assert
Assert.False(result.Success);
Assert.Contains("SBOM", result.Error);
}
[Fact]
public async Task WriteAsync_IncludesOptionalVex()
{
// Arrange
var writer = new AuditBundleWriter();
var outputPath = Path.Combine(_tempDir, "with-vex.tar.gz");
var vexContent = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(new
{
type = "https://openvex.dev/ns/v0.2.0",
statements = new[]
{
new { vulnerability = "CVE-2024-1234", status = "not_affected" }
}
}));
var request = CreateValidRequest(outputPath) with
{
VexStatements = vexContent
};
// Act
var result = await writer.WriteAsync(request);
// Assert
Assert.True(result.Success);
Assert.True(result.FileCount >= 5); // sbom, feeds, policy, verdict, vex
}
[Fact]
public async Task WriteAsync_AddsTimeAnchor()
{
// Arrange
var writer = new AuditBundleWriter();
var outputPath = Path.Combine(_tempDir, "with-anchor.tar.gz");
var request = CreateValidRequest(outputPath) with
{
TimeAnchor = new TimeAnchorInput
{
Timestamp = DateTimeOffset.UtcNow,
Source = "local"
}
};
// Act
var result = await writer.WriteAsync(request);
// Assert
Assert.True(result.Success);
}
[Fact]
public async Task WriteAsync_DeterministicMerkleRoot()
{
// Arrange
var writer = new AuditBundleWriter();
var sbom = CreateSbom();
var feeds = CreateFeedsSnapshot();
var policy = CreatePolicyBundle();
var verdict = CreateVerdict();
var request1 = new AuditBundleWriteRequest
{
OutputPath = Path.Combine(_tempDir, "det-1.tar.gz"),
ScanId = "scan-001",
ImageRef = "test:latest",
ImageDigest = "sha256:abc123",
Decision = "pass",
Sbom = sbom,
FeedsSnapshot = feeds,
PolicyBundle = policy,
Verdict = verdict,
Sign = false
};
var request2 = request1 with
{
OutputPath = Path.Combine(_tempDir, "det-2.tar.gz")
};
// Act
var result1 = await writer.WriteAsync(request1);
var result2 = await writer.WriteAsync(request2);
// Assert
Assert.True(result1.Success);
Assert.True(result2.Success);
Assert.Equal(result1.MerkleRoot, result2.MerkleRoot);
}
private AuditBundleWriteRequest CreateValidRequest(string outputPath)
{
return new AuditBundleWriteRequest
{
OutputPath = outputPath,
ScanId = "scan-001",
ImageRef = "test:latest",
ImageDigest = "sha256:abc123def456",
Decision = "pass",
Sbom = CreateSbom(),
FeedsSnapshot = CreateFeedsSnapshot(),
PolicyBundle = CreatePolicyBundle(),
Verdict = CreateVerdict(),
Sign = true
};
}
private static byte[] CreateSbom()
{
return Encoding.UTF8.GetBytes(JsonSerializer.Serialize(new
{
bomFormat = "CycloneDX",
specVersion = "1.6",
version = 1,
components = Array.Empty<object>()
}));
}
private static byte[] CreateFeedsSnapshot()
{
return Encoding.UTF8.GetBytes("{\"type\":\"feed-snapshot\"}\n");
}
private static byte[] CreatePolicyBundle()
{
// Minimal gzip content
return new byte[] { 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
}
private static byte[] CreateVerdict()
{
return Encoding.UTF8.GetBytes(JsonSerializer.Serialize(new
{
decision = "pass",
evaluatedAt = DateTimeOffset.UtcNow
}));
}
}