Files
git.stella-ops.org/src/Concelier/__Tests/StellaOps.Concelier.Merge.Tests/Precedence/SourcePrecedenceLatticeTests.cs
2025-12-26 00:32:58 +02:00

451 lines
13 KiB
C#

// -----------------------------------------------------------------------------
// SourcePrecedenceLatticeTests.cs
// Sprint: SPRINT_8200_0015_0001_CONCEL_backport_integration
// Task: BACKPORT-8200-022
// Description: Unit tests for ConfigurableSourcePrecedenceLattice
// -----------------------------------------------------------------------------
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using StellaOps.Concelier.Merge.Backport;
using StellaOps.Concelier.Merge.Precedence;
namespace StellaOps.Concelier.Merge.Tests.Precedence;
public sealed class SourcePrecedenceLatticeTests
{
private readonly TestLogger<ConfigurableSourcePrecedenceLattice> _logger = new();
[Theory]
[InlineData("vendor-psirt", 10)]
[InlineData("cisco", 10)]
[InlineData("oracle", 10)]
[InlineData("microsoft", 10)]
[InlineData("debian", 20)]
[InlineData("redhat", 20)]
[InlineData("ubuntu", 20)]
[InlineData("nvd", 40)]
[InlineData("ghsa", 35)]
[InlineData("osv", 30)]
[InlineData("community", 100)]
public void GetPrecedence_ReturnsDefaultPrecedence_ForKnownSources(string source, int expected)
{
var lattice = CreateLattice();
var precedence = lattice.GetPrecedence(source);
Assert.Equal(expected, precedence);
}
[Fact]
public void GetPrecedence_ReturnsHighValue_ForUnknownSource()
{
var lattice = CreateLattice();
var precedence = lattice.GetPrecedence("unknown-source");
Assert.Equal(1000, precedence);
}
[Theory]
[InlineData("DEBIAN", 20)]
[InlineData("Debian", 20)]
[InlineData("dEbIaN", 20)]
public void GetPrecedence_IsCaseInsensitive(string source, int expected)
{
var lattice = CreateLattice();
var precedence = lattice.GetPrecedence(source);
Assert.Equal(expected, precedence);
}
[Fact]
public void Compare_VendorTakesHigherPrecedence_OverDistro()
{
var lattice = CreateLattice();
var result = lattice.Compare("vendor-psirt", "debian");
Assert.Equal(SourceComparison.Source1Higher, result);
}
[Fact]
public void Compare_DistroTakesHigherPrecedence_OverNvd()
{
var lattice = CreateLattice();
var result = lattice.Compare("debian", "nvd");
Assert.Equal(SourceComparison.Source1Higher, result);
}
[Fact]
public void Compare_SameDistros_AreEqual()
{
var lattice = CreateLattice();
var result = lattice.Compare("debian", "redhat");
Assert.Equal(SourceComparison.Equal, result);
}
[Theory]
[InlineData("debian", true)]
[InlineData("redhat", true)]
[InlineData("suse", true)]
[InlineData("ubuntu", true)]
[InlineData("alpine", true)]
[InlineData("astra", true)]
[InlineData("centos", true)]
[InlineData("fedora", true)]
[InlineData("rocky", true)]
[InlineData("alma", true)]
[InlineData("nvd", false)]
[InlineData("ghsa", false)]
[InlineData("vendor-psirt", false)]
[InlineData("unknown", false)]
public void IsDistroSource_CorrectlyIdentifiesSources(string source, bool expected)
{
var lattice = CreateLattice();
var result = lattice.IsDistroSource(source);
Assert.Equal(expected, result);
}
[Fact]
public void BackportBoostAmount_ReturnsDefaultValue()
{
var lattice = CreateLattice();
Assert.Equal(15, lattice.BackportBoostAmount);
}
[Fact]
public void BackportBoostThreshold_ReturnsDefaultValue()
{
var lattice = CreateLattice();
Assert.Equal(0.7, lattice.BackportBoostThreshold);
}
[Fact]
public void GetPrecedence_AppliesBackportBoost_WhenDistroHasHighConfidenceEvidence()
{
var lattice = CreateLattice();
var context = new BackportContext
{
CveId = "CVE-2024-1234",
HasBackportEvidence = true,
EvidenceConfidence = 0.9,
EvidenceTier = BackportEvidenceTier.DistroAdvisory
};
var basePrecedence = lattice.GetPrecedence("debian");
var boostedPrecedence = lattice.GetPrecedence("debian", context);
Assert.Equal(20, basePrecedence);
Assert.Equal(5, boostedPrecedence); // 20 - 15 = 5
}
[Fact]
public void GetPrecedence_DoesNotApplyBackportBoost_WhenConfidenceBelowThreshold()
{
var lattice = CreateLattice();
var context = new BackportContext
{
CveId = "CVE-2024-1234",
HasBackportEvidence = true,
EvidenceConfidence = 0.5, // Below 0.7 threshold
EvidenceTier = BackportEvidenceTier.ChangelogMention
};
var precedence = lattice.GetPrecedence("debian", context);
Assert.Equal(20, precedence); // No boost applied
}
[Fact]
public void GetPrecedence_DoesNotApplyBackportBoost_WhenNoEvidence()
{
var lattice = CreateLattice();
var context = new BackportContext
{
CveId = "CVE-2024-1234",
HasBackportEvidence = false,
EvidenceConfidence = 0.9
};
var precedence = lattice.GetPrecedence("debian", context);
Assert.Equal(20, precedence); // No boost applied
}
[Fact]
public void GetPrecedence_DoesNotApplyBackportBoost_ToNonDistroSources()
{
var lattice = CreateLattice();
var context = new BackportContext
{
CveId = "CVE-2024-1234",
HasBackportEvidence = true,
EvidenceConfidence = 0.9,
EvidenceTier = BackportEvidenceTier.DistroAdvisory
};
var precedence = lattice.GetPrecedence("nvd", context);
Assert.Equal(40, precedence); // No boost - not a distro source
}
[Fact]
public void GetPrecedence_LowerTierEvidence_RequiresHigherConfidence()
{
var lattice = CreateLattice();
// Tier 3 (PatchHeader) with 80% confidence - should not get boost
var lowConfidenceContext = new BackportContext
{
CveId = "CVE-2024-1234",
HasBackportEvidence = true,
EvidenceConfidence = 0.8,
EvidenceTier = BackportEvidenceTier.PatchHeader
};
// Tier 3 with 95% confidence - should get boost
var highConfidenceContext = new BackportContext
{
CveId = "CVE-2024-1234",
HasBackportEvidence = true,
EvidenceConfidence = 0.95,
EvidenceTier = BackportEvidenceTier.PatchHeader
};
var noBoost = lattice.GetPrecedence("debian", lowConfidenceContext);
var withBoost = lattice.GetPrecedence("debian", highConfidenceContext);
Assert.Equal(20, noBoost); // No boost - 80% < 90% required for tier 3
Assert.Equal(5, withBoost); // Boost applied - 95% >= 90%
}
[Fact]
public void Compare_DistroWithBackportBoost_TakesHigherPrecedence_ThanVendor()
{
var lattice = CreateLattice();
var context = new BackportContext
{
CveId = "CVE-2024-1234",
HasBackportEvidence = true,
EvidenceConfidence = 0.95,
EvidenceTier = BackportEvidenceTier.DistroAdvisory
};
// Without context, vendor-psirt (10) > debian (20)
var withoutContext = lattice.Compare("debian", "vendor-psirt");
Assert.Equal(SourceComparison.Source2Higher, withoutContext);
// With backport context, debian (20 - 15 = 5) > vendor-psirt (10)
var withContext = lattice.Compare("debian", "vendor-psirt", context);
Assert.Equal(SourceComparison.Source1Higher, withContext);
}
[Fact]
public void GetPrecedence_UsesCveSpecificOverride_WhenConfigured()
{
var config = new PrecedenceConfig
{
Overrides = new(StringComparer.OrdinalIgnoreCase)
{
["CVE-2024-9999:debian"] = 5
}
};
var lattice = CreateLattice(config);
var context = new BackportContext
{
CveId = "CVE-2024-9999",
HasBackportEvidence = false
};
var precedence = lattice.GetPrecedence("debian", context);
Assert.Equal(5, precedence); // Uses override, not default
}
[Fact]
public void GetPrecedence_CveOverride_TakesPrecedence_OverBackportBoost()
{
var config = new PrecedenceConfig
{
Overrides = new(StringComparer.OrdinalIgnoreCase)
{
["CVE-2024-9999:debian"] = 50 // Explicitly set lower precedence
}
};
var lattice = CreateLattice(config);
var context = new BackportContext
{
CveId = "CVE-2024-9999",
HasBackportEvidence = true,
EvidenceConfidence = 0.95,
EvidenceTier = BackportEvidenceTier.DistroAdvisory
};
var precedence = lattice.GetPrecedence("debian", context);
// Override takes precedence, boost not applied
Assert.Equal(50, precedence);
}
[Fact]
public void GetPrecedence_WithBackportBoostDisabled_DoesNotApplyBoost()
{
var config = new PrecedenceConfig
{
EnableBackportBoost = false
};
var lattice = CreateLattice(config);
var context = new BackportContext
{
CveId = "CVE-2024-1234",
HasBackportEvidence = true,
EvidenceConfidence = 0.95,
EvidenceTier = BackportEvidenceTier.DistroAdvisory
};
var precedence = lattice.GetPrecedence("debian", context);
Assert.Equal(20, precedence); // No boost - disabled in config
}
[Theory]
[InlineData("")]
[InlineData(" ")]
public void GetPrecedence_ThrowsOnInvalidSource(string source)
{
var lattice = CreateLattice();
Assert.Throws<ArgumentException>(() => lattice.GetPrecedence(source));
}
private ConfigurableSourcePrecedenceLattice CreateLattice(PrecedenceConfig? config = null)
{
var options = Microsoft.Extensions.Options.Options.Create(config ?? new PrecedenceConfig());
return new ConfigurableSourcePrecedenceLattice(options, _logger);
}
}
public sealed class PrecedenceExceptionRuleTests
{
[Theory]
[InlineData("CVE-2024-1234", "CVE-2024-1234", true)]
[InlineData("CVE-2024-1234", "CVE-2024-1235", false)]
[InlineData("CVE-2024-*", "CVE-2024-1234", true)]
[InlineData("CVE-2024-*", "CVE-2024-9999", true)]
[InlineData("CVE-2024-*", "CVE-2025-1234", false)]
[InlineData("CVE-*", "CVE-2024-1234", true)]
public void Matches_WorksWithPatterns(string pattern, string cveId, bool expected)
{
var rule = new PrecedenceExceptionRule
{
CvePattern = pattern,
Source = "debian",
Precedence = 5
};
var result = rule.Matches(cveId);
Assert.Equal(expected, result);
}
[Theory]
[InlineData("")]
[InlineData(null)]
[InlineData(" ")]
public void Matches_ReturnsFalse_ForInvalidCveId(string? cveId)
{
var rule = new PrecedenceExceptionRule
{
CvePattern = "CVE-2024-*",
Source = "debian",
Precedence = 5
};
var result = rule.Matches(cveId!);
Assert.False(result);
}
}
public sealed class ExtendedPrecedenceConfigTests
{
[Fact]
public void GetActiveRules_ReturnsOnlyActiveRules()
{
var config = new ExtendedPrecedenceConfig
{
ExceptionRules =
[
new PrecedenceExceptionRule { CvePattern = "CVE-2024-1234", Source = "debian", Precedence = 5, IsActive = true },
new PrecedenceExceptionRule { CvePattern = "CVE-2024-5678", Source = "debian", Precedence = 5, IsActive = false },
new PrecedenceExceptionRule { CvePattern = "CVE-2024-9999", Source = "debian", Precedence = 5, IsActive = true }
]
};
var activeRules = config.GetActiveRules().ToList();
Assert.Equal(2, activeRules.Count);
Assert.All(activeRules, r => Assert.True(r.IsActive));
}
[Fact]
public void FindMatchingRule_ReturnsFirstMatch()
{
var config = new ExtendedPrecedenceConfig
{
ExceptionRules =
[
new PrecedenceExceptionRule { CvePattern = "CVE-2024-*", Source = "debian", Precedence = 5, IsActive = true },
new PrecedenceExceptionRule { CvePattern = "CVE-2024-1234", Source = "debian", Precedence = 10, IsActive = true }
]
};
var rule = config.FindMatchingRule("CVE-2024-1234", "debian");
Assert.NotNull(rule);
Assert.Equal(5, rule.Precedence); // First matching rule
}
[Fact]
public void FindMatchingRule_IsCaseInsensitiveForSource()
{
var config = new ExtendedPrecedenceConfig
{
ExceptionRules =
[
new PrecedenceExceptionRule { CvePattern = "CVE-2024-1234", Source = "debian", Precedence = 5, IsActive = true }
]
};
var rule = config.FindMatchingRule("CVE-2024-1234", "DEBIAN");
Assert.NotNull(rule);
}
[Fact]
public void FindMatchingRule_ReturnsNull_WhenNoMatch()
{
var config = new ExtendedPrecedenceConfig
{
ExceptionRules =
[
new PrecedenceExceptionRule { CvePattern = "CVE-2024-1234", Source = "redhat", Precedence = 5, IsActive = true }
]
};
var rule = config.FindMatchingRule("CVE-2024-1234", "debian");
Assert.Null(rule);
}
}