old sprints work, new sprints for exposing functionality via cli, improve code_of_conduct and other agents instructions

This commit is contained in:
master
2026-01-15 18:37:59 +02:00
parent c631bacee2
commit 88a85cdd92
208 changed files with 32271 additions and 2287 deletions

View File

@@ -0,0 +1,352 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (c) 2025 StellaOps
// Sprint: SPRINT_20260112_004_BE_findings_scoring_attested_reduction (EWS-API-004)
// Task: Unit tests for attested-reduction response fields
using FluentAssertions;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging.Abstractions;
using MsOptions = Microsoft.Extensions.Options;
using Moq;
using StellaOps.Findings.Ledger.WebService.Contracts;
using StellaOps.Findings.Ledger.WebService.Services;
using StellaOps.Signals.EvidenceWeightedScore;
using StellaOps.Signals.EvidenceWeightedScore.Normalizers;
using Xunit;
namespace StellaOps.Findings.Ledger.Tests.Services;
[Trait("Category", "Unit")]
public class FindingScoringServiceTests
{
private readonly Mock<INormalizerAggregator> _normalizer = new();
private readonly Mock<IEvidenceWeightedScoreCalculator> _calculator = new();
private readonly Mock<IEvidenceWeightPolicyProvider> _policyProvider = new();
private readonly Mock<IFindingEvidenceProvider> _evidenceProvider = new();
private readonly Mock<IScoreHistoryStore> _historyStore = new();
private readonly Mock<TimeProvider> _timeProvider = new();
private readonly IMemoryCache _cache;
private readonly FindingScoringService _service;
private readonly DateTimeOffset _now = new(2026, 1, 14, 12, 0, 0, TimeSpan.Zero);
public FindingScoringServiceTests()
{
_cache = new MemoryCache(new MemoryCacheOptions());
var options = MsOptions.Options.Create(new FindingScoringOptions
{
CacheTtlMinutes = 60,
MaxBatchSize = 100,
MaxConcurrency = 10
});
_timeProvider.Setup(tp => tp.GetUtcNow()).Returns(_now);
_service = new FindingScoringService(
_normalizer.Object,
_calculator.Object,
_policyProvider.Object,
_evidenceProvider.Object,
_historyStore.Object,
_cache,
options,
NullLogger<FindingScoringService>.Instance,
_timeProvider.Object);
}
#region Attested Reduction Response Fields Tests
[Fact]
public async Task CalculateScoreAsync_AttestedReductionEnabled_PopulatesReductionProfile()
{
// Arrange
var findingId = "CVE-2024-1234@pkg:npm/lodash@4.17.20";
var evidence = CreateFindingEvidence(findingId);
var policy = CreateAttestedReductionPolicy(enabled: true);
var input = CreateEvidenceWeightedScoreInput(findingId);
var result = CreateScoreResult(findingId, withAttestedReduction: true);
SetupMocks(evidence, policy, input, result);
// Act
var response = await _service.CalculateScoreAsync(
findingId,
new CalculateScoreRequest { IncludeBreakdown = true },
CancellationToken.None);
// Assert
response.Should().NotBeNull();
response!.ReductionProfile.Should().NotBeNull();
response.ReductionProfile!.Enabled.Should().BeTrue();
response.ReductionProfile.Mode.Should().NotBeNullOrEmpty();
response.ReductionProfile.MaxReductionPercent.Should().BeGreaterThan(0);
}
[Fact]
public async Task CalculateScoreAsync_HardFailTriggered_SetsHardFailTrue()
{
// Arrange
var findingId = "CVE-2024-9999@pkg:npm/critical@1.0.0";
var evidence = CreateFindingEvidence(findingId);
var policy = CreateAttestedReductionPolicy(enabled: true);
var input = CreateEvidenceWeightedScoreInput(findingId);
var result = CreateScoreResult(findingId, withHardFail: true);
SetupMocks(evidence, policy, input, result);
// Act
var response = await _service.CalculateScoreAsync(
findingId,
new CalculateScoreRequest { IncludeBreakdown = true },
CancellationToken.None);
// Assert
response.Should().NotBeNull();
response!.HardFail.Should().BeTrue();
response.ShortCircuitReason.Should().Be("anchored_affected_runtime_confirmed");
}
[Fact]
public async Task CalculateScoreAsync_AnchoredVexNotAffected_SetsShortCircuitReason()
{
// Arrange
var findingId = "CVE-2024-5555@pkg:npm/not-affected@1.0.0";
var evidence = CreateFindingEvidenceWithAnchor(findingId);
var policy = CreateAttestedReductionPolicy(enabled: true);
var input = CreateEvidenceWeightedScoreInput(findingId);
var result = CreateScoreResult(findingId, withAnchoredVex: true, score: 0);
SetupMocks(evidence, policy, input, result);
// Act
var response = await _service.CalculateScoreAsync(
findingId,
new CalculateScoreRequest { IncludeBreakdown = true },
CancellationToken.None);
// Assert
response.Should().NotBeNull();
response!.Score.Should().Be(0);
response.ShortCircuitReason.Should().Be("anchored_vex_not_affected");
response.HardFail.Should().BeFalse();
}
[Fact]
public async Task CalculateScoreAsync_WithAnchor_PopulatesAnchorDto()
{
// Arrange
var findingId = "CVE-2024-1111@pkg:npm/anchored@2.0.0";
var evidence = CreateFindingEvidenceWithAnchor(findingId);
var policy = CreateAttestedReductionPolicy(enabled: true);
var input = CreateEvidenceWeightedScoreInput(findingId);
var result = CreateScoreResult(findingId);
SetupMocks(evidence, policy, input, result);
// Act
var response = await _service.CalculateScoreAsync(
findingId,
new CalculateScoreRequest { IncludeBreakdown = true },
CancellationToken.None);
// Assert
response.Should().NotBeNull();
response!.Anchor.Should().NotBeNull();
response.Anchor!.Anchored.Should().BeTrue();
response.Anchor.EnvelopeDigest.Should().Be("sha256:abc123");
response.Anchor.RekorLogIndex.Should().Be(12345);
}
[Fact]
public async Task CalculateScoreAsync_NoReductionProfile_ReturnsNullReductionProfile()
{
// Arrange
var findingId = "CVE-2024-2222@pkg:npm/standard@1.0.0";
var evidence = CreateFindingEvidence(findingId);
var policy = CreateStandardPolicy(); // No attested reduction
var input = CreateEvidenceWeightedScoreInput(findingId);
var result = CreateScoreResult(findingId);
SetupMocks(evidence, policy, input, result);
// Act
var response = await _service.CalculateScoreAsync(
findingId,
new CalculateScoreRequest { IncludeBreakdown = true },
CancellationToken.None);
// Assert
response.Should().NotBeNull();
response!.ReductionProfile.Should().BeNull();
response.HardFail.Should().BeFalse();
response.ShortCircuitReason.Should().BeNull();
}
[Fact]
public async Task CalculateScoreAsync_NoEvidence_ReturnsNull()
{
// Arrange
var findingId = "CVE-2024-0000@pkg:npm/missing@1.0.0";
_evidenceProvider.Setup(p => p.GetEvidenceAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((FindingEvidence?)null);
// Act
var response = await _service.CalculateScoreAsync(
findingId,
new CalculateScoreRequest(),
CancellationToken.None);
// Assert
response.Should().BeNull();
}
#endregion
#region Cache Key Tests
[Fact]
public async Task CalculateScoreAsync_DifferentPolicies_UseDifferentCacheKeys()
{
// Arrange
var findingId = "CVE-2024-3333@pkg:npm/cached@1.0.0";
var evidence = CreateFindingEvidence(findingId);
var policy1 = CreateAttestedReductionPolicy(enabled: true);
var policy2 = CreateAttestedReductionPolicy(enabled: false);
var input = CreateEvidenceWeightedScoreInput(findingId);
var result1 = CreateScoreResult(findingId, withAttestedReduction: true, score: 25);
var result2 = CreateScoreResult(findingId, score: 75);
// First call with reduction enabled
SetupMocks(evidence, policy1, input, result1);
var response1 = await _service.CalculateScoreAsync(
findingId,
new CalculateScoreRequest { ForceRecalculate = true },
CancellationToken.None);
// Change policy to disabled
SetupMocks(evidence, policy2, input, result2);
var response2 = await _service.CalculateScoreAsync(
findingId,
new CalculateScoreRequest { ForceRecalculate = true },
CancellationToken.None);
// Assert - different scores due to different cache keys
response1!.Score.Should().NotBe(response2!.Score);
}
#endregion
#region Helper Methods
private void SetupMocks(
FindingEvidence evidence,
EvidenceWeightPolicy policy,
EvidenceWeightedScoreInput input,
EvidenceWeightedScoreResult result)
{
_evidenceProvider.Setup(p => p.GetEvidenceAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(evidence);
_policyProvider.Setup(p => p.GetDefaultPolicyAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(policy);
_normalizer.Setup(n => n.Aggregate(It.IsAny<FindingEvidence>()))
.Returns(input);
_calculator.Setup(c => c.Calculate(It.IsAny<EvidenceWeightedScoreInput>(), It.IsAny<EvidenceWeightPolicy>()))
.Returns(result);
}
private static FindingEvidence CreateFindingEvidence(string findingId) => new()
{
FindingId = findingId
};
private static FindingEvidence CreateFindingEvidenceWithAnchor(string findingId) => new()
{
FindingId = findingId,
Anchor = new EvidenceAnchor
{
Anchored = true,
EnvelopeDigest = "sha256:abc123",
PredicateType = "https://stellaops.io/attestation/vex/v1",
RekorLogIndex = 12345,
RekorEntryId = "entry-123",
Scope = "finding",
Verified = true,
AttestedAt = DateTimeOffset.UtcNow.AddHours(-1)
}
};
private static EvidenceWeightedScoreInput CreateEvidenceWeightedScoreInput(string findingId) => new()
{
FindingId = findingId,
Rch = 0.5,
Rts = 0.3,
Bkp = 0.0,
Xpl = 0.4,
Src = 0.6,
Mit = 0.1
};
private static EvidenceWeightPolicy CreateAttestedReductionPolicy(bool enabled) => new()
{
Version = "1.0.0",
Profile = "test",
CreatedAt = DateTimeOffset.UtcNow,
Weights = EvidenceWeights.Default,
Guardrails = GuardrailConfig.Default,
Buckets = BucketThresholds.Default,
AttestedReduction = enabled
? AttestedReductionConfig.EnabledDefault
: AttestedReductionConfig.Default
};
private static EvidenceWeightPolicy CreateStandardPolicy() => new()
{
Version = "1.0.0",
Profile = "standard",
CreatedAt = DateTimeOffset.UtcNow,
Weights = EvidenceWeights.Default,
Guardrails = GuardrailConfig.Default,
Buckets = BucketThresholds.Default,
AttestedReduction = AttestedReductionConfig.Default
};
private EvidenceWeightedScoreResult CreateScoreResult(
string findingId,
bool withAttestedReduction = false,
bool withHardFail = false,
bool withAnchoredVex = false,
int score = 50)
{
var flags = new List<string>();
if (withAttestedReduction) flags.Add("attested-reduction");
if (withHardFail)
{
flags.Add("hard-fail");
flags.Add("anchored-vex");
flags.Add("anchored-runtime");
}
if (withAnchoredVex)
{
flags.Add("anchored-vex");
flags.Add("vendor-na");
}
return new EvidenceWeightedScoreResult
{
FindingId = findingId,
Score = score,
Bucket = score >= 90 ? ScoreBucket.ActNow :
score >= 70 ? ScoreBucket.ScheduleNext :
score >= 40 ? ScoreBucket.Investigate : ScoreBucket.Watchlist,
Inputs = new EvidenceInputValues(0.5, 0.3, 0.0, 0.4, 0.6, 0.1),
Weights = EvidenceWeights.Default,
Breakdown = [],
Flags = flags,
Explanations = ["Test explanation"],
Caps = AppliedGuardrails.None(score),
PolicyDigest = "sha256:policy123",
CalculatedAt = _now
};
}
#endregion
}