147 lines
5.2 KiB
C#
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"));
|
|
}
|
|
}
|