137 lines
4.8 KiB
C#
137 lines
4.8 KiB
C#
using System;
|
|
using Microsoft.Extensions.Options;
|
|
using StellaOps.Policy;
|
|
using StellaOps.PolicyDsl;
|
|
using StellaOps.Policy.Engine.Options;
|
|
using StellaOps.Policy.Engine.Services;
|
|
using Xunit;
|
|
|
|
namespace StellaOps.Policy.Engine.Tests;
|
|
|
|
public sealed class PolicyCompilationServiceTests
|
|
{
|
|
private const string SimplePolicy = """
|
|
policy "Sample" syntax "stella-dsl@1" {
|
|
rule block_high priority 10 {
|
|
when severity.normalized >= "High"
|
|
then status := "blocked"
|
|
because "Block high severity findings"
|
|
}
|
|
|
|
rule warn_medium priority 20 {
|
|
when severity.normalized >= "Medium"
|
|
then status := "warn"
|
|
because "Warn on medium severity findings"
|
|
}
|
|
}
|
|
""";
|
|
|
|
[Fact]
|
|
public void Compile_ReturnsComplexityReport_WhenWithinLimits()
|
|
{
|
|
var service = CreateService(maxComplexityScore: 1000, maxDurationMilliseconds: 1000, simulatedDurationMilliseconds: 12.3);
|
|
var request = new PolicyCompileRequest(new PolicyDslPayload("stella-dsl@1", SimplePolicy));
|
|
|
|
var result = service.Compile(request);
|
|
|
|
Assert.True(result.Success);
|
|
Assert.NotNull(result.Digest);
|
|
Assert.NotNull(result.Complexity);
|
|
Assert.True(result.Complexity!.Score > 0);
|
|
Assert.True(result.Complexity.RuleCount >= 2);
|
|
Assert.Equal(13, result.DurationMilliseconds);
|
|
Assert.True(result.Diagnostics.IsDefaultOrEmpty);
|
|
}
|
|
|
|
[Fact]
|
|
public void Compile_Fails_WhenComplexityExceedsThreshold()
|
|
{
|
|
var service = CreateService(maxComplexityScore: 1, maxDurationMilliseconds: 1000, simulatedDurationMilliseconds: 2);
|
|
var request = new PolicyCompileRequest(new PolicyDslPayload("stella-dsl@1", SimplePolicy));
|
|
|
|
var result = service.Compile(request);
|
|
|
|
Assert.False(result.Success);
|
|
Assert.NotNull(result.Complexity);
|
|
Assert.Equal(2, result.DurationMilliseconds);
|
|
var diagnostic = Assert.Single(result.Diagnostics);
|
|
Assert.Equal(PolicyEngineDiagnosticCodes.CompilationComplexityExceeded, diagnostic.Code);
|
|
Assert.Equal(PolicyIssueSeverity.Error, diagnostic.Severity);
|
|
}
|
|
|
|
[Fact]
|
|
public void Compile_Fails_WhenDurationExceedsThreshold()
|
|
{
|
|
var service = CreateService(maxComplexityScore: 1000, maxDurationMilliseconds: 1, simulatedDurationMilliseconds: 5.2);
|
|
var request = new PolicyCompileRequest(new PolicyDslPayload("stella-dsl@1", SimplePolicy));
|
|
|
|
var result = service.Compile(request);
|
|
|
|
Assert.False(result.Success);
|
|
Assert.NotNull(result.Complexity);
|
|
Assert.Equal(6, result.DurationMilliseconds);
|
|
var diagnostic = Assert.Single(result.Diagnostics);
|
|
Assert.Equal(PolicyEngineDiagnosticCodes.CompilationComplexityExceeded, diagnostic.Code);
|
|
}
|
|
|
|
private static PolicyCompilationService CreateService(double maxComplexityScore, int maxDurationMilliseconds, double simulatedDurationMilliseconds)
|
|
{
|
|
var compiler = new PolicyCompiler();
|
|
var analyzer = new PolicyComplexityAnalyzer();
|
|
var options = new PolicyEngineOptions();
|
|
options.Compilation.MaxComplexityScore = maxComplexityScore;
|
|
options.Compilation.MaxDurationMilliseconds = maxDurationMilliseconds;
|
|
var optionsMonitor = new StaticOptionsMonitor<PolicyEngineOptions>(options);
|
|
var timeProvider = new FakeTimeProvider(simulatedDurationMilliseconds);
|
|
return new PolicyCompilationService(compiler, analyzer, optionsMonitor, timeProvider);
|
|
}
|
|
|
|
private sealed class StaticOptionsMonitor<T> : IOptionsMonitor<T>
|
|
where T : class
|
|
{
|
|
public StaticOptionsMonitor(T value)
|
|
{
|
|
CurrentValue = value ?? throw new ArgumentNullException(nameof(value));
|
|
}
|
|
|
|
public T CurrentValue { get; }
|
|
|
|
public T Get(string? name) => CurrentValue;
|
|
|
|
public IDisposable OnChange(Action<T, string> listener) => Disposable.Instance;
|
|
|
|
private sealed class Disposable : IDisposable
|
|
{
|
|
public static readonly Disposable Instance = new();
|
|
public void Dispose()
|
|
{
|
|
}
|
|
}
|
|
}
|
|
|
|
private sealed class FakeTimeProvider : TimeProvider
|
|
{
|
|
private readonly long elapsedCounts;
|
|
private readonly long frequency = 1_000_000;
|
|
private bool firstCall = true;
|
|
|
|
public FakeTimeProvider(double milliseconds)
|
|
{
|
|
elapsedCounts = (long)Math.Round(milliseconds * frequency / 1000d);
|
|
}
|
|
|
|
public override long GetTimestamp()
|
|
{
|
|
if (firstCall)
|
|
{
|
|
firstCall = false;
|
|
return 0;
|
|
}
|
|
|
|
return elapsedCounts;
|
|
}
|
|
|
|
public override long TimestampFrequency => frequency;
|
|
}
|
|
}
|