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 { private readonly PolicyEngineOptions _value; public StaticOptionsMonitor(PolicyEngineOptions value) => _value = value; public PolicyEngineOptions CurrentValue => _value; public PolicyEngineOptions Get(string? name) => _value; public IDisposable OnChange(Action listener) => NullDisposable.Instance; private sealed class NullDisposable : IDisposable { public static readonly NullDisposable Instance = new(); public void Dispose() { } } } }