- Added IExceptionEffectRegistry interface and its implementation ExceptionEffectRegistry to manage exception effects based on type and reason. - Created ExceptionAwareEvaluationService for evaluating policies with automatic exception loading from the repository. - Developed unit tests for ExceptionAdapter and ExceptionEffectRegistry to ensure correct behavior and mappings of exceptions and effects. - Enhanced exception loading logic to filter expired and non-active exceptions, and to respect maximum exceptions limit. - Implemented caching mechanism in ExceptionAdapter to optimize repeated exception loading.
224 lines
7.4 KiB
C#
224 lines
7.4 KiB
C#
using FluentAssertions;
|
|
using StellaOps.Policy.Engine.Adapters;
|
|
using StellaOps.Policy.Exceptions.Models;
|
|
using Xunit;
|
|
|
|
namespace StellaOps.Policy.Engine.Tests.Adapters;
|
|
|
|
/// <summary>
|
|
/// Unit tests for ExceptionEffectRegistry.
|
|
/// </summary>
|
|
public sealed class ExceptionEffectRegistryTests
|
|
{
|
|
private readonly IExceptionEffectRegistry _registry;
|
|
|
|
public ExceptionEffectRegistryTests()
|
|
{
|
|
_registry = new ExceptionEffectRegistry();
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(ExceptionType.Vulnerability, ExceptionReason.FalsePositive, PolicyExceptionEffectType.Suppress)]
|
|
[InlineData(ExceptionType.Vulnerability, ExceptionReason.AcceptedRisk, PolicyExceptionEffectType.Suppress)]
|
|
[InlineData(ExceptionType.Vulnerability, ExceptionReason.CompensatingControl, PolicyExceptionEffectType.RequireControl)]
|
|
[InlineData(ExceptionType.Vulnerability, ExceptionReason.TestOnly, PolicyExceptionEffectType.Suppress)]
|
|
[InlineData(ExceptionType.Vulnerability, ExceptionReason.VendorNotAffected, PolicyExceptionEffectType.Suppress)]
|
|
[InlineData(ExceptionType.Vulnerability, ExceptionReason.ScheduledFix, PolicyExceptionEffectType.Defer)]
|
|
[InlineData(ExceptionType.Vulnerability, ExceptionReason.RuntimeMitigation, PolicyExceptionEffectType.Downgrade)]
|
|
[InlineData(ExceptionType.Vulnerability, ExceptionReason.NetworkIsolation, PolicyExceptionEffectType.Downgrade)]
|
|
public void GetEffect_ReturnsCorrectEffect_ForVulnerabilityType(
|
|
ExceptionType type,
|
|
ExceptionReason reason,
|
|
PolicyExceptionEffectType expectedEffect)
|
|
{
|
|
// Act
|
|
var effect = _registry.GetEffect(type, reason);
|
|
|
|
// Assert
|
|
effect.Should().NotBeNull();
|
|
effect.Effect.Should().Be(expectedEffect);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(ExceptionType.Policy, ExceptionReason.FalsePositive, PolicyExceptionEffectType.Suppress)]
|
|
[InlineData(ExceptionType.Policy, ExceptionReason.AcceptedRisk, PolicyExceptionEffectType.Suppress)]
|
|
[InlineData(ExceptionType.Policy, ExceptionReason.CompensatingControl, PolicyExceptionEffectType.RequireControl)]
|
|
[InlineData(ExceptionType.Policy, ExceptionReason.ScheduledFix, PolicyExceptionEffectType.Defer)]
|
|
public void GetEffect_ReturnsCorrectEffect_ForPolicyType(
|
|
ExceptionType type,
|
|
ExceptionReason reason,
|
|
PolicyExceptionEffectType expectedEffect)
|
|
{
|
|
// Act
|
|
var effect = _registry.GetEffect(type, reason);
|
|
|
|
// Assert
|
|
effect.Should().NotBeNull();
|
|
effect.Effect.Should().Be(expectedEffect);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(ExceptionType.Unknown, ExceptionReason.FalsePositive, PolicyExceptionEffectType.Suppress)]
|
|
[InlineData(ExceptionType.Unknown, ExceptionReason.ScheduledFix, PolicyExceptionEffectType.Defer)]
|
|
public void GetEffect_ReturnsCorrectEffect_ForUnknownType(
|
|
ExceptionType type,
|
|
ExceptionReason reason,
|
|
PolicyExceptionEffectType expectedEffect)
|
|
{
|
|
// Act
|
|
var effect = _registry.GetEffect(type, reason);
|
|
|
|
// Assert
|
|
effect.Should().NotBeNull();
|
|
effect.Effect.Should().Be(expectedEffect);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(ExceptionType.Component, ExceptionReason.DeprecationInProgress, PolicyExceptionEffectType.Suppress)]
|
|
[InlineData(ExceptionType.Component, ExceptionReason.Other, PolicyExceptionEffectType.Suppress)] // License waiver
|
|
public void GetEffect_ReturnsCorrectEffect_ForComponentType(
|
|
ExceptionType type,
|
|
ExceptionReason reason,
|
|
PolicyExceptionEffectType expectedEffect)
|
|
{
|
|
// Act
|
|
var effect = _registry.GetEffect(type, reason);
|
|
|
|
// Assert
|
|
effect.Should().NotBeNull();
|
|
effect.Effect.Should().Be(expectedEffect);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetEffect_ReturnsDefaultDeferral_ForUnmappedCombination()
|
|
{
|
|
// Note: All combinations are mapped, so we test a hypothetical case
|
|
// by checking that the registry handles all known combinations
|
|
var allTypes = Enum.GetValues<ExceptionType>();
|
|
var allReasons = Enum.GetValues<ExceptionReason>();
|
|
|
|
foreach (var type in allTypes)
|
|
{
|
|
foreach (var reason in allReasons)
|
|
{
|
|
// Act
|
|
var effect = _registry.GetEffect(type, reason);
|
|
|
|
// Assert - should never be null
|
|
effect.Should().NotBeNull();
|
|
effect.Id.Should().NotBeNullOrEmpty();
|
|
effect.Effect.Should().BeOneOf(
|
|
PolicyExceptionEffectType.Suppress,
|
|
PolicyExceptionEffectType.Defer,
|
|
PolicyExceptionEffectType.Downgrade,
|
|
PolicyExceptionEffectType.RequireControl);
|
|
}
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void GetAllEffects_ReturnsDistinctEffects()
|
|
{
|
|
// Act
|
|
var allEffects = _registry.GetAllEffects();
|
|
|
|
// Assert
|
|
allEffects.Should().NotBeEmpty();
|
|
allEffects.Should().OnlyHaveUniqueItems(e => e.Id);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetEffectById_ReturnsEffect_WhenExists()
|
|
{
|
|
// Act
|
|
var effect = _registry.GetEffectById("suppress");
|
|
|
|
// Assert
|
|
effect.Should().NotBeNull();
|
|
effect!.Id.Should().Be("suppress");
|
|
effect.Effect.Should().Be(PolicyExceptionEffectType.Suppress);
|
|
}
|
|
|
|
[Fact]
|
|
public void GetEffectById_ReturnsNull_WhenNotExists()
|
|
{
|
|
// Act
|
|
var effect = _registry.GetEffectById("non-existent-effect-id");
|
|
|
|
// Assert
|
|
effect.Should().BeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public void GetEffectById_IsCaseInsensitive()
|
|
{
|
|
// Act
|
|
var effect1 = _registry.GetEffectById("SUPPRESS");
|
|
var effect2 = _registry.GetEffectById("suppress");
|
|
var effect3 = _registry.GetEffectById("Suppress");
|
|
|
|
// Assert
|
|
effect1.Should().Be(effect2);
|
|
effect2.Should().Be(effect3);
|
|
}
|
|
|
|
[Fact]
|
|
public void Effects_HaveValidProperties()
|
|
{
|
|
// Act
|
|
var allEffects = _registry.GetAllEffects();
|
|
|
|
// Assert
|
|
foreach (var effect in allEffects)
|
|
{
|
|
effect.Id.Should().NotBeNullOrWhiteSpace();
|
|
effect.Name.Should().NotBeNullOrWhiteSpace();
|
|
effect.Description.Should().NotBeNullOrWhiteSpace();
|
|
effect.MaxDurationDays.Should().BeGreaterThan(0);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void DowngradeEffects_HaveValidSeverity()
|
|
{
|
|
// Act
|
|
var downgradeEffects = _registry.GetAllEffects()
|
|
.Where(e => e.Effect == PolicyExceptionEffectType.Downgrade);
|
|
|
|
// Assert
|
|
foreach (var effect in downgradeEffects)
|
|
{
|
|
effect.DowngradeSeverity.Should().NotBeNull();
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void RequireControlEffects_HaveControlId()
|
|
{
|
|
// Act
|
|
var requireControlEffects = _registry.GetAllEffects()
|
|
.Where(e => e.Effect == PolicyExceptionEffectType.RequireControl);
|
|
|
|
// Assert
|
|
foreach (var effect in requireControlEffects)
|
|
{
|
|
effect.RequiredControlId.Should().NotBeNullOrWhiteSpace();
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void SuppressEffects_DoNotRequireControl()
|
|
{
|
|
// Act
|
|
var suppressEffects = _registry.GetAllEffects()
|
|
.Where(e => e.Effect == PolicyExceptionEffectType.Suppress);
|
|
|
|
// Assert
|
|
foreach (var effect in suppressEffects)
|
|
{
|
|
// Suppress effects should not require controls
|
|
effect.RequiredControlId.Should().BeNull();
|
|
}
|
|
}
|
|
}
|