// // 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 LicenseEvidenceBuilderTests { private readonly LicenseEvidenceBuilder _sut = new(); [Fact] public void Build_WithLicenseEvidence_ReturnsLicenseChoices() { // Arrange var evidence = ImmutableArray.Create( new ComponentEvidence { Kind = "license", Value = "MIT", Source = "/app/LICENSE" }); var component = CreateComponent(evidence); // Act var result = _sut.Build(component); // Assert result.Should().HaveCount(1); result[0].License.Should().NotBeNull(); result[0].License!.License.Should().NotBeNull(); result[0].License!.License!.Id.Should().Be("MIT"); } [Fact] public void Build_WithMultipleLicenses_ReturnsAllLicenses() { // Arrange var evidence = ImmutableArray.Create( new ComponentEvidence { Kind = "license", Value = "MIT", Source = "/app/LICENSE" }, new ComponentEvidence { Kind = "license", Value = "Apache-2.0", Source = "/app/LICENSE.apache" }, new ComponentEvidence { Kind = "license", Value = "GPL-3.0", Source = "/app/COPYING" }); var component = CreateComponent(evidence); // Act var result = _sut.Build(component); // Assert result.Should().HaveCount(3); } [Fact] public void Build_WithNonLicenseEvidence_FiltersOutNonLicenses() { // Arrange var evidence = ImmutableArray.Create( new ComponentEvidence { Kind = "license", Value = "MIT", Source = "/app/LICENSE" }, new ComponentEvidence { Kind = "file", Value = "readme.md", Source = "/app/README.md" }); var component = CreateComponent(evidence); // Act var result = _sut.Build(component); // Assert result.Should().HaveCount(1); result[0].License!.License!.Id.Should().Be("MIT"); } [Fact] public void Build_WithNoEvidence_ReturnsEmptyArray() { // Arrange var component = CreateComponent(ImmutableArray.Empty); // Act var result = _sut.Build(component); // Assert result.Should().BeEmpty(); } [Fact] public void Build_NullComponent_ThrowsArgumentNullException() { // Act & Assert var act = () => _sut.Build(null!); act.Should().Throw(); } [Fact] public void Build_WithExpression_ParsesAsExpression() { // Arrange var evidence = ImmutableArray.Create( new ComponentEvidence { Kind = "license", Value = "MIT OR Apache-2.0", Source = "/app/LICENSE" }); var component = CreateComponent(evidence); // Act var result = _sut.Build(component); // Assert result.Should().HaveCount(1); // SPDX expressions are parsed as expression rather than ID result[0].License.Expression.Should().NotBeNullOrWhiteSpace(); } [Fact] public void Build_DeduplicatesSameLicense() { // Arrange var evidence = ImmutableArray.Create( new ComponentEvidence { Kind = "license", Value = "MIT", Source = "/app/LICENSE" }, new ComponentEvidence { Kind = "license", Value = "MIT", Source = "/app/package.json" }); // Same license var component = CreateComponent(evidence); // Act var result = _sut.Build(component); // Assert result.Should().HaveCount(1); } private static AggregatedComponent CreateComponent(ImmutableArray evidence) { var identity = ComponentIdentity.Create( key: "pkg:npm/lodash@4.17.21", name: "lodash", purl: "pkg:npm/lodash@4.17.21"); return new AggregatedComponent { Identity = identity, Evidence = evidence, }; } }