feat(rate-limiting): Implement core rate limiting functionality with configuration, decision-making, metrics, middleware, and service registration
- Add RateLimitConfig for configuration management with YAML binding support. - Introduce RateLimitDecision to encapsulate the result of rate limit checks. - Implement RateLimitMetrics for OpenTelemetry metrics tracking. - Create RateLimitMiddleware for enforcing rate limits on incoming requests. - Develop RateLimitService to orchestrate instance and environment rate limit checks. - Add RateLimitServiceCollectionExtensions for dependency injection registration.
This commit is contained in:
@@ -0,0 +1,364 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// UnknownRankerTests.cs
|
||||
// Sprint: SPRINT_3600_0002_0001_unknowns_ranking_containment
|
||||
// Task: UNK-RANK-009 - Unit tests for ranking function
|
||||
// Description: Tests for unknown ranking determinism and edge cases
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using FluentAssertions;
|
||||
using StellaOps.Unknowns.Core.Models;
|
||||
using StellaOps.Unknowns.Core.Services;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Unknowns.Core.Tests.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for UnknownRanker.
|
||||
/// </summary>
|
||||
public class UnknownRankerTests
|
||||
{
|
||||
private readonly UnknownRanker _ranker = new();
|
||||
|
||||
#region Basic Ranking Tests
|
||||
|
||||
[Fact]
|
||||
public void Rank_HighBlastHighPressure_ReturnsHighScore()
|
||||
{
|
||||
// Arrange
|
||||
var blast = new BlastRadius(100, NetFacing: true, Privilege: "root");
|
||||
var pressure = new ExploitPressure(0.90, Kev: true);
|
||||
var containment = ContainmentSignals.Unknown;
|
||||
|
||||
// Act
|
||||
var score = _ranker.Rank(blast, scarcity: 0.8, pressure, containment);
|
||||
|
||||
// Assert - should be very high (close to 1.0)
|
||||
score.Should().BeGreaterOrEqualTo(0.8);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Rank_LowBlastLowPressure_ReturnsLowScore()
|
||||
{
|
||||
// Arrange
|
||||
var blast = new BlastRadius(1, NetFacing: false, Privilege: "none");
|
||||
var pressure = new ExploitPressure(0.01, Kev: false);
|
||||
var containment = ContainmentSignals.Unknown;
|
||||
|
||||
// Act
|
||||
var score = _ranker.Rank(blast, scarcity: 0.1, pressure, containment);
|
||||
|
||||
// Assert - should be low
|
||||
score.Should().BeLessThan(0.3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Rank_WithContainment_ReducesScore()
|
||||
{
|
||||
// Arrange
|
||||
var blast = new BlastRadius(50, NetFacing: true, Privilege: "user");
|
||||
var pressure = new ExploitPressure(0.5, Kev: false);
|
||||
var noContainment = ContainmentSignals.Unknown;
|
||||
var wellContained = ContainmentSignals.WellSandboxed;
|
||||
|
||||
// Act
|
||||
var scoreNoContainment = _ranker.Rank(blast, scarcity: 0.5, pressure, noContainment);
|
||||
var scoreWellContained = _ranker.Rank(blast, scarcity: 0.5, pressure, wellContained);
|
||||
|
||||
// Assert - containment should reduce score
|
||||
scoreWellContained.Should().BeLessThan(scoreNoContainment);
|
||||
(scoreNoContainment - scoreWellContained).Should().BeGreaterOrEqualTo(0.15); // At least 0.15 reduction
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Containment Signal Tests
|
||||
|
||||
[Fact]
|
||||
public void ContainmentSignals_SeccompEnforced_ProvidesDeduction()
|
||||
{
|
||||
// Arrange
|
||||
var containment = new ContainmentSignals("enforced", "rw");
|
||||
|
||||
// Act
|
||||
var deduction = containment.Deduction();
|
||||
|
||||
// Assert
|
||||
deduction.Should().BeApproximately(0.10, 0.001);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ContainmentSignals_ReadOnlyFs_ProvidesDeduction()
|
||||
{
|
||||
// Arrange
|
||||
var containment = new ContainmentSignals("disabled", "ro");
|
||||
|
||||
// Act
|
||||
var deduction = containment.Deduction();
|
||||
|
||||
// Assert
|
||||
deduction.Should().BeApproximately(0.10, 0.001);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ContainmentSignals_WellSandboxed_ProvidesMaxDeduction()
|
||||
{
|
||||
// Arrange
|
||||
var containment = ContainmentSignals.WellSandboxed; // seccomp=enforced, fs=ro, netpol=enforced, caps=20
|
||||
|
||||
// Act
|
||||
var deduction = containment.Deduction();
|
||||
|
||||
// Assert - should be significant
|
||||
deduction.Should().BeGreaterOrEqualTo(0.25);
|
||||
deduction.Should().BeLessOrEqualTo(0.30);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ContainmentSignals_Unknown_ProvidesNoDeduction()
|
||||
{
|
||||
// Arrange
|
||||
var containment = ContainmentSignals.Unknown;
|
||||
|
||||
// Act
|
||||
var deduction = containment.Deduction();
|
||||
|
||||
// Assert
|
||||
deduction.Should().Be(0);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Blast Radius Tests
|
||||
|
||||
[Fact]
|
||||
public void BlastRadius_HighDependents_IncreasesScore()
|
||||
{
|
||||
// Arrange
|
||||
var lowDeps = new BlastRadius(5, NetFacing: false, Privilege: "none");
|
||||
var highDeps = new BlastRadius(100, NetFacing: false, Privilege: "none");
|
||||
|
||||
// Act
|
||||
var lowScore = lowDeps.Score();
|
||||
var highScore = highDeps.Score();
|
||||
|
||||
// Assert
|
||||
highScore.Should().BeGreaterThan(lowScore);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BlastRadius_NetFacing_IncreasesScore()
|
||||
{
|
||||
// Arrange
|
||||
var notNetFacing = new BlastRadius(10, NetFacing: false, Privilege: "none");
|
||||
var netFacing = new BlastRadius(10, NetFacing: true, Privilege: "none");
|
||||
|
||||
// Act
|
||||
var notNetScore = notNetFacing.Score();
|
||||
var netScore = netFacing.Score();
|
||||
|
||||
// Assert
|
||||
netScore.Should().BeGreaterThan(notNetScore);
|
||||
(netScore - notNetScore).Should().BeApproximately(0.25, 0.01); // 0.5 / 2 = 0.25
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BlastRadius_RootPrivilege_IncreasesScore()
|
||||
{
|
||||
// Arrange
|
||||
var userPriv = new BlastRadius(10, NetFacing: false, Privilege: "user");
|
||||
var rootPriv = new BlastRadius(10, NetFacing: false, Privilege: "root");
|
||||
|
||||
// Act
|
||||
var userScore = userPriv.Score();
|
||||
var rootScore = rootPriv.Score();
|
||||
|
||||
// Assert
|
||||
rootScore.Should().BeGreaterThan(userScore);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Exploit Pressure Tests
|
||||
|
||||
[Fact]
|
||||
public void ExploitPressure_HighEpss_IncreasesScore()
|
||||
{
|
||||
// Arrange
|
||||
var lowEpss = new ExploitPressure(0.01, Kev: false);
|
||||
var highEpss = new ExploitPressure(0.90, Kev: false);
|
||||
|
||||
// Act
|
||||
var lowScore = lowEpss.Score();
|
||||
var highScore = highEpss.Score();
|
||||
|
||||
// Assert
|
||||
highScore.Should().BeGreaterThan(lowScore);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExploitPressure_Kev_IncreasesScore()
|
||||
{
|
||||
// Arrange
|
||||
var noKev = new ExploitPressure(0.5, Kev: false);
|
||||
var withKev = new ExploitPressure(0.5, Kev: true);
|
||||
|
||||
// Act
|
||||
var noKevScore = noKev.Score();
|
||||
var withKevScore = withKev.Score();
|
||||
|
||||
// Assert
|
||||
withKevScore.Should().BeGreaterThan(noKevScore);
|
||||
(withKevScore - noKevScore).Should().BeApproximately(0.30, 0.001);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExploitPressure_NullEpss_UsesDefault()
|
||||
{
|
||||
// Arrange
|
||||
var unknownEpss = ExploitPressure.Unknown;
|
||||
|
||||
// Act
|
||||
var score = unknownEpss.Score();
|
||||
|
||||
// Assert - should use 0.35 default
|
||||
score.Should().BeApproximately(0.35, 0.01);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Determinism Tests
|
||||
|
||||
[Fact]
|
||||
public void Rank_SameInputs_ReturnsSameScore()
|
||||
{
|
||||
// Arrange
|
||||
var blast = new BlastRadius(42, NetFacing: true, Privilege: "user");
|
||||
var pressure = new ExploitPressure(0.67, Kev: true);
|
||||
var containment = new ContainmentSignals("enforced", "ro");
|
||||
|
||||
// Act - rank multiple times
|
||||
var score1 = _ranker.Rank(blast, scarcity: 0.55, pressure, containment);
|
||||
var score2 = _ranker.Rank(blast, scarcity: 0.55, pressure, containment);
|
||||
var score3 = _ranker.Rank(blast, scarcity: 0.55, pressure, containment);
|
||||
|
||||
// Assert - all scores should be identical
|
||||
score1.Should().Be(score2);
|
||||
score2.Should().Be(score3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Rank_SlightlyDifferentInputs_ReturnsDifferentScores()
|
||||
{
|
||||
// Arrange
|
||||
var blast1 = new BlastRadius(42, NetFacing: true, Privilege: "user");
|
||||
var blast2 = new BlastRadius(43, NetFacing: true, Privilege: "user"); // Just 1 more dependent
|
||||
var pressure = new ExploitPressure(0.67, Kev: false);
|
||||
var containment = ContainmentSignals.Unknown;
|
||||
|
||||
// Act
|
||||
var score1 = _ranker.Rank(blast1, scarcity: 0.55, pressure, containment);
|
||||
var score2 = _ranker.Rank(blast2, scarcity: 0.55, pressure, containment);
|
||||
|
||||
// Assert - scores should be different
|
||||
score1.Should().NotBe(score2);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Boundary Tests
|
||||
|
||||
[Fact]
|
||||
public void Rank_AlwaysReturnsScoreInRange()
|
||||
{
|
||||
// Test many combinations to ensure score is always [0, 1]
|
||||
var testCases = new[]
|
||||
{
|
||||
(new BlastRadius(0, false, "none"), 0.0, new ExploitPressure(0, false), ContainmentSignals.Unknown),
|
||||
(new BlastRadius(1000, true, "root"), 1.0, new ExploitPressure(1.0, true), ContainmentSignals.Unknown),
|
||||
(new BlastRadius(50, true, "root"), 0.5, new ExploitPressure(0.5, true), ContainmentSignals.WellSandboxed),
|
||||
};
|
||||
|
||||
foreach (var (blast, scarcity, pressure, containment) in testCases)
|
||||
{
|
||||
var score = _ranker.Rank(blast, scarcity, pressure, containment);
|
||||
score.Should().BeInRange(0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Rank_NegativeValues_ClampedToZero()
|
||||
{
|
||||
// Arrange - minimal risk with high containment
|
||||
var blast = new BlastRadius(0, NetFacing: false, Privilege: "none");
|
||||
var pressure = new ExploitPressure(0, Kev: false);
|
||||
var containment = ContainmentSignals.WellSandboxed;
|
||||
|
||||
// Act
|
||||
var score = _ranker.Rank(blast, scarcity: 0, pressure, containment);
|
||||
|
||||
// Assert - should be clamped to 0, not negative
|
||||
score.Should().BeGreaterOrEqualTo(0);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Triage Band Tests
|
||||
|
||||
[Theory]
|
||||
[InlineData(0.9, "Hot")]
|
||||
[InlineData(0.7, "Hot")]
|
||||
[InlineData(0.5, "Warm")]
|
||||
[InlineData(0.4, "Warm")]
|
||||
[InlineData(0.3, "Cold")]
|
||||
[InlineData(0.1, "Cold")]
|
||||
public void ToTriageBand_ReturnsCorrectBand(double score, string expected)
|
||||
{
|
||||
// Act
|
||||
var band = score.ToTriageBand();
|
||||
|
||||
// Assert
|
||||
band.ToString().Should().Be(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0.9, "Critical")]
|
||||
[InlineData(0.8, "Critical")]
|
||||
[InlineData(0.7, "High")]
|
||||
[InlineData(0.6, "High")]
|
||||
[InlineData(0.5, "Medium")]
|
||||
[InlineData(0.3, "Low")]
|
||||
[InlineData(0.1, "Info")]
|
||||
public void ToPriorityLabel_ReturnsCorrectLabel(double score, string expected)
|
||||
{
|
||||
// Act
|
||||
var label = score.ToPriorityLabel();
|
||||
|
||||
// Assert
|
||||
label.Should().Be(expected);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Custom Weights Tests
|
||||
|
||||
[Fact]
|
||||
public void Rank_WithExploitFocusedWeights_PrioritizesExploitPressure()
|
||||
{
|
||||
// Arrange
|
||||
var rankerDefault = new UnknownRanker(RankingWeights.Default);
|
||||
var rankerExploitFocused = new UnknownRanker(RankingWeights.ExploitFocused);
|
||||
|
||||
var blast = new BlastRadius(10, NetFacing: false, Privilege: "none"); // Low blast
|
||||
var pressure = new ExploitPressure(0.95, Kev: true); // High pressure
|
||||
var containment = ContainmentSignals.Unknown;
|
||||
|
||||
// Act
|
||||
var scoreDefault = rankerDefault.Rank(blast, scarcity: 0.3, pressure, containment);
|
||||
var scoreExploitFocused = rankerExploitFocused.Rank(blast, scarcity: 0.3, pressure, containment);
|
||||
|
||||
// Assert - exploit-focused should rank this higher
|
||||
scoreExploitFocused.Should().BeGreaterThan(scoreDefault);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user