// 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 SbomLineageTests { #region SbomId Tests [Trait("Category", TestCategories.Unit)] [Fact] public void SbomId_New_CreatesUniqueId() { var id1 = SbomId.New(); var id2 = SbomId.New(); id1.Should().NotBe(id2); } [Trait("Category", TestCategories.Unit)] [Fact] public void SbomId_Parse_RoundTrips() { var original = SbomId.New(); var parsed = SbomId.Parse(original.ToString()); parsed.Should().Be(original); } [Trait("Category", TestCategories.Unit)] [Fact] public void SbomId_ToString_ReturnsGuidString() { var id = SbomId.New(); var str = id.ToString(); Guid.TryParse(str, out _).Should().BeTrue(); } #endregion #region SbomLineage Model Tests [Trait("Category", TestCategories.Unit)] [Fact] public void SbomLineage_RequiredProperties_MustBeSet() { var lineage = new SbomLineage { Id = SbomId.New(), ImageDigest = "sha256:abc123", ContentHash = "sha256:def456", CreatedAt = DateTimeOffset.UtcNow }; lineage.Id.Should().NotBe(default(SbomId)); lineage.ImageDigest.Should().Be("sha256:abc123"); lineage.ContentHash.Should().Be("sha256:def456"); } [Trait("Category", TestCategories.Unit)] [Fact] public void SbomLineage_WithParent_TracksLineage() { var parentId = SbomId.New(); var childId = SbomId.New(); var child = new SbomLineage { Id = childId, ParentId = parentId, ImageDigest = "sha256:child", ContentHash = "sha256:childhash", CreatedAt = DateTimeOffset.UtcNow, Ancestors = [parentId] }; child.ParentId.Should().Be(parentId); child.Ancestors.Should().Contain(parentId); } [Trait("Category", TestCategories.Unit)] [Fact] public void SbomLineage_WithDiffPointer_TracksChanges() { var diff = new SbomDiffPointer { ComponentsAdded = 5, ComponentsRemoved = 2, ComponentsModified = 3, DiffHash = "sha256:diffhash" }; var lineage = new SbomLineage { Id = SbomId.New(), ParentId = SbomId.New(), ImageDigest = "sha256:image", ContentHash = "sha256:content", CreatedAt = DateTimeOffset.UtcNow, DiffFromParent = diff }; lineage.DiffFromParent.Should().NotBeNull(); lineage.DiffFromParent!.TotalChanges.Should().Be(10); } [Trait("Category", TestCategories.Unit)] [Fact] public void SbomLineage_RootLineage_HasNoParent() { var root = new SbomLineage { Id = SbomId.New(), ImageDigest = "sha256:root", ContentHash = "sha256:roothash", CreatedAt = DateTimeOffset.UtcNow }; root.ParentId.Should().BeNull(); root.Ancestors.Should().BeEmpty(); root.DiffFromParent.Should().BeNull(); } #endregion #region SbomDiffPointer Tests [Trait("Category", TestCategories.Unit)] [Fact] public void SbomDiffPointer_TotalChanges_SumsAllCategories() { var pointer = new SbomDiffPointer { ComponentsAdded = 10, ComponentsRemoved = 5, ComponentsModified = 8, DiffHash = "sha256:hash" }; pointer.TotalChanges.Should().Be(23); } [Trait("Category", TestCategories.Unit)] [Fact] public void SbomDiffPointer_EmptyDiff_HasZeroChanges() { var pointer = new SbomDiffPointer { ComponentsAdded = 0, ComponentsRemoved = 0, ComponentsModified = 0, DiffHash = "sha256:empty" }; pointer.TotalChanges.Should().Be(0); } #endregion }