451 lines
13 KiB
C#
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);
|
|
}
|
|
}
|