// ----------------------------------------------------------------------------- // PolicyDecisionAttestationServiceTests.cs // Sprint: SPRINT_3801_0001_0001_policy_decision_attestation // Description: Unit tests for PolicyDecisionAttestationService. // ----------------------------------------------------------------------------- using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Moq; using StellaOps.Policy.Engine.Attestation; using StellaOps.Policy.Engine.Vex; using Xunit; namespace StellaOps.Policy.Engine.Tests.Attestation; public class PolicyDecisionAttestationServiceTests { private readonly Mock> _optionsMock; private readonly Mock _signerClientMock; private readonly Mock _rekorClientMock; private readonly PolicyDecisionAttestationService _service; public PolicyDecisionAttestationServiceTests() { _optionsMock = new Mock>(); _optionsMock.Setup(x => x.CurrentValue).Returns(new PolicyDecisionAttestationOptions { Enabled = true, UseSignerService = true, DefaultTtlHours = 24 }); _signerClientMock = new Mock(); _rekorClientMock = new Mock(); _service = new PolicyDecisionAttestationService( _signerClientMock.Object, _rekorClientMock.Object, _optionsMock.Object, TimeProvider.System, NullLogger.Instance); } [Fact] public async Task CreateAttestationAsync_WhenDisabled_ReturnsFailure() { // Arrange _optionsMock.Setup(x => x.CurrentValue).Returns(new PolicyDecisionAttestationOptions { Enabled = false }); var request = CreateTestRequest(); // Act var result = await _service.CreateAttestationAsync(request); // Assert Assert.False(result.Success); Assert.Contains("disabled", result.Error, StringComparison.OrdinalIgnoreCase); } [Fact] public async Task CreateAttestationAsync_WithSignerClient_CallsSigner() { // Arrange _signerClientMock.Setup(x => x.SignAsync( It.IsAny(), It.IsAny())) .ReturnsAsync(new VexSignerResult { Success = true, Signature = "AQID", KeyId = "key-1" }); var request = CreateTestRequest(); // Act var result = await _service.CreateAttestationAsync(request); // Assert Assert.True(result.Success); Assert.NotNull(result.AttestationDigest); Assert.Matches("^sha256:[a-f0-9]{64}$", result.AttestationDigest); Assert.Equal("key-1", result.KeyId); _signerClientMock.Verify(x => x.SignAsync( It.Is(r => r.PayloadType == "stella.ops/policy-decision@v1"), It.IsAny()), Times.Once); } [Fact] public async Task CreateAttestationAsync_WhenSigningFails_ReturnsFailure() { // Arrange _signerClientMock.Setup(x => x.SignAsync( It.IsAny(), It.IsAny())) .ReturnsAsync(new VexSignerResult { Success = false, Error = "Key not found" }); var request = CreateTestRequest(); // Act var result = await _service.CreateAttestationAsync(request); // Assert Assert.False(result.Success); Assert.Contains("Key not found", result.Error); } [Fact] public async Task CreateAttestationAsync_WithRekorSubmission_SubmitsToRekor() { // Arrange _signerClientMock.Setup(x => x.SignAsync( It.IsAny(), It.IsAny())) .ReturnsAsync(new VexSignerResult { Success = true, Signature = "AQID", KeyId = "key-1" }); _rekorClientMock.Setup(x => x.SubmitAsync( It.IsAny(), It.IsAny())) .ReturnsAsync(new VexRekorSubmitResult { Success = true, Metadata = new VexRekorMetadata { Uuid = "rekor-uuid-123", Index = 12345, LogUrl = "https://rekor.local/api/v1/log/entries/rekor-uuid-123", IntegratedAt = new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero) } }); var request = CreateTestRequest() with { SubmitToRekor = true }; // Act var result = await _service.CreateAttestationAsync(request); // Assert Assert.True(result.Success); Assert.NotNull(result.RekorResult); Assert.True(result.RekorResult.Success); Assert.Equal(12345, result.RekorResult.LogIndex); Assert.Equal("rekor-uuid-123", result.RekorResult.Uuid); var envelopeDigestHex = result.AttestationDigest!.Substring("sha256:".Length); _rekorClientMock.Verify(x => x.SubmitAsync( It.Is(r => r.ArtifactKind == "policy-decision" && r.Envelope.PayloadType == PredicateTypes.StellaOpsPolicyDecision && r.EnvelopeDigest == envelopeDigestHex && r.SubjectUris!.Contains("example.com/image:v1@sha256:abc123")), It.IsAny()), Times.Once); } [Fact] public async Task CreateAttestationAsync_WithoutSignerClient_CreatesUnsignedAttestation() { // Arrange var serviceWithoutSigner = new PolicyDecisionAttestationService( signerClient: null, rekorClient: null, _optionsMock.Object, TimeProvider.System, NullLogger.Instance); var request = CreateTestRequest(); // Act var result = await serviceWithoutSigner.CreateAttestationAsync(request); // Assert Assert.True(result.Success); Assert.StartsWith("sha256:", result.AttestationDigest); Assert.Null(result.KeyId); } [Fact] public async Task CreateAttestationAsync_IncludesAllSubjects() { // Arrange _signerClientMock.Setup(x => x.SignAsync( It.IsAny(), It.IsAny())) .ReturnsAsync(new VexSignerResult { Success = true, Signature = "AQID" }); var request = CreateTestRequest() with { Subjects = new[] { new AttestationSubject { Name = "example.com/image:v1", Digest = new Dictionary { ["sha256"] = "abc123" } }, new AttestationSubject { Name = "example.com/image:v2", Digest = new Dictionary { ["sha256"] = "def456" } } } }; // Act var result = await _service.CreateAttestationAsync(request); // Assert Assert.True(result.Success); } [Fact] public async Task CreateAttestationAsync_SetsExpirationFromOptions() { // Arrange _optionsMock.Setup(x => x.CurrentValue).Returns(new PolicyDecisionAttestationOptions { Enabled = true, UseSignerService = false, DefaultTtlHours = 48 }); var serviceWithOptions = new PolicyDecisionAttestationService( signerClient: null, rekorClient: null, _optionsMock.Object, TimeProvider.System, NullLogger.Instance); var request = CreateTestRequest(); // Act var result = await serviceWithOptions.CreateAttestationAsync(request); // Assert Assert.True(result.Success); } [Fact] public async Task SubmitToRekorAsync_WhenNoClient_ReturnsFailure() { // Arrange var serviceWithoutRekor = new PolicyDecisionAttestationService( _signerClientMock.Object, rekorClient: null, _optionsMock.Object, TimeProvider.System, NullLogger.Instance); // Act var result = await serviceWithoutRekor.SubmitToRekorAsync("sha256:test"); // Assert Assert.False(result.Success); Assert.Contains("not available", result.Error); } [Fact] public async Task VerifyAsync_ReturnsNotImplemented() { // Act var result = await _service.VerifyAsync("sha256:test"); // Assert Assert.False(result.Valid); Assert.Contains("not yet implemented", result.Issues![0], StringComparison.OrdinalIgnoreCase); } private static PolicyDecisionAttestationRequest CreateTestRequest() { return new PolicyDecisionAttestationRequest { Predicate = new PolicyDecisionPredicate { Policy = new PolicyReference { Id = "test-policy", Version = "1.0.0", Name = "Test Policy" }, Inputs = new PolicyDecisionInputs { Subjects = new[] { new SubjectReference { Name = "example.com/image:v1", Digest = "sha256:abc123" } } }, Result = new PolicyDecisionResult { Decision = PolicyDecision.Allow, Summary = "All gates passed" } }, Subjects = new[] { new AttestationSubject { Name = "example.com/image:v1", Digest = new Dictionary { ["sha256"] = "abc123" } } }, TenantId = "tenant-1" }; } }