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
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:
@@ -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!));
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user