Files
git.stella-ops.org/tests/Provenance/StellaOps.Provenance.Attestation.Tests/PromotionAttestationBuilderTests.cs
StellaOps Bot b6b9ffc050
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Add PHP Analyzer Plugin and Composer Lock Data Handling
- Implemented the PhpAnalyzerPlugin to analyze PHP projects.
- Created ComposerLockData class to represent data from composer.lock files.
- Developed ComposerLockReader to load and parse composer.lock files asynchronously.
- Introduced ComposerPackage class to encapsulate package details.
- Added PhpPackage class to represent PHP packages with metadata and evidence.
- Implemented PhpPackageCollector to gather packages from ComposerLockData.
- Created PhpLanguageAnalyzer to perform analysis and emit results.
- Added capability signals for known PHP frameworks and CMS.
- Developed unit tests for the PHP language analyzer and its components.
- Included sample composer.lock and expected output for testing.
- Updated project files for the new PHP analyzer library and tests.
2025-11-22 14:02:49 +02:00

79 lines
3.0 KiB
C#

using System.Text;
using StellaOps.Provenance.Attestation;
using Xunit;
namespace StellaOps.Provenance.Attestation.Tests;
public sealed class PromotionAttestationBuilderTests
{
[Fact]
public async Task BuildAsync_SignsCanonicalPayloadAndAddsPredicateClaim()
{
var predicate = new PromotionPredicate(
ImageDigest: "sha256:deadbeef",
SbomDigest: "sha256:sbom",
VexDigest: "sha256:vex",
PromotionId: "promo-123",
RekorEntry: "rekor:entry",
Metadata: new Dictionary<string, string>
{
{ "z", "last" },
{ "a", "first" }
});
var signer = new RecordingSigner();
var attestation = await PromotionAttestationBuilder.BuildAsync(predicate, signer);
Assert.Equal(predicate, attestation.Predicate);
Assert.NotNull(attestation.Payload);
Assert.Equal(PromotionAttestationBuilder.ContentType, signer.LastRequest?.ContentType);
Assert.Equal(PromotionAttestationBuilder.PredicateType, signer.LastRequest?.Claims!["predicateType"]);
Assert.Equal(attestation.Payload, signer.LastRequest?.Payload);
Assert.Equal(attestation.Signature, signer.LastResult);
// verify canonical order is stable (metadata keys sorted, property names sorted)
var canonicalJson = Encoding.UTF8.GetString(attestation.Payload);
Assert.Equal(
"{\"ImageDigest\":\"sha256:deadbeef\",\"Metadata\":{\"a\":\"first\",\"z\":\"last\"},\"PromotionId\":\"promo-123\",\"RekorEntry\":\"rekor:entry\",\"SbomDigest\":\"sha256:sbom\",\"VexDigest\":\"sha256:vex\"}",
canonicalJson);
}
[Fact]
public async Task BuildAsync_MergesClaimsWithoutOverwritingPredicateType()
{
var predicate = new PromotionPredicate(
ImageDigest: "sha256:x",
SbomDigest: "sha256:y",
VexDigest: "sha256:z",
PromotionId: "p-1");
var signer = new RecordingSigner();
var customClaims = new Dictionary<string, string> { { "env", "stage" }, { "predicateType", "custom" } };
await PromotionAttestationBuilder.BuildAsync(predicate, signer, customClaims);
Assert.NotNull(signer.LastRequest);
Assert.Equal(PromotionAttestationBuilder.PredicateType, signer.LastRequest!.Claims!["predicateType"]);
Assert.Equal("stage", signer.LastRequest!.Claims!["env"]);
}
private sealed class RecordingSigner : ISigner
{
public SignRequest? LastRequest { get; private set; }
public SignResult? LastResult { get; private set; }
public Task<SignResult> SignAsync(SignRequest request, CancellationToken cancellationToken = default)
{
LastRequest = request;
LastResult = new SignResult(
Signature: Encoding.UTF8.GetBytes("sig"),
KeyId: "key-1",
SignedAt: DateTimeOffset.UtcNow,
Claims: request.Claims);
return Task.FromResult(LastResult);
}
}
}