Files
git.stella-ops.org/src/Policy/__Tests/StellaOps.Policy.Exceptions.Tests/EvidenceRequirementValidatorTests.cs

147 lines
5.2 KiB
C#

using System.Collections.Immutable;
using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using StellaOps.Policy.Exceptions.Models;
using StellaOps.Policy.Exceptions.Services;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Policy.Exceptions.Tests;
public sealed class EvidenceRequirementValidatorTests
{
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ValidateForApprovalAsync_NoHooks_ReturnsValid()
{
var validator = CreateValidator(new StubHookRegistry([]));
var exception = CreateException();
var result = await validator.ValidateForApprovalAsync(exception);
result.IsValid.Should().BeTrue();
result.MissingEvidence.Should().BeEmpty();
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ValidateForApprovalAsync_MissingEvidence_ReturnsInvalid()
{
var hooks = ImmutableArray.Create(new EvidenceHook
{
HookId = "hook-1",
Type = EvidenceType.FeatureFlagDisabled,
Description = "Feature flag disabled",
IsMandatory = true
});
var validator = CreateValidator(new StubHookRegistry(hooks));
var exception = CreateException();
var result = await validator.ValidateForApprovalAsync(exception);
result.IsValid.Should().BeFalse();
result.MissingEvidence.Should().HaveCount(1);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ValidateForApprovalAsync_TrustScoreTooLow_ReturnsInvalid()
{
var hooks = ImmutableArray.Create(new EvidenceHook
{
HookId = "hook-1",
Type = EvidenceType.BackportMerged,
Description = "Backport merged",
IsMandatory = true,
MinTrustScore = 0.8m
});
var validator = CreateValidator(
new StubHookRegistry(hooks),
trustScore: 0.5m);
var exception = CreateException(new EvidenceRequirements
{
Hooks = hooks,
SubmittedEvidence = ImmutableArray.Create(new SubmittedEvidence
{
EvidenceId = "e-1",
HookId = "hook-1",
Type = EvidenceType.BackportMerged,
Reference = "ref",
SubmittedAt = DateTimeOffset.UtcNow,
SubmittedBy = "tester",
ValidationStatus = EvidenceValidationStatus.Valid
})
});
var result = await validator.ValidateForApprovalAsync(exception);
result.IsValid.Should().BeFalse();
result.InvalidEvidence.Should().HaveCount(1);
}
private static EvidenceRequirementValidator CreateValidator(
IEvidenceHookRegistry registry,
decimal trustScore = 1.0m,
bool schemaValid = true,
bool signatureValid = true)
{
return new EvidenceRequirementValidator(
registry,
new StubAttestationVerifier(signatureValid),
new StubTrustScoreService(trustScore),
new StubSchemaValidator(schemaValid),
NullLogger<EvidenceRequirementValidator>.Instance);
}
private static ExceptionObject CreateException(EvidenceRequirements? requirements = null)
{
return new ExceptionObject
{
ExceptionId = "EXC-TEST",
Version = 1,
Status = ExceptionStatus.Active,
Type = ExceptionType.Vulnerability,
Scope = new ExceptionScope { VulnerabilityId = "CVE-2024-0001" },
OwnerId = "owner",
RequesterId = "requester",
CreatedAt = DateTimeOffset.UtcNow,
UpdatedAt = DateTimeOffset.UtcNow,
ExpiresAt = DateTimeOffset.UtcNow.AddDays(30),
ReasonCode = ExceptionReason.AcceptedRisk,
Rationale = "This rationale is long enough to satisfy the minimum character requirement.",
EvidenceRequirements = requirements
};
}
private sealed class StubHookRegistry(ImmutableArray<EvidenceHook> hooks) : IEvidenceHookRegistry
{
public Task<ImmutableArray<EvidenceHook>> GetRequiredHooksAsync(
ExceptionType exceptionType,
ExceptionScope scope,
CancellationToken ct = default) => Task.FromResult(hooks);
}
private sealed class StubAttestationVerifier(bool isValid) : IAttestationVerifier
{
public Task<EvidenceVerificationResult> VerifyAsync(string dsseEnvelope, CancellationToken ct = default) =>
Task.FromResult(new EvidenceVerificationResult(isValid, isValid ? null : "invalid"));
}
private sealed class StubTrustScoreService(decimal score) : ITrustScoreService
{
public Task<decimal> GetScoreAsync(string reference, CancellationToken ct = default) => Task.FromResult(score);
}
private sealed class StubSchemaValidator(bool isValid) : IEvidenceSchemaValidator
{
public Task<EvidenceSchemaValidationResult> ValidateAsync(
string schemaId,
string? content,
CancellationToken ct = default) =>
Task.FromResult(new EvidenceSchemaValidationResult(isValid, isValid ? null : "schema invalid"));
}
}