// ============================================================================= // DsseAttestationParserTests.cs // Golden-file tests for DSSE attestation parsing // Part of Task T24: Golden-file tests for determinism // ============================================================================= using FluentAssertions; using StellaOps.AirGap.Importer.Reconciliation.Parsers; namespace StellaOps.AirGap.Importer.Tests.Reconciliation; public sealed class DsseAttestationParserTests { private static readonly string FixturesPath = Path.Combine( AppDomain.CurrentDomain.BaseDirectory, "Reconciliation", "Fixtures"); [Fact] public async Task ParseAsync_ValidDsse_ExtractsEnvelope() { // Arrange var parser = new DsseAttestationParser(); var filePath = Path.Combine(FixturesPath, "sample.intoto.json"); if (!File.Exists(filePath)) { return; } // Act var result = await parser.ParseAsync(filePath); // Assert result.IsSuccess.Should().BeTrue(); result.Envelope.Should().NotBeNull(); result.Envelope!.PayloadType.Should().Be("application/vnd.in-toto+json"); result.Envelope.Signatures.Should().HaveCount(1); result.Envelope.Signatures[0].KeyId.Should().Be("test-key-id"); } [Fact] public async Task ParseAsync_ValidDsse_ExtractsStatement() { // Arrange var parser = new DsseAttestationParser(); var filePath = Path.Combine(FixturesPath, "sample.intoto.json"); if (!File.Exists(filePath)) { return; } // Act var result = await parser.ParseAsync(filePath); // Assert result.Statement.Should().NotBeNull(); result.Statement!.Type.Should().Be("https://in-toto.io/Statement/v1"); result.Statement.PredicateType.Should().Be("https://slsa.dev/provenance/v1"); result.Statement.Subjects.Should().HaveCount(1); } [Fact] public async Task ParseAsync_ExtractsSubjectDigests() { // Arrange var parser = new DsseAttestationParser(); var filePath = Path.Combine(FixturesPath, "sample.intoto.json"); if (!File.Exists(filePath)) { return; } // Act var result = await parser.ParseAsync(filePath); // Assert var subject = result.Statement!.Subjects[0]; subject.Name.Should().Be("test-app"); subject.GetSha256Digest().Should().Be("sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); } [Fact] public void IsAttestation_DsseFile_ReturnsTrue() { var parser = new DsseAttestationParser(); parser.IsAttestation("test.intoto.json").Should().BeTrue(); parser.IsAttestation("test.intoto.jsonl").Should().BeTrue(); parser.IsAttestation("test.dsig").Should().BeTrue(); parser.IsAttestation("test.dsse").Should().BeTrue(); } [Fact] public void IsAttestation_NonDsseFile_ReturnsFalse() { var parser = new DsseAttestationParser(); parser.IsAttestation("test.json").Should().BeFalse(); parser.IsAttestation("test.cdx.json").Should().BeFalse(); parser.IsAttestation("test.spdx.json").Should().BeFalse(); } [Fact] public async Task ParseAsync_Deterministic_SameOutputForSameInput() { // Arrange var parser = new DsseAttestationParser(); var filePath = Path.Combine(FixturesPath, "sample.intoto.json"); if (!File.Exists(filePath)) { return; } // Act - parse twice var result1 = await parser.ParseAsync(filePath); var result2 = await parser.ParseAsync(filePath); // Assert - results should be identical result1.Statement!.PredicateType.Should().Be(result2.Statement!.PredicateType); result1.Statement.Subjects.Count.Should().Be(result2.Statement.Subjects.Count); result1.Statement.Subjects[0].GetSha256Digest() .Should().Be(result2.Statement.Subjects[0].GetSha256Digest()); } [Fact] public async Task ParseAsync_InvalidJson_ReturnsFailure() { // Arrange var parser = new DsseAttestationParser(); var json = "not valid json"; using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(json)); // Act var result = await parser.ParseAsync(stream); // Assert result.IsSuccess.Should().BeFalse(); result.ErrorMessage.Should().Contain("parsing error"); } }