Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
- 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.
79 lines
3.0 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|