using System.Text; using System.Collections.Generic; using FluentAssertions; using System.Threading.Tasks; using StellaOps.Provenance.Attestation; using Xunit; namespace StellaOps.Provenance.Attestation.Tests; public class PromotionAttestationBuilderTests { [Fact] public void Produces_canonical_json_for_predicate() { var predicate = new PromotionPredicate( ImageDigest: "sha256:img", SbomDigest: "sha256:sbom", VexDigest: "sha256:vex", PromotionId: "prom-1", RekorEntry: "uuid", // Intentionally shuffled input order; canonical JSON must be sorted. Metadata: new Dictionary { { "env", "prod" }, { "region", "us-east" } }); var bytes = PromotionAttestationBuilder.CreateCanonicalJson(predicate); var json = Encoding.UTF8.GetString(bytes); json.Should().Be("{\"ImageDigest\":\"sha256:img\",\"Metadata\":{\"env\":\"prod\",\"region\":\"us-east\"},\"PromotionId\":\"prom-1\",\"RekorEntry\":\"uuid\",\"SbomDigest\":\"sha256:sbom\",\"VexDigest\":\"sha256:vex\"}"); } [Fact] public async Task BuildAsync_adds_predicate_claim_and_signs_payload() { var predicate = new PromotionPredicate( ImageDigest: "sha256:img", SbomDigest: "sha256:sbom", VexDigest: "sha256:vex", PromotionId: "prom-1"); var key = new InMemoryKeyProvider("kid-1", Encoding.UTF8.GetBytes("secret")); var signer = new HmacSigner(key); var attestation = await PromotionAttestationBuilder.BuildAsync( predicate, signer, claims: new Dictionary { { "traceId", "abc123" } }); attestation.Payload.Should().BeEquivalentTo(PromotionAttestationBuilder.CreateCanonicalJson(predicate)); attestation.Signature.KeyId.Should().Be("kid-1"); attestation.Signature.Claims.Should().ContainKey("predicateType").WhoseValue.Should().Be(PromotionAttestationBuilder.PredicateType); attestation.Signature.Claims.Should().ContainKey("traceId").WhoseValue.Should().Be("abc123"); } }