Files
git.stella-ops.org/src/Policy/__Tests/StellaOps.Policy.Engine.Tests/PolicyBundleServiceTests.cs
StellaOps Bot 05da719048
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
up
2025-11-28 09:41:08 +02:00

160 lines
6.7 KiB
C#

using Xunit;
using System.Collections.Immutable;
using Microsoft.Extensions.Options;
using StellaOps.Policy;
using StellaOps.Policy.Engine.Compilation;
using StellaOps.Policy.Engine.Domain;
using StellaOps.Policy.Engine.Options;
using StellaOps.Policy.Engine.Services;
using StellaOps.PolicyDsl;
namespace StellaOps.Policy.Engine.Tests;
public sealed class PolicyBundleServiceTests
{
private const string BaselineDsl = """
policy "Baseline Production Policy" syntax "stella-dsl@1" {
rule r1 { when true then status := "ok" because "baseline" }
}
""";
[Fact]
public async Task CompileAndStoreAsync_SucceedsAndStoresBundle()
{
var services = CreateServices();
var request = new PolicyBundleRequest(new PolicyDslPayload("stella-dsl@1", BaselineDsl), SigningKeyId: "test-key");
var response = await services.BundleService.CompileAndStoreAsync("pack-1", 1, request, CancellationToken.None);
Assert.True(response.Success);
Assert.NotNull(response.Digest);
Assert.StartsWith("sig:sha256:", response.Signature);
Assert.True(response.SizeBytes > 0);
}
[Fact]
public async Task CompileAndStoreAsync_FailsWithBadSyntax()
{
var services = CreateServices();
var request = new PolicyBundleRequest(new PolicyDslPayload("unknown", "policy bad"), SigningKeyId: null);
var response = await services.BundleService.CompileAndStoreAsync("pack-1", 1, request, CancellationToken.None);
Assert.False(response.Success);
Assert.Null(response.Digest);
Assert.NotEmpty(response.Diagnostics);
}
[Fact]
public async Task CompileAndStoreAsync_ReturnsAocMetadata()
{
var services = CreateServices();
var request = new PolicyBundleRequest(new PolicyDslPayload("stella-dsl@1", BaselineDsl), SigningKeyId: "test-key");
var response = await services.BundleService.CompileAndStoreAsync("pack-1", 1, request, CancellationToken.None);
Assert.True(response.Success);
Assert.NotNull(response.AocMetadata);
Assert.StartsWith("comp-", response.AocMetadata!.CompilationId);
Assert.Equal("stella-dsl@1", response.AocMetadata.CompilerVersion);
Assert.StartsWith("sha256:", response.AocMetadata.SourceDigest);
Assert.StartsWith("sha256:", response.AocMetadata.ArtifactDigest);
Assert.True(response.AocMetadata.RuleCount >= 1);
Assert.True(response.AocMetadata.ComplexityScore >= 0);
}
[Fact]
public async Task CompileAndStoreAsync_IncludesProvenanceWhenProvided()
{
var services = CreateServices();
var provenance = new PolicyProvenanceInput(
SourceType: "git",
SourceUrl: "https://github.com/test/policies",
Submitter: "test-user",
CommitSha: "abc123",
Branch: "main");
var request = new PolicyBundleRequest(
new PolicyDslPayload("stella-dsl@1", BaselineDsl),
SigningKeyId: "test-key",
Provenance: provenance);
var response = await services.BundleService.CompileAndStoreAsync("pack-1", 1, request, CancellationToken.None);
Assert.True(response.Success);
Assert.NotNull(response.AocMetadata);
// Verify bundle record has provenance stored
var bundle = await services.Repository.GetBundleAsync("pack-1", 1, CancellationToken.None);
Assert.NotNull(bundle);
Assert.NotNull(bundle!.AocMetadata);
Assert.NotNull(bundle.AocMetadata!.Provenance);
Assert.Equal("git", bundle.AocMetadata.Provenance!.SourceType);
Assert.Equal("https://github.com/test/policies", bundle.AocMetadata.Provenance.SourceUrl);
Assert.Equal("test-user", bundle.AocMetadata.Provenance.Submitter);
Assert.Equal("abc123", bundle.AocMetadata.Provenance.CommitSha);
Assert.Equal("main", bundle.AocMetadata.Provenance.Branch);
}
[Fact]
public async Task CompileAndStoreAsync_NullAocMetadataOnFailure()
{
var services = CreateServices();
var request = new PolicyBundleRequest(new PolicyDslPayload("unknown", "policy bad"), SigningKeyId: null);
var response = await services.BundleService.CompileAndStoreAsync("pack-1", 1, request, CancellationToken.None);
Assert.False(response.Success);
Assert.Null(response.AocMetadata);
}
[Fact]
public async Task CompileAndStoreAsync_SourceDigestIsDeterministic()
{
var services = CreateServices();
var request1 = new PolicyBundleRequest(new PolicyDslPayload("stella-dsl@1", BaselineDsl), SigningKeyId: "test-key");
var request2 = new PolicyBundleRequest(new PolicyDslPayload("stella-dsl@1", BaselineDsl), SigningKeyId: "test-key");
var response1 = await services.BundleService.CompileAndStoreAsync("pack-1", 1, request1, CancellationToken.None);
var response2 = await services.BundleService.CompileAndStoreAsync("pack-2", 1, request2, CancellationToken.None);
Assert.NotNull(response1.AocMetadata);
Assert.NotNull(response2.AocMetadata);
Assert.Equal(response1.AocMetadata!.SourceDigest, response2.AocMetadata!.SourceDigest);
Assert.Equal(response1.AocMetadata.ArtifactDigest, response2.AocMetadata.ArtifactDigest);
}
private static ServiceHarness CreateServices()
{
var compiler = new PolicyCompiler();
var complexity = new PolicyComplexityAnalyzer();
var options = Microsoft.Extensions.Options.Options.Create(new PolicyEngineOptions());
var metadataExtractor = new PolicyMetadataExtractor();
var compilationService = new PolicyCompilationService(compiler, complexity, metadataExtractor, new StaticOptionsMonitor(options.Value), TimeProvider.System);
var repo = new InMemoryPolicyPackRepository();
return new ServiceHarness(
new PolicyBundleService(compilationService, repo, TimeProvider.System),
repo);
}
private sealed record ServiceHarness(PolicyBundleService BundleService, InMemoryPolicyPackRepository Repository);
private sealed class StaticOptionsMonitor : IOptionsMonitor<PolicyEngineOptions>
{
private readonly PolicyEngineOptions _value;
public StaticOptionsMonitor(PolicyEngineOptions value) => _value = value;
public PolicyEngineOptions CurrentValue => _value;
public PolicyEngineOptions Get(string? name) => _value;
public IDisposable OnChange(Action<PolicyEngineOptions, string> listener) => NullDisposable.Instance;
private sealed class NullDisposable : IDisposable
{
public static readonly NullDisposable Instance = new();
public void Dispose() { }
}
}
}