up
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-11-28 09:40:40 +02:00
parent 1c6730a1d2
commit 05da719048
206 changed files with 34741 additions and 1751 deletions

View File

@@ -0,0 +1,380 @@
using System.Collections.Immutable;
using FluentAssertions;
using StellaOps.Policy.Engine.SelectionJoin;
using Xunit;
namespace StellaOps.Policy.Engine.Tests.SelectionJoin;
public sealed class SelectionJoinTests
{
#region PurlEquivalence Tests
[Theory]
[InlineData("pkg:npm/lodash@4.17.21", "pkg:npm/lodash")]
[InlineData("pkg:maven/org.apache.commons/commons-lang3@3.12.0", "pkg:maven/org.apache.commons/commons-lang3")]
[InlineData("pkg:pypi/requests@2.28.0", "pkg:pypi/requests")]
[InlineData("pkg:gem/rails@7.0.0", "pkg:gem/rails")]
[InlineData("pkg:nuget/Newtonsoft.Json@13.0.1", "pkg:nuget/Newtonsoft.Json")]
public void ExtractPackageKey_RemovesVersion(string purl, string expectedKey)
{
var key = PurlEquivalence.ExtractPackageKey(purl);
key.Should().Be(expectedKey);
}
[Fact]
public void ExtractPackageKey_HandlesNoVersion()
{
var purl = "pkg:npm/lodash";
var key = PurlEquivalence.ExtractPackageKey(purl);
key.Should().Be("pkg:npm/lodash");
}
[Fact]
public void ExtractPackageKey_HandlesScopedPackages()
{
var purl = "pkg:npm/@scope/package@1.0.0";
var key = PurlEquivalence.ExtractPackageKey(purl);
key.Should().Be("pkg:npm/@scope/package");
}
[Theory]
[InlineData("pkg:npm/lodash@4.17.21", "npm")]
[InlineData("pkg:maven/org.apache/commons@1.0", "maven")]
[InlineData("pkg:pypi/requests@2.28", "pypi")]
public void ExtractEcosystem_ReturnsCorrectEcosystem(string purl, string expected)
{
var ecosystem = PurlEquivalence.ExtractEcosystem(purl);
ecosystem.Should().Be(expected);
}
[Fact]
public void ComputeMatchConfidence_ExactMatch_Returns1()
{
var confidence = PurlEquivalence.ComputeMatchConfidence(
"pkg:npm/lodash@4.17.21",
"pkg:npm/lodash@4.17.21");
confidence.Should().Be(1.0);
}
[Fact]
public void ComputeMatchConfidence_PackageKeyMatch_Returns08()
{
var confidence = PurlEquivalence.ComputeMatchConfidence(
"pkg:npm/lodash@4.17.21",
"pkg:npm/lodash@4.17.20");
confidence.Should().Be(0.8);
}
#endregion
#region PurlEquivalenceTable Tests
[Fact]
public void FromGroups_CreatesEquivalentMappings()
{
var groups = new[]
{
new[] { "pkg:npm/lodash", "pkg:npm/lodash-es" }
};
var table = PurlEquivalenceTable.FromGroups(groups);
table.AreEquivalent("pkg:npm/lodash", "pkg:npm/lodash-es").Should().BeTrue();
table.GroupCount.Should().Be(1);
}
[Fact]
public void GetCanonical_ReturnsFirstLexicographically()
{
var groups = new[]
{
new[] { "pkg:npm/b-package", "pkg:npm/a-package" }
};
var table = PurlEquivalenceTable.FromGroups(groups);
// "a-package" is lexicographically first
table.GetCanonical("pkg:npm/b-package").Should().Be("pkg:npm/a-package");
}
[Fact]
public void GetEquivalents_ReturnsAllEquivalentPurls()
{
var groups = new[]
{
new[] { "pkg:npm/a", "pkg:npm/b", "pkg:npm/c" }
};
var table = PurlEquivalenceTable.FromGroups(groups);
var equivalents = table.GetEquivalents("pkg:npm/b");
equivalents.Should().HaveCount(3);
equivalents.Should().Contain("pkg:npm/a");
equivalents.Should().Contain("pkg:npm/b");
equivalents.Should().Contain("pkg:npm/c");
}
[Fact]
public void Empty_HasNoMappings()
{
var table = PurlEquivalenceTable.Empty;
table.GroupCount.Should().Be(0);
table.TotalEntries.Should().Be(0);
table.AreEquivalent("pkg:npm/a", "pkg:npm/b").Should().BeFalse();
}
#endregion
#region SelectionJoinService Tests
[Fact]
public void ResolveTuples_MatchesByExactPurl()
{
var service = new SelectionJoinService();
var input = new SelectionJoinBatchInput(
TenantId: "test-tenant",
BatchId: "batch-1",
Components: [
new SbomComponentInput(
Purl: "pkg:npm/lodash@4.17.21",
Name: "lodash",
Version: "4.17.21",
Ecosystem: "npm",
Metadata: ImmutableDictionary<string, string>.Empty)
],
Advisories: [
new AdvisoryLinksetInput(
AdvisoryId: "GHSA-test-001",
Source: "github",
Purls: ["pkg:npm/lodash@4.17.21"],
Cpes: ImmutableArray<string>.Empty,
Aliases: ["CVE-2021-12345"],
Confidence: 1.0)
],
VexLinksets: ImmutableArray<VexLinksetInput>.Empty,
EquivalenceTable: null,
Options: new SelectionJoinOptions());
var result = service.ResolveTuples(input);
result.Tuples.Should().ContainSingle();
result.Tuples[0].MatchType.Should().Be(SelectionMatchType.ExactPurl);
result.Tuples[0].Component.Purl.Should().Be("pkg:npm/lodash@4.17.21");
result.Statistics.ExactPurlMatches.Should().Be(1);
}
[Fact]
public void ResolveTuples_MatchesByPackageKey()
{
var service = new SelectionJoinService();
var input = new SelectionJoinBatchInput(
TenantId: "test-tenant",
BatchId: "batch-1",
Components: [
new SbomComponentInput("pkg:npm/lodash@4.17.21", "lodash", "4.17.21", "npm",
ImmutableDictionary<string, string>.Empty)
],
Advisories: [
new AdvisoryLinksetInput("GHSA-test-001", "github",
Purls: ["pkg:npm/lodash@4.17.20"], // Different version
Cpes: ImmutableArray<string>.Empty,
Aliases: ["CVE-2021-12345"],
Confidence: 1.0)
],
VexLinksets: ImmutableArray<VexLinksetInput>.Empty,
EquivalenceTable: null,
Options: new SelectionJoinOptions());
var result = service.ResolveTuples(input);
result.Tuples.Should().ContainSingle();
result.Tuples[0].MatchType.Should().Be(SelectionMatchType.PackageKeyMatch);
}
[Fact]
public void ResolveTuples_AppliesVexOverlay()
{
var service = new SelectionJoinService();
var input = new SelectionJoinBatchInput(
TenantId: "test-tenant",
BatchId: "batch-1",
Components: [
new SbomComponentInput("pkg:npm/lodash@4.17.21", "lodash", "4.17.21", "npm",
ImmutableDictionary<string, string>.Empty)
],
Advisories: [
new AdvisoryLinksetInput("GHSA-test-001", "github",
Purls: ["pkg:npm/lodash@4.17.21"],
Cpes: ImmutableArray<string>.Empty,
Aliases: ["CVE-2021-12345"],
Confidence: 1.0)
],
VexLinksets: [
new VexLinksetInput("vex-1", "CVE-2021-12345", "pkg:npm/lodash@4.17.21",
"not_affected", "vulnerable_code_not_in_execute_path", VexConfidenceLevel.High)
],
EquivalenceTable: null,
Options: new SelectionJoinOptions());
var result = service.ResolveTuples(input);
result.Tuples.Should().ContainSingle();
result.Tuples[0].Vex.Should().NotBeNull();
result.Tuples[0].Vex!.Status.Should().Be("not_affected");
result.Statistics.VexOverlays.Should().Be(1);
}
[Fact]
public void ResolveTuples_ProducesDeterministicOrdering()
{
var service = new SelectionJoinService();
var input = new SelectionJoinBatchInput(
TenantId: "test-tenant",
BatchId: "batch-1",
Components: [
new SbomComponentInput("pkg:npm/z-package@1.0.0", "z", "1.0.0", "npm",
ImmutableDictionary<string, string>.Empty),
new SbomComponentInput("pkg:npm/a-package@1.0.0", "a", "1.0.0", "npm",
ImmutableDictionary<string, string>.Empty),
new SbomComponentInput("pkg:npm/m-package@1.0.0", "m", "1.0.0", "npm",
ImmutableDictionary<string, string>.Empty)
],
Advisories: [
new AdvisoryLinksetInput("ADV-001", "test",
Purls: ["pkg:npm/z-package", "pkg:npm/a-package", "pkg:npm/m-package"],
Cpes: ImmutableArray<string>.Empty,
Aliases: ["CVE-2021-001"],
Confidence: 1.0)
],
VexLinksets: ImmutableArray<VexLinksetInput>.Empty,
EquivalenceTable: null,
Options: new SelectionJoinOptions());
var result = service.ResolveTuples(input);
// Should be sorted by component PURL
result.Tuples.Should().HaveCount(3);
result.Tuples[0].Component.Purl.Should().Be("pkg:npm/a-package@1.0.0");
result.Tuples[1].Component.Purl.Should().Be("pkg:npm/m-package@1.0.0");
result.Tuples[2].Component.Purl.Should().Be("pkg:npm/z-package@1.0.0");
}
[Fact]
public void ResolveTuples_HandlesMultipleAdvisories()
{
var service = new SelectionJoinService();
var input = new SelectionJoinBatchInput(
TenantId: "test-tenant",
BatchId: "batch-1",
Components: [
new SbomComponentInput("pkg:npm/lodash@4.17.21", "lodash", "4.17.21", "npm",
ImmutableDictionary<string, string>.Empty)
],
Advisories: [
new AdvisoryLinksetInput("ADV-001", "test",
Purls: ["pkg:npm/lodash@4.17.21"],
Cpes: ImmutableArray<string>.Empty,
Aliases: ["CVE-2021-001"],
Confidence: 1.0),
new AdvisoryLinksetInput("ADV-002", "test",
Purls: ["pkg:npm/lodash@4.17.21"],
Cpes: ImmutableArray<string>.Empty,
Aliases: ["CVE-2021-002"],
Confidence: 1.0)
],
VexLinksets: ImmutableArray<VexLinksetInput>.Empty,
EquivalenceTable: null,
Options: new SelectionJoinOptions());
var result = service.ResolveTuples(input);
result.Tuples.Should().HaveCount(2);
result.Tuples.Should().Contain(t => t.Advisory.AdvisoryId == "ADV-001");
result.Tuples.Should().Contain(t => t.Advisory.AdvisoryId == "ADV-002");
}
[Fact]
public void ResolveTuples_ReturnsStatistics()
{
var service = new SelectionJoinService();
var input = new SelectionJoinBatchInput(
TenantId: "test-tenant",
BatchId: "batch-1",
Components: [
new SbomComponentInput("pkg:npm/a@1.0.0", "a", "1.0.0", "npm",
ImmutableDictionary<string, string>.Empty),
new SbomComponentInput("pkg:npm/b@1.0.0", "b", "1.0.0", "npm",
ImmutableDictionary<string, string>.Empty)
],
Advisories: [
new AdvisoryLinksetInput("ADV-001", "test",
Purls: ["pkg:npm/a"],
Cpes: ImmutableArray<string>.Empty,
Aliases: ["CVE-001"],
Confidence: 1.0)
],
VexLinksets: ImmutableArray<VexLinksetInput>.Empty,
EquivalenceTable: null,
Options: new SelectionJoinOptions());
var result = service.ResolveTuples(input);
result.Statistics.TotalComponents.Should().Be(2);
result.Statistics.TotalAdvisories.Should().Be(1);
result.Statistics.MatchedTuples.Should().Be(1);
result.UnmatchedComponents.Should().ContainSingle(c => c.Purl == "pkg:npm/b@1.0.0");
}
[Fact]
public void ResolveTuples_HandlesEmptyInput()
{
var service = new SelectionJoinService();
var input = new SelectionJoinBatchInput(
TenantId: "test-tenant",
BatchId: "batch-1",
Components: ImmutableArray<SbomComponentInput>.Empty,
Advisories: ImmutableArray<AdvisoryLinksetInput>.Empty,
VexLinksets: ImmutableArray<VexLinksetInput>.Empty,
EquivalenceTable: null,
Options: new SelectionJoinOptions());
var result = service.ResolveTuples(input);
result.Tuples.Should().BeEmpty();
result.Statistics.TotalComponents.Should().Be(0);
}
#endregion
#region SelectionJoinTuple Tests
[Fact]
public void CreateTupleId_IsDeterministic()
{
var id1 = SelectionJoinTuple.CreateTupleId("tenant1", "pkg:npm/lodash@4.17.21", "CVE-2021-12345");
var id2 = SelectionJoinTuple.CreateTupleId("tenant1", "pkg:npm/lodash@4.17.21", "CVE-2021-12345");
id1.Should().Be(id2);
id1.Should().StartWith("tuple:sha256:");
}
[Fact]
public void CreateTupleId_NormalizesInput()
{
var id1 = SelectionJoinTuple.CreateTupleId("TENANT1", "PKG:NPM/LODASH@4.17.21", "CVE-2021-12345");
var id2 = SelectionJoinTuple.CreateTupleId("tenant1", "pkg:npm/lodash@4.17.21", "CVE-2021-12345");
id1.Should().Be(id2);
}
#endregion
}