Files
git.stella-ops.org/src/EvidenceLocker/StellaOps.EvidenceLocker/StellaOps.EvidenceLocker.Tests/GoldenFixturesTests.cs
2026-01-24 00:12:43 +02:00

87 lines
3.5 KiB
C#

using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.EvidenceLocker.Tests;
/// <summary>
/// Golden fixture tests for evidence bundle integrity verification.
/// These tests verify that checksum/hash computation logic works correctly.
/// </summary>
public sealed class GoldenFixturesTests
{
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);
[Trait("Category", TestCategories.Unit)]
[Fact]
public void SealedBundle_ComputedHashMatchesRoot()
{
// Arrange - Create a minimal bundle structure
var entries = new[]
{
new { canonicalPath = "artifacts/sbom.json", sha256 = "a5b8e9c4f3d2e1b0a7c6d5e4f3c2b1a0e9d8c7b6a5f4e3d2c1b0a9f8e7d6c5b4" },
new { canonicalPath = "artifacts/provenance.json", sha256 = "b6c9d0e5f4a3b2c1d0e9f8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3d2e1f0a9b8c7" }
};
// Act - Compute the merkle root by hashing entries
var entryHashes = entries.Select(e => e.sha256).OrderBy(h => h).ToArray();
var concatenated = string.Join("", entryHashes);
var merkleRoot = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(concatenated))).ToLowerInvariant();
// Assert - Should be able to verify the root was computed from entries
Assert.NotEmpty(merkleRoot);
Assert.Equal(64, merkleRoot.Length); // SHA256 produces 64 hex chars
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void PortableBundle_RedactionRemovesTenantInfo()
{
// Arrange - Create a bundle with tenant info
var originalBundle = JsonSerializer.Serialize(new
{
bundleId = "test-bundle",
tenantId = "secret-tenant-123",
tenantName = "Acme Corp",
data = new { value = "public" }
}, JsonOptions);
// Act - Simulate redaction by removing tenant fields
using var doc = JsonDocument.Parse(originalBundle);
var redactedData = new Dictionary<string, object?>
{
["bundleId"] = doc.RootElement.GetProperty("bundleId").GetString(),
["data"] = new { value = "public" }
};
var redactedBundle = JsonSerializer.Serialize(redactedData, JsonOptions);
// Assert - Redacted bundle should not contain tenant info
Assert.Contains("bundleId", redactedBundle);
Assert.DoesNotContain("tenant", redactedBundle, StringComparison.OrdinalIgnoreCase);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ReplayRecord_DigestMatchesContent()
{
// Arrange - Create sample replay record
var replayContent = "{\"eventId\":\"evt-001\",\"timestamp\":\"2026-01-22T12:00:00Z\",\"action\":\"promote\"}\n"
+ "{\"eventId\":\"evt-002\",\"timestamp\":\"2026-01-22T12:01:00Z\",\"action\":\"approve\"}\n";
var contentBytes = Encoding.UTF8.GetBytes(replayContent);
// Act - Compute digest
var computedHash = "sha256:" + Convert.ToHexString(SHA256.HashData(contentBytes)).ToLowerInvariant();
// Assert - Digest should match expected format
Assert.StartsWith("sha256:", computedHash);
Assert.Equal(71, computedHash.Length); // "sha256:" (7) + 64 hex chars
// Verify digest is deterministic
var recomputedHash = "sha256:" + Convert.ToHexString(SHA256.HashData(contentBytes)).ToLowerInvariant();
Assert.Equal(computedHash, recomputedHash);
}
}