Refactor code structure and optimize performance across multiple modules
This commit is contained in:
@@ -5,11 +5,13 @@ using StellaOps.Attestor.Core.Tests.Fixtures.Rekor;
|
||||
using StellaOps.Attestor.Core.Verification;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Attestor.Core.Tests;
|
||||
|
||||
public sealed class RekorOfflineReceiptVerifierTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAsync_ValidReceipt_Succeeds()
|
||||
{
|
||||
var (directory, receiptPath) = CreateTempReceipt(RekorOfflineReceiptFixtures.ReceiptJson);
|
||||
@@ -33,7 +35,8 @@ public sealed class RekorOfflineReceiptVerifierTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAsync_CheckpointPathReference_Succeeds()
|
||||
{
|
||||
var directory = Path.Combine(Path.GetTempPath(), "stellaops-attestor-rekor-offline-" + Guid.NewGuid().ToString("n"));
|
||||
@@ -62,7 +65,8 @@ public sealed class RekorOfflineReceiptVerifierTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAsync_TamperedCheckpointSignature_Fails()
|
||||
{
|
||||
var tampered = MutateReceiptJson(root =>
|
||||
@@ -90,7 +94,8 @@ public sealed class RekorOfflineReceiptVerifierTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAsync_RootHashMismatch_Fails()
|
||||
{
|
||||
var badJson = MutateReceiptJson(root => root["rootHash"] = new string('0', 64));
|
||||
@@ -114,7 +119,8 @@ public sealed class RekorOfflineReceiptVerifierTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAsync_AllowOfflineWithoutSignature_AllowsUnsignedCheckpoint()
|
||||
{
|
||||
var checkpointBodyOnly = RekorOfflineReceiptFixtures.SignedCheckpointNote.Split("\n\n", StringSplitOptions.None)[0] + "\n";
|
||||
|
||||
@@ -39,7 +39,8 @@ namespace StellaOps.Attestor.Tests;
|
||||
|
||||
public sealed class AttestationBundleEndpointsTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExportEndpoint_RequiresAuthentication()
|
||||
{
|
||||
using var factory = new AttestorWebApplicationFactory();
|
||||
@@ -50,7 +51,8 @@ public sealed class AttestationBundleEndpointsTests
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExportAndImportEndpoints_RoundTripBundles()
|
||||
{
|
||||
using var factory = new AttestorWebApplicationFactory();
|
||||
@@ -64,6 +66,7 @@ public sealed class AttestationBundleEndpointsTests
|
||||
using (var scope = factory.Services.CreateScope())
|
||||
{
|
||||
var repository = scope.ServiceProvider.GetRequiredService<IAttestorEntryRepository>();
|
||||
using StellaOps.TestKit;
|
||||
var archiveStore = scope.ServiceProvider.GetRequiredService<IAttestorArchiveStore>();
|
||||
|
||||
var entry = new AttestorEntry
|
||||
|
||||
@@ -8,11 +8,13 @@ using StellaOps.Attestor.Core.Storage;
|
||||
using StellaOps.Attestor.WebService.Contracts;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Attestor.Tests;
|
||||
|
||||
public sealed class AttestationQueryTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task QueryAsync_FiltersAndPagination_Work()
|
||||
{
|
||||
var repository = new InMemoryAttestorEntryRepository();
|
||||
@@ -83,7 +85,8 @@ public sealed class AttestationQueryTests
|
||||
Assert.All(secondPage.Items, item => Assert.DoesNotContain(item.RekorUuid, firstPageIds));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryBuildQuery_ValidatesInputs()
|
||||
{
|
||||
var httpContext = new DefaultHttpContext();
|
||||
|
||||
@@ -5,11 +5,13 @@ using System.Threading.Tasks;
|
||||
using StellaOps.Attestor.Core.Storage;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Attestor.Tests;
|
||||
|
||||
public sealed class AttestorEntryRepositoryTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task QueryAsync_FiltersAndPagination_Work()
|
||||
{
|
||||
var repository = new InMemoryAttestorEntryRepository();
|
||||
@@ -53,7 +55,8 @@ public sealed class AttestorEntryRepositoryTests
|
||||
Assert.All(secondPage.Items, item => Assert.DoesNotContain(item.RekorUuid, seen));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SaveAsync_EnforcesUniqueBundleSha()
|
||||
{
|
||||
var repository = new InMemoryAttestorEntryRepository();
|
||||
|
||||
@@ -28,7 +28,8 @@ public sealed class AttestorSigningServiceTests : IDisposable
|
||||
{
|
||||
private readonly List<string> _temporaryPaths = new();
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SignAsync_Ed25519Key_ReturnsValidSignature()
|
||||
{
|
||||
var privateKey = new byte[32];
|
||||
@@ -110,7 +111,8 @@ public sealed class AttestorSigningServiceTests : IDisposable
|
||||
Assert.Equal("signed", auditSink.Records[0].Result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SignAsync_KmsKey_ProducesVerifiableSignature()
|
||||
{
|
||||
var kmsRoot = CreateTempDirectory();
|
||||
@@ -215,7 +217,8 @@ public sealed class AttestorSigningServiceTests : IDisposable
|
||||
Assert.Equal("signed", auditSink.Records[0].Result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SignAsync_Sm2Key_ReturnsValidSignature_WhenGateEnabled()
|
||||
{
|
||||
var originalGate = Environment.GetEnvironmentVariable("SM_SOFT_ALLOWED");
|
||||
@@ -251,6 +254,7 @@ public sealed class AttestorSigningServiceTests : IDisposable
|
||||
|
||||
using var metrics = new AttestorMetrics();
|
||||
using var registry = new AttestorSigningKeyRegistry(options, TimeProvider.System, NullLogger<AttestorSigningKeyRegistry>.Instance);
|
||||
using StellaOps.TestKit;
|
||||
var auditSink = new InMemoryAttestorAuditSink();
|
||||
var service = new AttestorSigningService(
|
||||
registry,
|
||||
@@ -312,7 +316,8 @@ public sealed class AttestorSigningServiceTests : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Sm2Registry_Fails_WhenGateDisabled()
|
||||
{
|
||||
var originalGate = Environment.GetEnvironmentVariable("SM_SOFT_ALLOWED");
|
||||
|
||||
@@ -5,11 +5,13 @@ using StellaOps.Attestor.Core.Storage;
|
||||
using StellaOps.Attestor.Infrastructure.Storage;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Attestor.Tests;
|
||||
|
||||
public sealed class AttestorStorageTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SaveAsync_PersistsAndFetchesEntry()
|
||||
{
|
||||
var repository = new InMemoryAttestorEntryRepository();
|
||||
@@ -27,7 +29,8 @@ public sealed class AttestorStorageTests
|
||||
Assert.Single(byArtifact);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SaveAsync_UpsertsExistingDocument()
|
||||
{
|
||||
var repository = new InMemoryAttestorEntryRepository();
|
||||
@@ -47,7 +50,8 @@ public sealed class AttestorStorageTests
|
||||
Assert.Equal("pending", stored!.Status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task InMemoryDedupeStore_RoundTripsAndExpires()
|
||||
{
|
||||
var store = new InMemoryAttestorDedupeStore();
|
||||
|
||||
@@ -21,7 +21,8 @@ namespace StellaOps.Attestor.Tests;
|
||||
|
||||
public sealed class AttestorSubmissionServiceTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SubmitAsync_ReturnsDeterministicUuid_OnDuplicateBundle()
|
||||
{
|
||||
var options = Options.Create(new AttestorOptions
|
||||
@@ -92,7 +93,8 @@ public sealed class AttestorSubmissionServiceTests
|
||||
Assert.Equal(request.Meta.Artifact.Sha256, verificationCache.InvalidatedSubjects[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Validator_ThrowsWhenModeNotAllowed()
|
||||
{
|
||||
var canonicalizer = new DefaultDsseCanonicalizer();
|
||||
@@ -104,7 +106,8 @@ public sealed class AttestorSubmissionServiceTests
|
||||
await Assert.ThrowsAsync<AttestorValidationException>(() => validator.ValidateAsync(request));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SubmitAsync_Throws_WhenMirrorDisabledButRequested()
|
||||
{
|
||||
var options = Options.Create(new AttestorOptions
|
||||
@@ -163,7 +166,8 @@ public sealed class AttestorSubmissionServiceTests
|
||||
Assert.Equal("mirror_disabled", ex.Code);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SubmitAsync_ReturnsMirrorMetadata_WhenPreferenceBoth()
|
||||
{
|
||||
var options = Options.Create(new AttestorOptions
|
||||
@@ -233,7 +237,8 @@ public sealed class AttestorSubmissionServiceTests
|
||||
Assert.Equal("included", result.Mirror.Status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SubmitAsync_UsesMirrorAsCanonical_WhenPreferenceMirror()
|
||||
{
|
||||
var options = Options.Create(new AttestorOptions
|
||||
@@ -270,6 +275,7 @@ public sealed class AttestorSubmissionServiceTests
|
||||
var logger = new NullLogger<AttestorSubmissionService>();
|
||||
using var metrics = new AttestorMetrics();
|
||||
|
||||
using StellaOps.TestKit;
|
||||
var service = new AttestorSubmissionService(
|
||||
validator,
|
||||
repository,
|
||||
|
||||
@@ -7,13 +7,15 @@ using StellaOps.Attestor.Core.Submission;
|
||||
using StellaOps.Attestor.Infrastructure.Submission;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Attestor.Tests;
|
||||
|
||||
public sealed class AttestorSubmissionValidatorHardeningTests
|
||||
{
|
||||
private static readonly DefaultDsseCanonicalizer Canonicalizer = new();
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ValidateAsync_ThrowsWhenPayloadExceedsLimit()
|
||||
{
|
||||
var constraints = new AttestorSubmissionConstraints(
|
||||
@@ -28,7 +30,8 @@ public sealed class AttestorSubmissionValidatorHardeningTests
|
||||
Assert.Equal("payload_too_large", exception.Code);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ValidateAsync_ThrowsWhenCertificateChainTooLong()
|
||||
{
|
||||
var constraints = new AttestorSubmissionConstraints(
|
||||
@@ -43,7 +46,8 @@ public sealed class AttestorSubmissionValidatorHardeningTests
|
||||
Assert.Equal("certificate_chain_too_long", exception.Code);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ValidateAsync_FuzzedInputs_DoNotCrash()
|
||||
{
|
||||
var constraints = new AttestorSubmissionConstraints();
|
||||
|
||||
@@ -29,7 +29,8 @@ public sealed class AttestorVerificationServiceTests
|
||||
private static readonly byte[] HmacSecret = Encoding.UTF8.GetBytes("attestor-hmac-secret");
|
||||
private static readonly string HmacSecretBase64 = Convert.ToBase64String(HmacSecret);
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAsync_ReturnsOk_ForExistingUuid()
|
||||
{
|
||||
var options = Options.Create(new AttestorOptions
|
||||
@@ -122,7 +123,8 @@ public sealed class AttestorVerificationServiceTests
|
||||
Assert.Equal("missing", verifyResult.Report.Transparency.WitnessStatus);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAsync_KmsBundle_Passes_WhenTwoSignaturesRequired()
|
||||
{
|
||||
var options = Options.Create(new AttestorOptions
|
||||
@@ -213,7 +215,8 @@ public sealed class AttestorVerificationServiceTests
|
||||
Assert.Equal(2, verifyResult.Report.Signatures.RequiredSignatures);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAsync_FlagsTamperedBundle()
|
||||
{
|
||||
var options = Options.Create(new AttestorOptions
|
||||
@@ -426,7 +429,8 @@ public sealed class AttestorVerificationServiceTests
|
||||
return buffer;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAsync_OfflineSkipsProofRefreshWhenMissing()
|
||||
{
|
||||
var options = Options.Create(new AttestorOptions
|
||||
@@ -490,7 +494,8 @@ public sealed class AttestorVerificationServiceTests
|
||||
Assert.Equal(0, rekorClient.ProofRequests);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAsync_OfflineUsesImportedProof()
|
||||
{
|
||||
var options = Options.Create(new AttestorOptions
|
||||
@@ -577,7 +582,8 @@ public sealed class AttestorVerificationServiceTests
|
||||
Assert.Equal(0, rekorClient.ProofRequests);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAsync_FailsWhenWitnessRootMismatch()
|
||||
{
|
||||
var options = Options.Create(new AttestorOptions
|
||||
@@ -692,6 +698,7 @@ public sealed class AttestorVerificationServiceTests
|
||||
private static byte[] ComputeMerkleNode(byte[] left, byte[] right)
|
||||
{
|
||||
using var sha = SHA256.Create();
|
||||
using StellaOps.TestKit;
|
||||
var buffer = new byte[1 + left.Length + right.Length];
|
||||
buffer[0] = 0x01;
|
||||
Buffer.BlockCopy(left, 0, buffer, 1, left.Length);
|
||||
|
||||
@@ -5,11 +5,13 @@ using StellaOps.Attestor.Core.Options;
|
||||
using StellaOps.Attestor.WebService.Contracts;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Attestor.Tests;
|
||||
|
||||
public sealed class BulkVerificationContractsTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryBuildJob_ReturnsError_WhenItemsMissing()
|
||||
{
|
||||
var options = new AttestorOptions();
|
||||
@@ -22,7 +24,8 @@ public sealed class BulkVerificationContractsTests
|
||||
Assert.NotNull(error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryBuildJob_AppliesDefaults()
|
||||
{
|
||||
var options = new AttestorOptions
|
||||
|
||||
@@ -15,12 +15,14 @@ namespace StellaOps.Attestor.Tests;
|
||||
|
||||
public sealed class BulkVerificationWorkerTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ProcessJobAsync_CompletesAllItems()
|
||||
{
|
||||
var jobStore = new InMemoryBulkVerificationJobStore();
|
||||
var verificationService = new StubVerificationService();
|
||||
using var metrics = new AttestorMetrics();
|
||||
using StellaOps.TestKit;
|
||||
var options = Options.Create(new AttestorOptions
|
||||
{
|
||||
BulkVerification = new AttestorOptions.BulkVerificationOptions
|
||||
|
||||
@@ -14,7 +14,8 @@ namespace StellaOps.Attestor.Tests;
|
||||
|
||||
public sealed class CachedAttestorVerificationServiceTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAsync_ReturnsCachedResult_OnRepeatedCalls()
|
||||
{
|
||||
var options = Options.Create(new AttestorOptions());
|
||||
@@ -44,7 +45,8 @@ public sealed class CachedAttestorVerificationServiceTests
|
||||
Assert.Equal(1, inner.VerifyCallCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAsync_BypassesCache_WhenRefreshProofRequested()
|
||||
{
|
||||
var options = Options.Create(new AttestorOptions());
|
||||
@@ -75,12 +77,14 @@ public sealed class CachedAttestorVerificationServiceTests
|
||||
Assert.Equal(2, inner.VerifyCallCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAsync_BypassesCache_WhenDescriptorIncomplete()
|
||||
{
|
||||
var options = Options.Create(new AttestorOptions());
|
||||
using var memoryCache = new MemoryCache(new MemoryCacheOptions());
|
||||
using var metrics = new AttestorMetrics();
|
||||
using StellaOps.TestKit;
|
||||
var cache = new InMemoryAttestorVerificationCache(memoryCache, options, new NullLogger<InMemoryAttestorVerificationCache>());
|
||||
var inner = new StubVerificationService();
|
||||
var service = new CachedAttestorVerificationService(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using StellaOps.Attestor.Core.Verification;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Attestor.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -19,7 +20,8 @@ public sealed class CheckpointSignatureVerifierTests
|
||||
|
||||
private const string InvalidFormatCheckpoint = "not a valid checkpoint";
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParseCheckpoint_ValidFormat_ExtractsFields()
|
||||
{
|
||||
// Act
|
||||
@@ -32,7 +34,8 @@ public sealed class CheckpointSignatureVerifierTests
|
||||
Assert.NotNull(result.RootHash);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParseCheckpoint_InvalidFormat_ReturnsFailure()
|
||||
{
|
||||
// Act
|
||||
@@ -43,7 +46,8 @@ public sealed class CheckpointSignatureVerifierTests
|
||||
Assert.Contains("Invalid", result.FailureReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParseCheckpoint_EmptyString_ReturnsFailure()
|
||||
{
|
||||
// Act
|
||||
@@ -54,7 +58,8 @@ public sealed class CheckpointSignatureVerifierTests
|
||||
Assert.NotNull(result.FailureReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParseCheckpoint_MinimalValidFormat_ExtractsFields()
|
||||
{
|
||||
// Arrange - minimal checkpoint without timestamp
|
||||
@@ -74,7 +79,8 @@ public sealed class CheckpointSignatureVerifierTests
|
||||
Assert.Equal(32, result.RootHash!.Length); // SHA-256 hash
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParseCheckpoint_InvalidBase64Root_ReturnsFailure()
|
||||
{
|
||||
// Arrange - invalid base64 in root hash
|
||||
@@ -92,7 +98,8 @@ public sealed class CheckpointSignatureVerifierTests
|
||||
Assert.Contains("Invalid root hash", result.FailureReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParseCheckpoint_InvalidTreeSize_ReturnsFailure()
|
||||
{
|
||||
// Arrange - non-numeric tree size
|
||||
@@ -110,7 +117,8 @@ public sealed class CheckpointSignatureVerifierTests
|
||||
Assert.Contains("Invalid tree size", result.FailureReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void VerifyCheckpoint_NullCheckpoint_ThrowsArgumentNull()
|
||||
{
|
||||
// Act & Assert
|
||||
@@ -118,7 +126,8 @@ public sealed class CheckpointSignatureVerifierTests
|
||||
CheckpointSignatureVerifier.VerifyCheckpoint(null!, [], []));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void VerifyCheckpoint_NullSignature_ThrowsArgumentNull()
|
||||
{
|
||||
// Act & Assert
|
||||
@@ -126,7 +135,8 @@ public sealed class CheckpointSignatureVerifierTests
|
||||
CheckpointSignatureVerifier.VerifyCheckpoint("checkpoint", null!, []));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void VerifyCheckpoint_NullPublicKey_ThrowsArgumentNull()
|
||||
{
|
||||
// Act & Assert
|
||||
@@ -134,7 +144,8 @@ public sealed class CheckpointSignatureVerifierTests
|
||||
CheckpointSignatureVerifier.VerifyCheckpoint("checkpoint", [], null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void VerifyCheckpoint_InvalidFormat_ReturnsFailure()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -11,11 +11,13 @@ using StellaOps.Attestor.Core.Submission;
|
||||
using StellaOps.Attestor.Infrastructure.Rekor;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Attestor.Tests;
|
||||
|
||||
public sealed class HttpRekorClientTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SubmitAsync_ParsesResponse()
|
||||
{
|
||||
var payload = new
|
||||
@@ -65,7 +67,8 @@ public sealed class HttpRekorClientTests
|
||||
Assert.Equal("leaf", response.Proof!.Inclusion!.LeafHash);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SubmitAsync_ThrowsOnConflict()
|
||||
{
|
||||
var client = CreateClient(HttpStatusCode.Conflict, new { error = "duplicate" });
|
||||
@@ -96,7 +99,8 @@ public sealed class HttpRekorClientTests
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => rekorClient.SubmitAsync(request, backend));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetProofAsync_ReturnsNullOnNotFound()
|
||||
{
|
||||
var client = CreateClient(HttpStatusCode.NotFound, new { });
|
||||
|
||||
@@ -17,7 +17,8 @@ namespace StellaOps.Attestor.Tests;
|
||||
|
||||
public sealed class HttpTransparencyWitnessClientTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetObservationAsync_CachesSuccessfulResponses()
|
||||
{
|
||||
var handler = new StubHttpMessageHandler(_ =>
|
||||
@@ -78,7 +79,8 @@ public sealed class HttpTransparencyWitnessClientTests
|
||||
Assert.Equal(1, handler.CallCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetObservationAsync_ReturnsErrorObservation_OnNonSuccess()
|
||||
{
|
||||
var handler = new StubHttpMessageHandler(_ => new HttpResponseMessage(HttpStatusCode.BadGateway));
|
||||
@@ -121,7 +123,8 @@ public sealed class HttpTransparencyWitnessClientTests
|
||||
Assert.Equal(1, handler.CallCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetObservationAsync_ReturnsCachedErrorObservation_OnException()
|
||||
{
|
||||
var handler = new StubHttpMessageHandler(_ => throw new HttpRequestException("boom"));
|
||||
@@ -131,6 +134,7 @@ public sealed class HttpTransparencyWitnessClientTests
|
||||
using var metrics = new AttestorMetrics();
|
||||
using var activitySource = new AttestorActivitySource();
|
||||
|
||||
using StellaOps.TestKit;
|
||||
var options = Options.Create(new AttestorOptions
|
||||
{
|
||||
TransparencyWitness = new AttestorOptions.TransparencyWitnessOptions
|
||||
|
||||
@@ -8,13 +8,15 @@ using StellaOps.Attestor.Core.Options;
|
||||
using StellaOps.Attestor.Infrastructure.Storage;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Attestor.Tests;
|
||||
|
||||
public sealed class LiveDedupeStoreTests
|
||||
{
|
||||
private const string Category = "LiveTTL";
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
[Trait("Category", Category)]
|
||||
public async Task Redis_dedupe_entry_sets_time_to_live()
|
||||
{
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
using StellaOps.Attestor.Core.Verification;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Attestor.Tests;
|
||||
|
||||
public sealed class MerkleProofVerifierTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void HashLeaf_ProducesDeterministicHash()
|
||||
{
|
||||
var data = "test data"u8.ToArray();
|
||||
@@ -17,7 +19,8 @@ public sealed class MerkleProofVerifierTests
|
||||
Assert.Equal(32, hash1.Length); // SHA-256 produces 32 bytes
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void HashLeaf_IncludesLeafPrefix()
|
||||
{
|
||||
var data = Array.Empty<byte>();
|
||||
@@ -29,7 +32,8 @@ public sealed class MerkleProofVerifierTests
|
||||
Assert.Equal(32, hash.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void HashInterior_ProducesDeterministicHash()
|
||||
{
|
||||
var left = new byte[] { 1, 2, 3 };
|
||||
@@ -41,7 +45,8 @@ public sealed class MerkleProofVerifierTests
|
||||
Assert.Equal(hash1, hash2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void HashInterior_OrderMatters()
|
||||
{
|
||||
var a = new byte[] { 1, 2, 3 };
|
||||
@@ -53,7 +58,8 @@ public sealed class MerkleProofVerifierTests
|
||||
Assert.NotEqual(hashAB, hashBA);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void VerifyInclusion_SingleLeafTree_Succeeds()
|
||||
{
|
||||
var leafData = "single leaf"u8.ToArray();
|
||||
@@ -70,7 +76,8 @@ public sealed class MerkleProofVerifierTests
|
||||
Assert.True(verified);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void VerifyInclusion_TwoLeafTree_LeftLeaf_Succeeds()
|
||||
{
|
||||
var leaf0Data = "leaf 0"u8.ToArray();
|
||||
@@ -91,7 +98,8 @@ public sealed class MerkleProofVerifierTests
|
||||
Assert.True(verified);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void VerifyInclusion_TwoLeafTree_RightLeaf_Succeeds()
|
||||
{
|
||||
var leaf0Data = "leaf 0"u8.ToArray();
|
||||
@@ -112,7 +120,8 @@ public sealed class MerkleProofVerifierTests
|
||||
Assert.True(verified);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void VerifyInclusion_InvalidLeafHash_Fails()
|
||||
{
|
||||
var leaf0Data = "leaf 0"u8.ToArray();
|
||||
@@ -135,7 +144,8 @@ public sealed class MerkleProofVerifierTests
|
||||
Assert.False(verified);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void VerifyInclusion_WrongRootHash_Fails()
|
||||
{
|
||||
var leaf0Hash = MerkleProofVerifier.HashLeaf("leaf 0"u8.ToArray());
|
||||
@@ -152,7 +162,8 @@ public sealed class MerkleProofVerifierTests
|
||||
Assert.False(verified);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void VerifyInclusion_InvalidIndex_Fails()
|
||||
{
|
||||
var leafHash = MerkleProofVerifier.HashLeaf("test"u8.ToArray());
|
||||
@@ -168,7 +179,8 @@ public sealed class MerkleProofVerifierTests
|
||||
Assert.False(verified);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void VerifyInclusion_NegativeIndex_Fails()
|
||||
{
|
||||
var leafHash = MerkleProofVerifier.HashLeaf("test"u8.ToArray());
|
||||
@@ -183,7 +195,8 @@ public sealed class MerkleProofVerifierTests
|
||||
Assert.False(verified);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void VerifyInclusion_ZeroTreeSize_Fails()
|
||||
{
|
||||
var leafHash = MerkleProofVerifier.HashLeaf("test"u8.ToArray());
|
||||
@@ -198,7 +211,8 @@ public sealed class MerkleProofVerifierTests
|
||||
Assert.False(verified);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void HexToBytes_ConvertsCorrectly()
|
||||
{
|
||||
var hex = "0102030405";
|
||||
@@ -209,7 +223,8 @@ public sealed class MerkleProofVerifierTests
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void HexToBytes_Handles0xPrefix()
|
||||
{
|
||||
var hex = "0x0102030405";
|
||||
@@ -220,7 +235,8 @@ public sealed class MerkleProofVerifierTests
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void BytesToHex_ConvertsCorrectly()
|
||||
{
|
||||
var bytes = new byte[] { 0xAB, 0xCD, 0xEF };
|
||||
@@ -230,7 +246,8 @@ public sealed class MerkleProofVerifierTests
|
||||
Assert.Equal("abcdef", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ComputeRootFromPath_WithEmptyPath_ReturnsSingleLeaf()
|
||||
{
|
||||
var leafHash = MerkleProofVerifier.HashLeaf("test"u8.ToArray());
|
||||
@@ -245,7 +262,8 @@ public sealed class MerkleProofVerifierTests
|
||||
Assert.Equal(leafHash, root);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ComputeRootFromPath_WithEmptyPath_NonSingleTree_ReturnsNull()
|
||||
{
|
||||
var leafHash = MerkleProofVerifier.HashLeaf("test"u8.ToArray());
|
||||
@@ -259,7 +277,8 @@ public sealed class MerkleProofVerifierTests
|
||||
Assert.Null(root);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void VerifyInclusion_FourLeafTree_AllPositions()
|
||||
{
|
||||
// Build a 4-leaf tree manually
|
||||
|
||||
@@ -35,7 +35,8 @@ public sealed class RekorInclusionVerificationIntegrationTests
|
||||
""",
|
||||
};
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void VerifyInclusion_SingleLeafTree_Succeeds()
|
||||
{
|
||||
// Arrange - single leaf tree (tree size = 1)
|
||||
@@ -54,7 +55,8 @@ public sealed class RekorInclusionVerificationIntegrationTests
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void VerifyInclusion_TwoLeafTree_LeftLeaf_Succeeds()
|
||||
{
|
||||
// Arrange - two-leaf tree, verify left leaf
|
||||
@@ -78,7 +80,8 @@ public sealed class RekorInclusionVerificationIntegrationTests
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void VerifyInclusion_TwoLeafTree_RightLeaf_Succeeds()
|
||||
{
|
||||
// Arrange - two-leaf tree, verify right leaf
|
||||
@@ -102,7 +105,8 @@ public sealed class RekorInclusionVerificationIntegrationTests
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void VerifyInclusion_FourLeafTree_AllPositions_Succeed()
|
||||
{
|
||||
// Arrange - four-leaf balanced tree
|
||||
@@ -147,7 +151,8 @@ public sealed class RekorInclusionVerificationIntegrationTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void VerifyInclusion_WrongLeafHash_Fails()
|
||||
{
|
||||
// Arrange
|
||||
@@ -172,7 +177,8 @@ public sealed class RekorInclusionVerificationIntegrationTests
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void VerifyInclusion_WrongRootHash_Fails()
|
||||
{
|
||||
// Arrange
|
||||
@@ -195,7 +201,8 @@ public sealed class RekorInclusionVerificationIntegrationTests
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void VerifyInclusion_InvalidLeafIndex_Fails()
|
||||
{
|
||||
// Arrange
|
||||
@@ -214,7 +221,8 @@ public sealed class RekorInclusionVerificationIntegrationTests
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void VerifyInclusion_NegativeLeafIndex_Fails()
|
||||
{
|
||||
// Arrange
|
||||
@@ -233,7 +241,8 @@ public sealed class RekorInclusionVerificationIntegrationTests
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void VerifyInclusion_ZeroTreeSize_Fails()
|
||||
{
|
||||
// Arrange
|
||||
@@ -252,7 +261,8 @@ public sealed class RekorInclusionVerificationIntegrationTests
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ComputeRootFromPath_EmptyProof_SingleLeaf_ReturnsLeafHash()
|
||||
{
|
||||
// Arrange
|
||||
@@ -271,7 +281,8 @@ public sealed class RekorInclusionVerificationIntegrationTests
|
||||
Assert.Equal(leaf, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ComputeRootFromPath_EmptyProof_MultiLeaf_ReturnsNull()
|
||||
{
|
||||
// Arrange - empty proof for multi-leaf tree is invalid
|
||||
@@ -296,6 +307,7 @@ public sealed class RekorInclusionVerificationIntegrationTests
|
||||
private static byte[] ComputeInteriorHash(byte[] left, byte[] right)
|
||||
{
|
||||
using var sha256 = System.Security.Cryptography.SHA256.Create();
|
||||
using StellaOps.TestKit;
|
||||
var combined = new byte[1 + left.Length + right.Length];
|
||||
combined[0] = 0x01; // Interior node prefix
|
||||
left.CopyTo(combined, 1);
|
||||
|
||||
@@ -22,13 +22,15 @@ using StellaOps.Attestor.Infrastructure.Verification;
|
||||
using StellaOps.Attestor.Verify;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Attestor.Tests;
|
||||
|
||||
public sealed class TimeSkewValidationIntegrationTests
|
||||
{
|
||||
private static readonly DateTimeOffset FixedNow = new(2025, 12, 18, 12, 0, 0, TimeSpan.Zero);
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SubmitAsync_WhenSkewRejected_Throws_WhenFailOnRejectEnabled()
|
||||
{
|
||||
var options = CreateOptions(new TimeSkewOptions
|
||||
@@ -52,7 +54,8 @@ public sealed class TimeSkewValidationIntegrationTests
|
||||
await Assert.ThrowsAsync<TimeSkewValidationException>(() => submissionService.SubmitAsync(request, context));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SubmitAsync_WhenSkewRejected_Succeeds_WhenFailOnRejectDisabled()
|
||||
{
|
||||
var options = CreateOptions(new TimeSkewOptions
|
||||
@@ -77,7 +80,8 @@ public sealed class TimeSkewValidationIntegrationTests
|
||||
Assert.False(string.IsNullOrWhiteSpace(result.Uuid));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAsync_WhenSkewRejected_ReturnsFailed_WhenFailOnRejectEnabled()
|
||||
{
|
||||
var options = CreateOptions(new TimeSkewOptions
|
||||
@@ -139,7 +143,8 @@ public sealed class TimeSkewValidationIntegrationTests
|
||||
Assert.Contains(result.Issues, issue => issue.StartsWith("time_skew_rejected:", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAsync_WhenSkewRejected_DoesNotFail_WhenFailOnRejectDisabled()
|
||||
{
|
||||
var options = CreateOptions(new TimeSkewOptions
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using StellaOps.Attestor.Core.Verification;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Attestor.Tests;
|
||||
|
||||
public class TimeSkewValidatorTests
|
||||
@@ -14,7 +15,8 @@ public class TimeSkewValidatorTests
|
||||
FailOnReject = true
|
||||
};
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_WhenDisabled_ReturnsSkipped()
|
||||
{
|
||||
// Arrange
|
||||
@@ -31,7 +33,8 @@ public class TimeSkewValidatorTests
|
||||
Assert.Contains("disabled", result.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_WhenNoIntegratedTime_ReturnsSkipped()
|
||||
{
|
||||
// Arrange
|
||||
@@ -46,7 +49,8 @@ public class TimeSkewValidatorTests
|
||||
Assert.Contains("No integrated time", result.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(0)] // No skew
|
||||
[InlineData(5)] // 5 seconds ago
|
||||
[InlineData(30)] // 30 seconds ago
|
||||
@@ -67,7 +71,8 @@ public class TimeSkewValidatorTests
|
||||
Assert.InRange(result.SkewSeconds, secondsAgo - 1, secondsAgo + 1);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(60)] // At warn threshold
|
||||
[InlineData(120)] // 2 minutes
|
||||
[InlineData(299)] // Just under reject threshold
|
||||
@@ -87,7 +92,8 @@ public class TimeSkewValidatorTests
|
||||
Assert.Contains("warning threshold", result.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(300)] // At reject threshold
|
||||
[InlineData(600)] // 10 minutes
|
||||
[InlineData(3600)] // 1 hour
|
||||
@@ -107,7 +113,8 @@ public class TimeSkewValidatorTests
|
||||
Assert.Contains("rejection threshold", result.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(5)] // 5 seconds in future (OK)
|
||||
[InlineData(30)] // 30 seconds in future (OK)
|
||||
[InlineData(60)] // At max future threshold (OK)
|
||||
@@ -127,7 +134,8 @@ public class TimeSkewValidatorTests
|
||||
Assert.True(result.SkewSeconds < 0); // Negative means future
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(61)] // Just over max future
|
||||
[InlineData(120)] // 2 minutes in future
|
||||
[InlineData(3600)] // 1 hour in future
|
||||
@@ -147,7 +155,8 @@ public class TimeSkewValidatorTests
|
||||
Assert.Contains("Future timestamp", result.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_UsesCurrentTimeWhenLocalTimeNotProvided()
|
||||
{
|
||||
// Arrange
|
||||
@@ -162,7 +171,8 @@ public class TimeSkewValidatorTests
|
||||
Assert.InRange(result.SkewSeconds, 9, 12); // Allow for test execution time
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_CustomThresholds_AreRespected()
|
||||
{
|
||||
// Arrange
|
||||
@@ -184,7 +194,8 @@ public class TimeSkewValidatorTests
|
||||
Assert.Equal(TimeSkewStatus.Warning, result.Status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_ReturnsCorrectTimestamps()
|
||||
{
|
||||
// Arrange
|
||||
@@ -201,7 +212,8 @@ public class TimeSkewValidatorTests
|
||||
Assert.Equal(30, result.SkewSeconds, precision: 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullOptions()
|
||||
{
|
||||
// Act & Assert
|
||||
|
||||
Reference in New Issue
Block a user