// // Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later. // using System.Collections.Immutable; using FluentAssertions; using StellaOps.Scanner.Core.Contracts; using StellaOps.Scanner.Emit.Evidence; using Xunit; namespace StellaOps.Scanner.Emit.Tests.Evidence; /// /// Unit tests for . /// Sprint: SPRINT_20260107_005_001 Task EV-011 /// [Trait("Category", "Unit")] public sealed class IdentityEvidenceBuilderTests { private readonly IdentityEvidenceBuilder _sut = new(); [Fact] public void Build_WithPurl_ReturnsFieldAsPurl() { // Arrange var component = CreateComponent(purl: "pkg:npm/lodash@4.17.21"); // Act var result = _sut.Build(component); // Assert result.Should().NotBeNull(); result!.Field.Should().Be("purl"); } [Fact] public void Build_WithNameOnly_ReturnsFieldAsName() { // Arrange var component = CreateComponent(name: "my-package"); // Act var result = _sut.Build(component); // Assert result.Should().NotBeNull(); result!.Field.Should().Be("name"); } [Fact] public void Build_WithManifestEvidence_IncludesManifestAnalysisMethod() { // Arrange var evidence = ImmutableArray.Create( new ComponentEvidence { Kind = "manifest", Value = "package.json", Source = "/app/package.json" }); var component = CreateComponent(purl: "pkg:npm/lodash@4.17.21", evidence: evidence); // Act var result = _sut.Build(component); // Assert result!.Methods.Should().ContainSingle(m => m.Technique == IdentityEvidenceTechnique.ManifestAnalysis); result.Methods![0].Confidence.Should().Be(0.95); } [Fact] public void Build_WithBinaryEvidence_IncludesBinaryAnalysisMethod() { // Arrange var evidence = ImmutableArray.Create( new ComponentEvidence { Kind = "binary", Value = "lodash.dll", Source = "/app/lodash.dll" }); var component = CreateComponent(purl: "pkg:npm/lodash@4.17.21", evidence: evidence); // Act var result = _sut.Build(component); // Assert result!.Methods.Should().ContainSingle(m => m.Technique == IdentityEvidenceTechnique.BinaryAnalysis); result.Methods![0].Confidence.Should().Be(0.80); } [Fact] public void Build_WithHashEvidence_IncludesHighConfidenceMethod() { // Arrange var evidence = ImmutableArray.Create( new ComponentEvidence { Kind = "hash", Value = "sha256:abc123", Source = "/app/lib.so" }); var component = CreateComponent(purl: "pkg:npm/lodash@4.17.21", evidence: evidence); // Act var result = _sut.Build(component); // Assert result!.Methods.Should().ContainSingle(m => m.Technique == IdentityEvidenceTechnique.HashComparison); result.Methods![0].Confidence.Should().Be(0.99); } [Fact] public void Build_WithMultipleSources_IncludesAllMethods() { // Arrange var evidence = ImmutableArray.Create( new ComponentEvidence { Kind = "manifest", Value = "package.json", Source = "/app/package.json" }, new ComponentEvidence { Kind = "binary", Value = "lib.dll", Source = "/app/lib.dll" }, new ComponentEvidence { Kind = "hash", Value = "sha256:abc", Source = "/app/lib.so" }); var component = CreateComponent(purl: "pkg:npm/lodash@4.17.21", evidence: evidence); // Act var result = _sut.Build(component); // Assert result!.Methods.Should().HaveCount(3); } [Fact] public void Build_CalculatesOverallConfidenceFromHighestMethod() { // Arrange var evidence = ImmutableArray.Create( new ComponentEvidence { Kind = "binary", Value = "lib.dll", Source = "/app/lib.dll" }, new ComponentEvidence { Kind = "hash", Value = "sha256:abc", Source = "/app/lib.so" }); var component = CreateComponent(purl: "pkg:npm/lodash@4.17.21", evidence: evidence); // Act var result = _sut.Build(component); // Assert result!.Confidence.Should().Be(0.99); // Hash match has highest confidence } [Fact] public void Build_WithNoIdentifyingData_ReturnsNull() { // Arrange - unknown name means no identification var component = new AggregatedComponent { Identity = ComponentIdentity.Create("unknown", "unknown"), }; // Act var result = _sut.Build(component); // Assert result.Should().BeNull(); } [Fact] public void Build_WithPurlNoEvidence_ReturnsAttestationMethod() { // Arrange var component = CreateComponent(purl: "pkg:npm/lodash@4.17.21"); // Act var result = _sut.Build(component); // Assert result!.Methods.Should().ContainSingle(m => m.Technique == IdentityEvidenceTechnique.Attestation); result.Methods![0].Confidence.Should().Be(0.70); } [Fact] public void Build_NullComponent_ThrowsArgumentNullException() { // Act & Assert var act = () => _sut.Build(null!); act.Should().Throw(); } private static AggregatedComponent CreateComponent( string? purl = null, string? name = null, ImmutableArray evidence = default) { var identity = ComponentIdentity.Create( key: purl ?? name ?? "unknown", name: name ?? "test-component", purl: purl); return new AggregatedComponent { Identity = identity, Evidence = evidence.IsDefault ? ImmutableArray.Empty : evidence, }; } }