Files
git.stella-ops.org/src/Scanner/__Tests/StellaOps.Scanner.Emit.Tests/Evidence/IdentityEvidenceBuilderTests.cs
2026-01-08 20:46:43 +02:00

190 lines
5.9 KiB
C#

// <copyright file="IdentityEvidenceBuilderTests.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later.
// </copyright>
using System.Collections.Immutable;
using FluentAssertions;
using StellaOps.Scanner.Core.Contracts;
using StellaOps.Scanner.Emit.Evidence;
using Xunit;
namespace StellaOps.Scanner.Emit.Tests.Evidence;
/// <summary>
/// Unit tests for <see cref="IdentityEvidenceBuilder"/>.
/// Sprint: SPRINT_20260107_005_001 Task EV-011
/// </summary>
[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<ArgumentNullException>();
}
private static AggregatedComponent CreateComponent(
string? purl = null,
string? name = null,
ImmutableArray<ComponentEvidence> 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<ComponentEvidence>.Empty : evidence,
};
}
}