update evidence bundle to include new evidence types and implement ProofSpine integration
Some checks failed
Lighthouse CI / Lighthouse Audit (push) Has been cancelled
Lighthouse CI / Axe Accessibility Audit (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
sm-remote-ci / build-and-test (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-12-15 09:15:30 +02:00
parent 8c8f0c632d
commit 505fe7a885
49 changed files with 4756 additions and 551 deletions

View File

@@ -0,0 +1,231 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Time.Testing;
using Xunit;
namespace StellaOps.Evidence.Bundle.Tests;
public class EvidenceBundleTests
{
private readonly FakeTimeProvider _timeProvider = new(new DateTimeOffset(2024, 12, 15, 12, 0, 0, TimeSpan.Zero));
[Fact]
public void Builder_MinimalBundle_CreatesValid()
{
var bundle = new EvidenceBundleBuilder(_timeProvider)
.WithAlertId("ALERT-001")
.WithArtifactId("sha256:abc123")
.Build();
Assert.NotNull(bundle);
Assert.Equal("ALERT-001", bundle.AlertId);
Assert.Equal("sha256:abc123", bundle.ArtifactId);
Assert.Equal("1.0", bundle.SchemaVersion);
Assert.NotEmpty(bundle.BundleId);
Assert.Equal(_timeProvider.GetUtcNow(), bundle.CreatedAt);
}
[Fact]
public void Builder_MissingAlertId_Throws()
{
var builder = new EvidenceBundleBuilder(_timeProvider).WithArtifactId("sha256:abc");
Assert.Throws<InvalidOperationException>(() => builder.Build());
}
[Fact]
public void Builder_MissingArtifactId_Throws()
{
var builder = new EvidenceBundleBuilder(_timeProvider).WithAlertId("ALERT-001");
Assert.Throws<InvalidOperationException>(() => builder.Build());
}
[Fact]
public void Builder_WithAllEvidence_ComputesHashSet()
{
var bundle = new EvidenceBundleBuilder(_timeProvider)
.WithAlertId("ALERT-001")
.WithArtifactId("sha256:abc123")
.WithReachability(new ReachabilityEvidence { Status = EvidenceStatus.Available, Hash = "hash1" })
.WithCallStack(new CallStackEvidence { Status = EvidenceStatus.Available, Hash = "hash2" })
.WithProvenance(new ProvenanceEvidence { Status = EvidenceStatus.Available, Hash = "hash3" })
.WithVexStatus(new VexStatusEvidence { Status = EvidenceStatus.Available, Hash = "hash4" })
.Build();
Assert.NotNull(bundle.Hashes);
Assert.Equal(4, bundle.Hashes.Hashes.Count);
Assert.NotEmpty(bundle.Hashes.CombinedHash);
Assert.Equal(64, bundle.Hashes.CombinedHash.Length);
}
[Fact]
public void ComputeCompletenessScore_AllAvailable_Returns4()
{
var bundle = new EvidenceBundleBuilder(_timeProvider)
.WithAlertId("ALERT-001")
.WithArtifactId("sha256:abc123")
.WithReachability(new ReachabilityEvidence { Status = EvidenceStatus.Available })
.WithCallStack(new CallStackEvidence { Status = EvidenceStatus.Available })
.WithProvenance(new ProvenanceEvidence { Status = EvidenceStatus.Available })
.WithVexStatus(new VexStatusEvidence { Status = EvidenceStatus.Available })
.Build();
Assert.Equal(4, bundle.ComputeCompletenessScore());
}
[Fact]
public void ComputeCompletenessScore_NoneAvailable_Returns0()
{
var bundle = new EvidenceBundleBuilder(_timeProvider)
.WithAlertId("ALERT-001")
.WithArtifactId("sha256:abc123")
.Build();
Assert.Equal(0, bundle.ComputeCompletenessScore());
}
[Fact]
public void ComputeCompletenessScore_PartialAvailable_ReturnsCorrect()
{
var bundle = new EvidenceBundleBuilder(_timeProvider)
.WithAlertId("ALERT-001")
.WithArtifactId("sha256:abc123")
.WithReachability(new ReachabilityEvidence { Status = EvidenceStatus.Available })
.WithCallStack(new CallStackEvidence { Status = EvidenceStatus.Unavailable })
.WithProvenance(new ProvenanceEvidence { Status = EvidenceStatus.Available })
.WithVexStatus(new VexStatusEvidence { Status = EvidenceStatus.Loading })
.Build();
Assert.Equal(2, bundle.ComputeCompletenessScore());
}
[Fact]
public void CreateStatusSummary_ReturnsCorrectStatuses()
{
var bundle = new EvidenceBundleBuilder(_timeProvider)
.WithAlertId("ALERT-001")
.WithArtifactId("sha256:abc123")
.WithReachability(new ReachabilityEvidence { Status = EvidenceStatus.Available })
.WithCallStack(new CallStackEvidence { Status = EvidenceStatus.Loading })
.WithProvenance(new ProvenanceEvidence { Status = EvidenceStatus.Error })
.Build();
var summary = bundle.CreateStatusSummary();
Assert.Equal(EvidenceStatus.Available, summary.Reachability);
Assert.Equal(EvidenceStatus.Loading, summary.CallStack);
Assert.Equal(EvidenceStatus.Error, summary.Provenance);
Assert.Equal(EvidenceStatus.Unavailable, summary.VexStatus);
}
[Fact]
public void ToSigningPredicate_CreatesValidPredicate()
{
var bundle = new EvidenceBundleBuilder(_timeProvider)
.WithAlertId("ALERT-001")
.WithArtifactId("sha256:abc123")
.WithReachability(new ReachabilityEvidence { Status = EvidenceStatus.Available, Hash = "hash1" })
.Build();
var predicate = bundle.ToSigningPredicate();
Assert.Equal(EvidenceBundlePredicate.PredicateType, "stellaops.dev/predicates/evidence-bundle@v1");
Assert.Equal(bundle.BundleId, predicate.BundleId);
Assert.Equal(bundle.AlertId, predicate.AlertId);
Assert.Equal(bundle.ArtifactId, predicate.ArtifactId);
Assert.Equal(1, predicate.CompletenessScore);
Assert.Equal(bundle.CreatedAt, predicate.CreatedAt);
}
}
public class EvidenceHashSetTests
{
[Fact]
public void Compute_DeterministicOutput()
{
var hashes1 = new Dictionary<string, string> { ["a"] = "hash1", ["b"] = "hash2" };
var hashes2 = new Dictionary<string, string> { ["b"] = "hash2", ["a"] = "hash1" };
var set1 = EvidenceHashSet.Compute(hashes1);
var set2 = EvidenceHashSet.Compute(hashes2);
Assert.Equal(set1.CombinedHash, set2.CombinedHash);
}
[Fact]
public void Compute_DifferentInputs_DifferentHash()
{
var hashes1 = new Dictionary<string, string> { ["a"] = "hash1" };
var hashes2 = new Dictionary<string, string> { ["a"] = "hash2" };
var set1 = EvidenceHashSet.Compute(hashes1);
var set2 = EvidenceHashSet.Compute(hashes2);
Assert.NotEqual(set1.CombinedHash, set2.CombinedHash);
}
[Fact]
public void Empty_CreatesEmptyHashSet()
{
var empty = EvidenceHashSet.Empty();
Assert.Empty(empty.Hashes);
Assert.NotEmpty(empty.CombinedHash);
Assert.Equal("SHA-256", empty.Algorithm);
}
[Fact]
public void Compute_PreservesLabeledHashes()
{
var hashes = new Dictionary<string, string> { ["reachability"] = "h1", ["vex"] = "h2" };
var set = EvidenceHashSet.Compute(hashes);
Assert.NotNull(set.LabeledHashes);
Assert.Equal("h1", set.LabeledHashes["reachability"]);
Assert.Equal("h2", set.LabeledHashes["vex"]);
}
[Fact]
public void Compute_NullInput_Throws()
{
Assert.Throws<ArgumentNullException>(() => EvidenceHashSet.Compute(null!));
}
}
public class ServiceCollectionExtensionsTests
{
[Fact]
public void AddEvidenceBundleServices_RegistersBuilder()
{
var services = new ServiceCollection();
services.AddEvidenceBundleServices();
var provider = services.BuildServiceProvider();
var builder = provider.GetService<EvidenceBundleBuilder>();
Assert.NotNull(builder);
}
[Fact]
public void AddEvidenceBundleServices_WithTimeProvider_UsesProvided()
{
var fakeTime = new FakeTimeProvider();
var services = new ServiceCollection();
services.AddEvidenceBundleServices(fakeTime);
var provider = services.BuildServiceProvider();
var timeProvider = provider.GetService<TimeProvider>();
Assert.Same(fakeTime, timeProvider);
}
[Fact]
public void AddEvidenceBundleServices_NullServices_Throws()
{
IServiceCollection? services = null;
Assert.Throws<ArgumentNullException>(() => services!.AddEvidenceBundleServices());
}
[Fact]
public void AddEvidenceBundleServices_NullTimeProvider_Throws()
{
var services = new ServiceCollection();
Assert.Throws<ArgumentNullException>(() => services.AddEvidenceBundleServices(null!));
}
}

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" ?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\__Libraries\StellaOps.Evidence.Bundle\StellaOps.Evidence.Bundle.csproj" />
</ItemGroup>
</Project>