save progress
This commit is contained in:
@@ -6,6 +6,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Moq;
|
||||
using Org.BouncyCastle.Crypto.Parameters;
|
||||
using StellaOps.Attestor.Envelope;
|
||||
using StellaOps.Attestor.GraphRoot.Models;
|
||||
using Xunit;
|
||||
@@ -15,6 +16,9 @@ namespace StellaOps.Attestor.GraphRoot.Tests;
|
||||
|
||||
public class GraphRootAttestorTests
|
||||
{
|
||||
private static readonly TimeProvider FixedTimeProviderInstance =
|
||||
new FixedTimeProvider(new DateTimeOffset(2026, 1, 1, 0, 0, 0, TimeSpan.Zero));
|
||||
|
||||
private readonly Mock<IMerkleRootComputer> _merkleComputerMock;
|
||||
private readonly EnvelopeSignatureService _signatureService;
|
||||
private readonly GraphRootAttestor _attestor;
|
||||
@@ -28,12 +32,7 @@ public class GraphRootAttestorTests
|
||||
.Setup(m => m.ComputeRoot(It.IsAny<IReadOnlyList<ReadOnlyMemory<byte>>>()))
|
||||
.Returns(new byte[32]); // 32-byte hash
|
||||
|
||||
// Create a real test key for signing (need both private and public for Ed25519)
|
||||
var privateKey = new byte[64]; // Ed25519 expanded private key is 64 bytes
|
||||
var publicKey = new byte[32];
|
||||
Random.Shared.NextBytes(privateKey);
|
||||
Random.Shared.NextBytes(publicKey);
|
||||
_testKey = EnvelopeKey.CreateEd25519Signer(privateKey, publicKey, "test-key-id");
|
||||
_testKey = CreateDeterministicKey("test-key-id");
|
||||
|
||||
_signatureService = new EnvelopeSignatureService();
|
||||
|
||||
@@ -41,7 +40,8 @@ public class GraphRootAttestorTests
|
||||
_merkleComputerMock.Object,
|
||||
_signatureService,
|
||||
_ => _testKey,
|
||||
NullLogger<GraphRootAttestor>.Instance);
|
||||
NullLogger<GraphRootAttestor>.Instance,
|
||||
timeProvider: FixedTimeProviderInstance);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
@@ -170,6 +170,40 @@ public class GraphRootAttestorTests
|
||||
Assert.Contains("sha256:params", digestStrings);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AttestAsync_IncludesEvidenceIdsInLeaves()
|
||||
{
|
||||
// Arrange
|
||||
var request = new GraphRootAttestationRequest
|
||||
{
|
||||
GraphType = GraphType.DependencyGraph,
|
||||
NodeIds = Array.Empty<string>(),
|
||||
EdgeIds = Array.Empty<string>(),
|
||||
PolicyDigest = "sha256:policy",
|
||||
FeedsDigest = "sha256:feeds",
|
||||
ToolchainDigest = "sha256:toolchain",
|
||||
ParamsDigest = "sha256:params",
|
||||
ArtifactDigest = "sha256:artifact",
|
||||
EvidenceIds = new[] { "evidence-b", "evidence-a" }
|
||||
};
|
||||
|
||||
IReadOnlyList<ReadOnlyMemory<byte>>? capturedLeaves = null;
|
||||
_merkleComputerMock
|
||||
.Setup(m => m.ComputeRoot(It.IsAny<IReadOnlyList<ReadOnlyMemory<byte>>>()))
|
||||
.Callback<IReadOnlyList<ReadOnlyMemory<byte>>>(leaves => capturedLeaves = leaves)
|
||||
.Returns(new byte[32]);
|
||||
|
||||
// Act
|
||||
await _attestor.AttestAsync(request);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(capturedLeaves);
|
||||
var leafStrings = capturedLeaves.Select(l => System.Text.Encoding.UTF8.GetString(l.Span)).ToList();
|
||||
Assert.Contains("evidence-a", leafStrings);
|
||||
Assert.Contains("evidence-b", leafStrings);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AttestAsync_NullRequest_ThrowsArgumentNullException()
|
||||
@@ -187,7 +221,8 @@ public class GraphRootAttestorTests
|
||||
_merkleComputerMock.Object,
|
||||
_signatureService,
|
||||
_ => null,
|
||||
NullLogger<GraphRootAttestor>.Instance);
|
||||
NullLogger<GraphRootAttestor>.Instance,
|
||||
timeProvider: FixedTimeProviderInstance);
|
||||
|
||||
var request = CreateValidRequest();
|
||||
|
||||
@@ -249,4 +284,32 @@ public class GraphRootAttestorTests
|
||||
ArtifactDigest = "sha256:artifact345"
|
||||
};
|
||||
}
|
||||
|
||||
private static EnvelopeKey CreateDeterministicKey(string keyId)
|
||||
{
|
||||
var seed = new byte[32];
|
||||
for (var i = 0; i < seed.Length; i++)
|
||||
{
|
||||
seed[i] = (byte)(i + 1);
|
||||
}
|
||||
|
||||
var privateKeyParameters = new Ed25519PrivateKeyParameters(seed, 0);
|
||||
var publicKeyParameters = privateKeyParameters.GeneratePublicKey();
|
||||
var publicKey = publicKeyParameters.GetEncoded();
|
||||
var privateKey = privateKeyParameters.GetEncoded();
|
||||
|
||||
return EnvelopeKey.CreateEd25519Signer(privateKey, publicKey, keyId);
|
||||
}
|
||||
|
||||
private sealed class FixedTimeProvider : TimeProvider
|
||||
{
|
||||
private readonly DateTimeOffset _fixedTime;
|
||||
|
||||
public FixedTimeProvider(DateTimeOffset fixedTime)
|
||||
{
|
||||
_fixedTime = fixedTime;
|
||||
}
|
||||
|
||||
public override DateTimeOffset GetUtcNow() => _fixedTime;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ public class GraphRootModelsTests
|
||||
public void GraphRootPredicate_RequiredProperties_Set()
|
||||
{
|
||||
// Arrange & Act
|
||||
var fixedTime = new DateTimeOffset(2026, 1, 1, 0, 0, 0, TimeSpan.Zero);
|
||||
var predicate = new GraphRootPredicate
|
||||
{
|
||||
GraphType = "DependencyGraph",
|
||||
@@ -79,7 +80,7 @@ public class GraphRootModelsTests
|
||||
ParamsDigest = "sha256:pr"
|
||||
},
|
||||
CanonVersion = "stella:canon:v1",
|
||||
ComputedAt = DateTimeOffset.UtcNow,
|
||||
ComputedAt = fixedTime,
|
||||
ComputedBy = "test",
|
||||
ComputedByVersion = "1.0.0"
|
||||
};
|
||||
@@ -97,6 +98,7 @@ public class GraphRootModelsTests
|
||||
public void GraphRootAttestation_HasCorrectDefaults()
|
||||
{
|
||||
// Arrange & Act
|
||||
var fixedTime = new DateTimeOffset(2026, 1, 1, 0, 0, 0, TimeSpan.Zero);
|
||||
var attestation = new GraphRootAttestation
|
||||
{
|
||||
Subject = new[]
|
||||
@@ -123,7 +125,7 @@ public class GraphRootModelsTests
|
||||
ParamsDigest = "sha256:pr"
|
||||
},
|
||||
CanonVersion = "v1",
|
||||
ComputedAt = DateTimeOffset.UtcNow,
|
||||
ComputedAt = fixedTime,
|
||||
ComputedBy = "test",
|
||||
ComputedByVersion = "1.0"
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using Org.BouncyCastle.Crypto.Parameters;
|
||||
using StellaOps.Attestor.Core.Rekor;
|
||||
using StellaOps.Attestor.Core.Submission;
|
||||
using StellaOps.Attestor.Envelope;
|
||||
@@ -33,16 +34,23 @@ namespace StellaOps.Attestor.GraphRoot.Tests;
|
||||
/// </summary>
|
||||
public class GraphRootPipelineIntegrationTests
|
||||
{
|
||||
private static readonly TimeProvider FixedTimeProviderInstance =
|
||||
new FixedTimeProvider(new DateTimeOffset(2026, 1, 1, 0, 0, 0, TimeSpan.Zero));
|
||||
|
||||
#region Helpers
|
||||
|
||||
private static (EnvelopeKey Key, byte[] PublicKey) CreateTestKey()
|
||||
{
|
||||
// Generate a real Ed25519 key pair for testing
|
||||
var privateKey = new byte[64]; // Ed25519 expanded private key
|
||||
var publicKey = new byte[32];
|
||||
Random.Shared.NextBytes(privateKey);
|
||||
Random.Shared.NextBytes(publicKey);
|
||||
var seed = new byte[32];
|
||||
for (var i = 0; i < seed.Length; i++)
|
||||
{
|
||||
seed[i] = (byte)(i + 1);
|
||||
}
|
||||
|
||||
var privateKeyParameters = new Ed25519PrivateKeyParameters(seed, 0);
|
||||
var publicKeyParameters = privateKeyParameters.GeneratePublicKey();
|
||||
var publicKey = publicKeyParameters.GetEncoded();
|
||||
var privateKey = privateKeyParameters.GetEncoded();
|
||||
var key = EnvelopeKey.CreateEd25519Signer(privateKey, publicKey, "test-integration-key");
|
||||
return (key, publicKey);
|
||||
}
|
||||
@@ -58,7 +66,8 @@ public class GraphRootPipelineIntegrationTests
|
||||
_ => key,
|
||||
NullLogger<GraphRootAttestor>.Instance,
|
||||
rekorClient,
|
||||
Options.Create(options ?? new GraphRootAttestorOptions()));
|
||||
Options.Create(options ?? new GraphRootAttestorOptions()),
|
||||
FixedTimeProviderInstance);
|
||||
}
|
||||
|
||||
private static GraphRootAttestationRequest CreateRealisticRequest(
|
||||
@@ -69,7 +78,7 @@ public class GraphRootPipelineIntegrationTests
|
||||
var nodeIds = Enumerable.Range(1, nodeCount)
|
||||
.Select(i =>
|
||||
{
|
||||
var content = $"node-{i}-content-{Guid.NewGuid()}";
|
||||
var content = $"node-{i}-content";
|
||||
var hash = SHA256.HashData(Encoding.UTF8.GetBytes(content));
|
||||
return $"sha256:{Convert.ToHexStringLower(hash)}";
|
||||
})
|
||||
@@ -102,7 +111,7 @@ public class GraphRootPipelineIntegrationTests
|
||||
ToolchainDigest = toolchainDigest,
|
||||
ParamsDigest = paramsDigest,
|
||||
ArtifactDigest = artifactDigest,
|
||||
EvidenceIds = [$"evidence-{Guid.NewGuid()}", $"evidence-{Guid.NewGuid()}"]
|
||||
EvidenceIds = ["evidence-1", "evidence-2"]
|
||||
};
|
||||
}
|
||||
|
||||
@@ -219,7 +228,7 @@ public class GraphRootPipelineIntegrationTests
|
||||
Uuid = "test-uuid-12345",
|
||||
Index = 42,
|
||||
Status = "included",
|
||||
IntegratedTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
|
||||
IntegratedTime = new DateTimeOffset(2026, 1, 1, 0, 0, 0, TimeSpan.Zero).ToUnixTimeSeconds()
|
||||
});
|
||||
|
||||
var options = new GraphRootAttestorOptions
|
||||
@@ -348,8 +357,7 @@ public class GraphRootPipelineIntegrationTests
|
||||
|
||||
// Assert
|
||||
Assert.False(verifyResult.IsValid);
|
||||
Assert.Contains("Root mismatch", verifyResult.FailureReason);
|
||||
Assert.NotEqual(verifyResult.ExpectedRoot, verifyResult.ComputedRoot);
|
||||
Assert.Contains("Predicate node IDs do not match", verifyResult.FailureReason);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
@@ -375,7 +383,7 @@ public class GraphRootPipelineIntegrationTests
|
||||
|
||||
// Assert
|
||||
Assert.False(verifyResult.IsValid);
|
||||
Assert.Contains("Root mismatch", verifyResult.FailureReason);
|
||||
Assert.Contains("Predicate edge IDs do not match", verifyResult.FailureReason);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
@@ -401,7 +409,7 @@ public class GraphRootPipelineIntegrationTests
|
||||
|
||||
// Assert
|
||||
Assert.False(verifyResult.IsValid);
|
||||
Assert.NotEqual(request.NodeIds.Count, verifyResult.NodeCount);
|
||||
Assert.Contains("Predicate node IDs do not match", verifyResult.FailureReason);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
@@ -425,6 +433,7 @@ public class GraphRootPipelineIntegrationTests
|
||||
|
||||
// Assert
|
||||
Assert.False(verifyResult.IsValid);
|
||||
Assert.Contains("Predicate node IDs do not match", verifyResult.FailureReason);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -543,4 +552,16 @@ public class GraphRootPipelineIntegrationTests
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private sealed class FixedTimeProvider : TimeProvider
|
||||
{
|
||||
private readonly DateTimeOffset _fixedTime;
|
||||
|
||||
public FixedTimeProvider(DateTimeOffset fixedTime)
|
||||
{
|
||||
_fixedTime = fixedTime;
|
||||
}
|
||||
|
||||
public override DateTimeOffset GetUtcNow() => _fixedTime;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user