// SPDX-License-Identifier: AGPL-3.0-or-later // Copyright (c) StellaOps using System.Collections.Immutable; using FluentAssertions; using StellaOps.Scanner.Emit.Lineage; using StellaOps.TestKit; namespace StellaOps.Scanner.Emit.Lineage.Tests; public class RebuildProofTests { #region RebuildProof Model Tests [Trait("Category", TestCategories.Unit)] [Fact] public void RebuildProof_RequiredProperties_MustBeSet() { var proof = new RebuildProof { SbomId = SbomId.New(), ImageDigest = "sha256:abc123", StellaOpsVersion = "1.0.0", FeedSnapshots = [], AnalyzerVersions = [], PolicyHash = "sha256:policy", GeneratedAt = DateTimeOffset.UtcNow }; proof.SbomId.Should().NotBe(default(SbomId)); proof.ImageDigest.Should().NotBeNullOrEmpty(); proof.StellaOpsVersion.Should().Be("1.0.0"); proof.PolicyHash.Should().NotBeNullOrEmpty(); } [Trait("Category", TestCategories.Unit)] [Fact] public void RebuildProof_WithFeedSnapshots_TracksAllFeeds() { var feeds = ImmutableArray.Create( new FeedSnapshot { FeedId = "nvd", FeedName = "NVD CVE Feed", SnapshotHash = "sha256:nvdhash", AsOf = DateTimeOffset.UtcNow, EntryCount = 200000 }, new FeedSnapshot { FeedId = "ghsa", FeedName = "GitHub Security Advisories", SnapshotHash = "sha256:ghsahash", AsOf = DateTimeOffset.UtcNow, EntryCount = 15000 } ); var proof = new RebuildProof { SbomId = SbomId.New(), ImageDigest = "sha256:image", StellaOpsVersion = "1.0.0", FeedSnapshots = feeds, AnalyzerVersions = [], PolicyHash = "sha256:policy", GeneratedAt = DateTimeOffset.UtcNow }; proof.FeedSnapshots.Should().HaveCount(2); proof.FeedSnapshots[0].FeedId.Should().Be("nvd"); proof.FeedSnapshots[1].EntryCount.Should().Be(15000); } [Trait("Category", TestCategories.Unit)] [Fact] public void RebuildProof_WithAnalyzerVersions_TracksAllAnalyzers() { var analyzers = ImmutableArray.Create( new AnalyzerVersion { AnalyzerId = "npm-analyzer", AnalyzerName = "NPM Package Analyzer", Version = "2.0.0", CodeHash = "sha256:npmhash" }, new AnalyzerVersion { AnalyzerId = "dotnet-analyzer", AnalyzerName = ".NET Package Analyzer", Version = "3.1.0" } ); var proof = new RebuildProof { SbomId = SbomId.New(), ImageDigest = "sha256:image", StellaOpsVersion = "1.0.0", FeedSnapshots = [], AnalyzerVersions = analyzers, PolicyHash = "sha256:policy", GeneratedAt = DateTimeOffset.UtcNow }; proof.AnalyzerVersions.Should().HaveCount(2); proof.AnalyzerVersions[0].AnalyzerId.Should().Be("npm-analyzer"); } [Trait("Category", TestCategories.Unit)] [Fact] public void RebuildProof_OptionalDsseSignature_IsNullByDefault() { var proof = new RebuildProof { SbomId = SbomId.New(), ImageDigest = "sha256:image", StellaOpsVersion = "1.0.0", FeedSnapshots = [], AnalyzerVersions = [], PolicyHash = "sha256:policy", GeneratedAt = DateTimeOffset.UtcNow }; proof.DsseSignature.Should().BeNull(); proof.ProofHash.Should().BeNull(); } [Trait("Category", TestCategories.Unit)] [Fact] public void RebuildProof_WithSignature_StoresSignature() { var proof = new RebuildProof { SbomId = SbomId.New(), ImageDigest = "sha256:image", StellaOpsVersion = "1.0.0", FeedSnapshots = [], AnalyzerVersions = [], PolicyHash = "sha256:policy", GeneratedAt = DateTimeOffset.UtcNow, DsseSignature = "eyJwYXlsb2FkIjoiLi4uIn0=", ProofHash = "sha256:proofhash" }; proof.DsseSignature.Should().NotBeNullOrEmpty(); proof.ProofHash.Should().StartWith("sha256:"); } #endregion #region FeedSnapshot Tests [Trait("Category", TestCategories.Unit)] [Fact] public void FeedSnapshot_RequiredProperties_MustBeSet() { var snapshot = new FeedSnapshot { FeedId = "nvd", FeedName = "NVD CVE Feed", SnapshotHash = "sha256:hash", AsOf = DateTimeOffset.UtcNow }; snapshot.FeedId.Should().Be("nvd"); snapshot.FeedName.Should().Be("NVD CVE Feed"); snapshot.SnapshotHash.Should().NotBeNullOrEmpty(); } [Trait("Category", TestCategories.Unit)] [Fact] public void FeedSnapshot_OptionalProperties_AreNullByDefault() { var snapshot = new FeedSnapshot { FeedId = "nvd", FeedName = "NVD", SnapshotHash = "sha256:hash", AsOf = DateTimeOffset.UtcNow }; snapshot.EntryCount.Should().BeNull(); snapshot.FeedVersion.Should().BeNull(); } #endregion #region AnalyzerVersion Tests [Trait("Category", TestCategories.Unit)] [Fact] public void AnalyzerVersion_RequiredProperties_MustBeSet() { var analyzer = new AnalyzerVersion { AnalyzerId = "npm-analyzer", AnalyzerName = "NPM Package Analyzer", Version = "2.0.0" }; analyzer.AnalyzerId.Should().Be("npm-analyzer"); analyzer.AnalyzerName.Should().Be("NPM Package Analyzer"); analyzer.Version.Should().Be("2.0.0"); } [Trait("Category", TestCategories.Unit)] [Fact] public void AnalyzerVersion_OptionalHashes_AreNullByDefault() { var analyzer = new AnalyzerVersion { AnalyzerId = "test", AnalyzerName = "Test", Version = "1.0.0" }; analyzer.CodeHash.Should().BeNull(); analyzer.ConfigHash.Should().BeNull(); } #endregion #region RebuildVerification Tests [Trait("Category", TestCategories.Unit)] [Fact] public void RebuildVerification_SuccessfulRebuild_HasMatchingHash() { var proof = new RebuildProof { SbomId = SbomId.New(), ImageDigest = "sha256:image", StellaOpsVersion = "1.0.0", FeedSnapshots = [], AnalyzerVersions = [], PolicyHash = "sha256:policy", GeneratedAt = DateTimeOffset.UtcNow }; var verification = new RebuildVerification { Proof = proof, Success = true, RebuiltSbomId = SbomId.New(), HashMatches = true, VerifiedAt = DateTimeOffset.UtcNow }; verification.Success.Should().BeTrue(); verification.HashMatches.Should().BeTrue(); verification.Differences.Should().BeNull(); verification.ErrorMessage.Should().BeNull(); } [Trait("Category", TestCategories.Unit)] [Fact] public void RebuildVerification_FailedRebuild_HasErrorMessage() { var proof = new RebuildProof { SbomId = SbomId.New(), ImageDigest = "sha256:image", StellaOpsVersion = "1.0.0", FeedSnapshots = [], AnalyzerVersions = [], PolicyHash = "sha256:policy", GeneratedAt = DateTimeOffset.UtcNow }; var verification = new RebuildVerification { Proof = proof, Success = false, ErrorMessage = "Feed snapshot not available", VerifiedAt = DateTimeOffset.UtcNow }; verification.Success.Should().BeFalse(); verification.ErrorMessage.Should().Be("Feed snapshot not available"); verification.RebuiltSbomId.Should().BeNull(); } [Trait("Category", TestCategories.Unit)] [Fact] public void RebuildVerification_MismatchRebuild_HasDifferences() { var proof = new RebuildProof { SbomId = SbomId.New(), ImageDigest = "sha256:image", StellaOpsVersion = "1.0.0", FeedSnapshots = [], AnalyzerVersions = [], PolicyHash = "sha256:policy", GeneratedAt = DateTimeOffset.UtcNow }; var diff = new SbomDiff { FromId = proof.SbomId, ToId = SbomId.New(), Deltas = [], Summary = new DiffSummary { Added = 1, Removed = 0, VersionChanged = 0, OtherModified = 0, Unchanged = 100 }, ComputedAt = DateTimeOffset.UtcNow }; var verification = new RebuildVerification { Proof = proof, Success = true, RebuiltSbomId = SbomId.New(), HashMatches = false, Differences = diff, VerifiedAt = DateTimeOffset.UtcNow }; verification.Success.Should().BeTrue(); verification.HashMatches.Should().BeFalse(); verification.Differences.Should().NotBeNull(); verification.Differences!.Summary.Added.Should().Be(1); } #endregion }