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

245 lines
7.3 KiB
C#

// <copyright file="CycloneDxPedigreeMapperTests.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later.
// </copyright>
using System.Collections.Immutable;
using CycloneDX.Models;
using FluentAssertions;
using StellaOps.Scanner.Emit.Pedigree;
using Xunit;
namespace StellaOps.Scanner.Emit.Tests.Pedigree;
/// <summary>
/// Unit tests for <see cref="CycloneDxPedigreeMapper"/>.
/// Sprint: SPRINT_20260107_005_002 Task PD-011
/// </summary>
[Trait("Category", "Unit")]
public sealed class CycloneDxPedigreeMapperTests
{
private readonly CycloneDxPedigreeMapper _mapper = new();
[Fact]
public void Map_NullData_ReturnsNull()
{
// Act
var result = _mapper.Map(null);
// Assert
result.Should().BeNull();
}
[Fact]
public void Map_EmptyData_ReturnsNull()
{
// Arrange
var data = new PedigreeData();
// Act
var result = _mapper.Map(data);
// Assert
result.Should().BeNull();
}
[Fact]
public void Map_WithAncestors_MapsToComponents()
{
// Arrange
var data = new PedigreeData
{
Ancestors = ImmutableArray.Create(
new AncestorComponent
{
Name = "openssl",
Version = "1.1.1n",
Purl = "pkg:generic/openssl@1.1.1n",
ProjectUrl = "https://www.openssl.org",
Level = 1
})
};
// Act
var result = _mapper.Map(data);
// Assert
result.Should().NotBeNull();
result!.Ancestors.Should().HaveCount(1);
var ancestor = result.Ancestors![0];
ancestor.Name.Should().Be("openssl");
ancestor.Version.Should().Be("1.1.1n");
ancestor.Purl.Should().Be("pkg:generic/openssl@1.1.1n");
ancestor.ExternalReferences.Should().Contain(r =>
r.Type == ExternalReference.ExternalReferenceType.Website &&
r.Url == "https://www.openssl.org");
}
[Fact]
public void Map_WithVariants_MapsToComponents()
{
// Arrange
var data = new PedigreeData
{
Variants = ImmutableArray.Create(
new VariantComponent
{
Name = "openssl",
Version = "1.1.1n-0+deb11u5",
Purl = "pkg:deb/debian/openssl@1.1.1n-0+deb11u5",
Distribution = "debian",
Release = "bullseye"
})
};
// Act
var result = _mapper.Map(data);
// Assert
result.Should().NotBeNull();
result!.Variants.Should().HaveCount(1);
var variant = result.Variants![0];
variant.Name.Should().Be("openssl");
variant.Purl.Should().Be("pkg:deb/debian/openssl@1.1.1n-0+deb11u5");
variant.Properties.Should().Contain(p => p.Name == "stellaops:pedigree:distribution" && p.Value == "debian");
variant.Properties.Should().Contain(p => p.Name == "stellaops:pedigree:release" && p.Value == "bullseye");
}
[Fact]
public void Map_WithCommits_MapsToCommitList()
{
// Arrange
var timestamp = new DateTimeOffset(2024, 6, 15, 10, 30, 0, TimeSpan.Zero);
var data = new PedigreeData
{
Commits = ImmutableArray.Create(
new CommitInfo
{
Uid = "abc123def456789",
Url = "https://github.com/openssl/openssl/commit/abc123",
Message = "Fix CVE-2024-1234",
Author = new CommitActor
{
Name = "Developer",
Email = "dev@example.com",
Timestamp = timestamp
}
})
};
// Act
var result = _mapper.Map(data);
// Assert
result.Should().NotBeNull();
result!.Commits.Should().HaveCount(1);
var commit = result.Commits![0];
commit.Uid.Should().Be("abc123def456789");
commit.Url.Should().Be("https://github.com/openssl/openssl/commit/abc123");
commit.Message.Should().Be("Fix CVE-2024-1234");
commit.Author.Should().NotBeNull();
commit.Author!.Name.Should().Be("Developer");
}
[Fact]
public void Map_WithPatches_MapsToPatchList()
{
// Arrange
var data = new PedigreeData
{
Patches = ImmutableArray.Create(
new PatchInfo
{
Type = PatchType.Backport,
DiffUrl = "https://patch.url/fix.patch",
DiffText = "--- a/file.c\n+++ b/file.c\n@@ -10,3 +10,4 @@",
Resolves = ImmutableArray.Create(
new PatchResolution
{
Id = "CVE-2024-1234",
SourceName = "NVD"
})
})
};
// Act
var result = _mapper.Map(data);
// Assert
result.Should().NotBeNull();
result!.Patches.Should().HaveCount(1);
var patch = result.Patches![0];
patch.Type.Should().Be(Patch.PatchClassification.Backport);
patch.Diff.Should().NotBeNull();
patch.Diff!.Url.Should().Be("https://patch.url/fix.patch");
patch.Resolves.Should().Contain(i => i.Id == "CVE-2024-1234");
}
[Fact]
public void Map_WithNotes_IncludesNotes()
{
// Arrange
var data = new PedigreeData
{
Notes = "Backported security fix from upstream 1.1.1o",
Ancestors = ImmutableArray.Create(
new AncestorComponent { Name = "openssl", Version = "1.1.1o" })
};
// Act
var result = _mapper.Map(data);
// Assert
result.Should().NotBeNull();
result!.Notes.Should().Be("Backported security fix from upstream 1.1.1o");
}
[Fact]
public void Map_MultipleAncestors_OrdersByLevel()
{
// Arrange
var data = new PedigreeData
{
Ancestors = ImmutableArray.Create(
new AncestorComponent { Name = "grandparent", Version = "1.0", Level = 2 },
new AncestorComponent { Name = "parent", Version = "2.0", Level = 1 })
};
// Act
var result = _mapper.Map(data);
// Assert
result!.Ancestors![0].Name.Should().Be("parent");
result.Ancestors[1].Name.Should().Be("grandparent");
}
[Fact]
public void Map_PatchTypes_MapCorrectly()
{
// Arrange
var data = new PedigreeData
{
Patches = ImmutableArray.Create(
new PatchInfo { Type = PatchType.Backport },
new PatchInfo { Type = PatchType.CherryPick },
new PatchInfo { Type = PatchType.Unofficial },
new PatchInfo { Type = PatchType.Monkey })
};
// Act
var result = _mapper.Map(data);
// Assert
result!.Patches!.Select(p => p.Type).Should().BeEquivalentTo(new[]
{
Patch.PatchClassification.Backport,
Patch.PatchClassification.Cherry_Pick,
Patch.PatchClassification.Unofficial,
Patch.PatchClassification.Monkey
});
}
}