190 lines
5.9 KiB
C#
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,
|
|
};
|
|
}
|
|
}
|