using System; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Time.Testing; using Xunit; namespace StellaOps.Policy.Tests; public sealed class PolicySnapshotStoreTests { private const string BasePolicyYaml = """ version: "1.0" rules: - name: Block Critical severity: [Critical] action: block """; [Fact] public async Task SaveAsync_CreatesNewSnapshotAndAuditEntry() { var snapshotRepo = new InMemoryPolicySnapshotRepository(); var auditRepo = new InMemoryPolicyAuditRepository(); var timeProvider = new FakeTimeProvider(new DateTimeOffset(2025, 10, 18, 10, 0, 0, TimeSpan.Zero)); var store = new PolicySnapshotStore(snapshotRepo, auditRepo, timeProvider, NullLogger.Instance); var content = new PolicySnapshotContent(BasePolicyYaml, PolicyDocumentFormat.Yaml, "cli", "test", null); var result = await store.SaveAsync(content, CancellationToken.None); Assert.True(result.Success); Assert.True(result.Created); Assert.NotNull(result.Snapshot); Assert.Equal("rev-1", result.Snapshot!.RevisionId); Assert.Equal(result.Digest, result.Snapshot.Digest); Assert.Equal(timeProvider.GetUtcNow(), result.Snapshot.CreatedAt); Assert.Equal(PolicyScoringConfig.Default.Version, result.Snapshot.ScoringConfig.Version); var latest = await store.GetLatestAsync(); Assert.Equal(result.Snapshot, latest); var audits = await auditRepo.ListAsync(10); Assert.Single(audits); Assert.Equal(result.Digest, audits[0].Digest); Assert.Equal("snapshot.created", audits[0].Action); Assert.Equal("rev-1", audits[0].RevisionId); } [Fact] public async Task SaveAsync_DoesNotCreateNewRevisionWhenDigestUnchanged() { var snapshotRepo = new InMemoryPolicySnapshotRepository(); var auditRepo = new InMemoryPolicyAuditRepository(); var timeProvider = new FakeTimeProvider(new DateTimeOffset(2025, 10, 18, 10, 0, 0, TimeSpan.Zero)); var store = new PolicySnapshotStore(snapshotRepo, auditRepo, timeProvider, NullLogger.Instance); var content = new PolicySnapshotContent(BasePolicyYaml, PolicyDocumentFormat.Yaml, "cli", "test", null); var first = await store.SaveAsync(content, CancellationToken.None); Assert.True(first.Created); timeProvider.Advance(TimeSpan.FromHours(1)); var second = await store.SaveAsync(content, CancellationToken.None); Assert.True(second.Success); Assert.False(second.Created); Assert.Equal(first.Digest, second.Digest); Assert.Equal("rev-1", second.Snapshot!.RevisionId); Assert.Equal(PolicyScoringConfig.Default.Version, second.Snapshot.ScoringConfig.Version); var audits = await auditRepo.ListAsync(10); Assert.Single(audits); } [Fact] public async Task SaveAsync_ReturnsFailureWhenValidationFails() { var snapshotRepo = new InMemoryPolicySnapshotRepository(); var auditRepo = new InMemoryPolicyAuditRepository(); var store = new PolicySnapshotStore(snapshotRepo, auditRepo, TimeProvider.System, NullLogger.Instance); const string invalidYaml = "version: '1.0'\nrules: []"; var content = new PolicySnapshotContent(invalidYaml, PolicyDocumentFormat.Yaml, null, null, null); var result = await store.SaveAsync(content, CancellationToken.None); Assert.False(result.Success); Assert.False(result.Created); Assert.Null(result.Snapshot); var audits = await auditRepo.ListAsync(5); Assert.Empty(audits); } }