Refactor code structure and optimize performance across multiple modules
This commit is contained in:
@@ -33,7 +33,8 @@ public sealed class DatabaseMigrationTests : IAsyncLifetime
|
||||
.Build();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ApplyAsync_CreatesExpectedSchemaAndPolicies()
|
||||
{
|
||||
if (_skipReason is not null)
|
||||
@@ -98,6 +99,7 @@ public sealed class DatabaseMigrationTests : IAsyncLifetime
|
||||
Assert.Equal(0, otherVisible);
|
||||
|
||||
await using var violationConnection = await _dataSource.OpenConnectionAsync(tenant, cancellationToken);
|
||||
using StellaOps.TestKit;
|
||||
await using var violationCommand = new NpgsqlCommand(@"
|
||||
INSERT INTO evidence_locker.evidence_bundles
|
||||
(bundle_id, tenant_id, kind, status, root_hash, storage_key)
|
||||
|
||||
@@ -9,6 +9,7 @@ using StellaOps.EvidenceLocker.Core.Repositories;
|
||||
using StellaOps.EvidenceLocker.Infrastructure.Builders;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.EvidenceLocker.Tests;
|
||||
|
||||
public sealed class EvidenceBundleBuilderTests
|
||||
@@ -21,7 +22,8 @@ public sealed class EvidenceBundleBuilderTests
|
||||
_builder = new EvidenceBundleBuilder(_repository, new MerkleTreeCalculator());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task BuildAsync_ComputesDeterministicRootAndPersists()
|
||||
{
|
||||
var bundleId = EvidenceBundleId.FromGuid(Guid.NewGuid());
|
||||
@@ -51,7 +53,8 @@ public sealed class EvidenceBundleBuilderTests
|
||||
result.Manifest.Entries.OrderBy(entry => entry.CanonicalPath, StringComparer.Ordinal)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task BuildAsync_NormalizesSectionAndPath()
|
||||
{
|
||||
var request = new EvidenceBundleBuildRequest(
|
||||
|
||||
@@ -23,7 +23,8 @@ public sealed class EvidenceBundlePackagingServiceTests
|
||||
new Guid("11111111-2222-3333-4444-555555555555"));
|
||||
private static readonly DateTimeOffset CreatedAtForDeterminism = new(2025, 11, 10, 8, 0, 0, TimeSpan.Zero);
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EnsurePackageAsync_ReturnsCached_WhenPackageExists()
|
||||
{
|
||||
var repository = new FakeRepository(CreateSealedBundle(), CreateSignature());
|
||||
@@ -38,7 +39,8 @@ public sealed class EvidenceBundlePackagingServiceTests
|
||||
Assert.False(objectStore.Stored);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EnsurePackageAsync_Throws_WhenSignatureMissing()
|
||||
{
|
||||
var repository = new FakeRepository(CreateSealedBundle(), signature: null);
|
||||
@@ -48,7 +50,8 @@ public sealed class EvidenceBundlePackagingServiceTests
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => service.EnsurePackageAsync(TenantId, BundleId, CancellationToken.None));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EnsurePackageAsync_CreatesPackageWithExpectedEntries()
|
||||
{
|
||||
var repository = new FakeRepository(
|
||||
@@ -89,7 +92,8 @@ public sealed class EvidenceBundlePackagingServiceTests
|
||||
Assert.True(repository.StorageKeyUpdated);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EnsurePackageAsync_ProducesDeterministicGzipHeader()
|
||||
{
|
||||
var repository = new FakeRepository(CreateSealedBundle(), CreateSignature());
|
||||
@@ -110,7 +114,8 @@ public sealed class EvidenceBundlePackagingServiceTests
|
||||
Assert.Equal(expectedSeconds, mtime);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EnsurePackageAsync_ProducesDeterministicTarEntryMetadata()
|
||||
{
|
||||
var repository = new FakeRepository(CreateSealedBundle(), CreateSignature());
|
||||
@@ -137,7 +142,8 @@ public sealed class EvidenceBundlePackagingServiceTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EnsurePackageAsync_ProducesIdenticalBytesForSameInput()
|
||||
{
|
||||
// First run
|
||||
@@ -163,7 +169,8 @@ public sealed class EvidenceBundlePackagingServiceTests
|
||||
Assert.Equal(objectStore1.StoredBytes, objectStore2.StoredBytes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EnsurePackageAsync_Throws_WhenManifestPayloadInvalid()
|
||||
{
|
||||
var signature = CreateSignature() with { Payload = "not-base64" };
|
||||
@@ -175,7 +182,8 @@ public sealed class EvidenceBundlePackagingServiceTests
|
||||
Assert.Contains("manifest payload", exception.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EnsurePackageAsync_Throws_WhenManifestPayloadNotJson()
|
||||
{
|
||||
var rawPayload = Convert.ToBase64String(Encoding.UTF8.GetBytes("not-json"));
|
||||
@@ -433,6 +441,7 @@ public sealed class EvidenceBundlePackagingServiceTests
|
||||
{
|
||||
Stored = true;
|
||||
using var memory = new MemoryStream();
|
||||
using StellaOps.TestKit;
|
||||
content.CopyTo(memory);
|
||||
StoredBytes = memory.ToArray();
|
||||
|
||||
|
||||
@@ -34,7 +34,8 @@ public sealed class EvidenceLockerIntegrationTests : IDisposable
|
||||
|
||||
#region EVIDENCE-5100-007: Store → Retrieve → Verify Hash
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task StoreArtifact_ThenRetrieve_HashMatches()
|
||||
{
|
||||
// Arrange
|
||||
@@ -94,7 +95,8 @@ public sealed class EvidenceLockerIntegrationTests : IDisposable
|
||||
retrievedRootHash.Should().Be(storedRootHash, "Root hash should match between store and retrieve");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task StoreArtifact_ThenDownload_ContainsCorrectManifest()
|
||||
{
|
||||
// Arrange
|
||||
@@ -133,7 +135,8 @@ public sealed class EvidenceLockerIntegrationTests : IDisposable
|
||||
manifestDoc.RootElement.GetProperty("bundleId").GetString().Should().Be(bundleId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task StoreMultipleArtifacts_EachHasUniqueHash()
|
||||
{
|
||||
// Arrange
|
||||
@@ -180,7 +183,8 @@ public sealed class EvidenceLockerIntegrationTests : IDisposable
|
||||
hashes.Should().OnlyHaveUniqueItems("Each bundle should have a unique root hash");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task StoreArtifact_SignatureIsValid()
|
||||
{
|
||||
// Arrange
|
||||
@@ -207,7 +211,8 @@ public sealed class EvidenceLockerIntegrationTests : IDisposable
|
||||
signature.TryGetProperty("timestampToken", out var timestampToken).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task StoreArtifact_ThenRetrieve_MetadataPreserved()
|
||||
{
|
||||
// Arrange
|
||||
@@ -266,7 +271,8 @@ public sealed class EvidenceLockerIntegrationTests : IDisposable
|
||||
metadataDict["pipelineId"].Should().Be("pipe-123");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task StoreArtifact_TimelineEventEmitted()
|
||||
{
|
||||
// Arrange
|
||||
@@ -296,7 +302,8 @@ public sealed class EvidenceLockerIntegrationTests : IDisposable
|
||||
|
||||
#region Portable Bundle Integration
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task StoreArtifact_PortableDownload_IsSanitized()
|
||||
{
|
||||
// Arrange
|
||||
@@ -386,6 +393,7 @@ public sealed class EvidenceLockerIntegrationTests : IDisposable
|
||||
if (entry.DataStream is not null)
|
||||
{
|
||||
using var contentStream = new MemoryStream();
|
||||
using StellaOps.TestKit;
|
||||
entry.DataStream.CopyTo(contentStream);
|
||||
entries[entry.Name] = Encoding.UTF8.GetString(contentStream.ToArray());
|
||||
}
|
||||
|
||||
@@ -40,7 +40,8 @@ public sealed class EvidenceLockerWebServiceContractTests : IDisposable
|
||||
|
||||
#region EVIDENCE-5100-004: Contract Tests (OpenAPI Snapshot)
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task StoreArtifact_Endpoint_Returns_Expected_Schema()
|
||||
{
|
||||
// Arrange
|
||||
@@ -70,7 +71,8 @@ public sealed class EvidenceLockerWebServiceContractTests : IDisposable
|
||||
signature.ValueKind.Should().Be(JsonValueKind.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task RetrieveArtifact_Endpoint_Returns_Expected_Schema()
|
||||
{
|
||||
// Arrange
|
||||
@@ -95,6 +97,7 @@ public sealed class EvidenceLockerWebServiceContractTests : IDisposable
|
||||
|
||||
var content = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
|
||||
using var doc = JsonDocument.Parse(content);
|
||||
using StellaOps.TestKit;
|
||||
var root = doc.RootElement;
|
||||
|
||||
// Verify contract schema for retrieved bundle
|
||||
@@ -104,7 +107,8 @@ public sealed class EvidenceLockerWebServiceContractTests : IDisposable
|
||||
root.TryGetProperty("createdAt", out _).Should().BeTrue("createdAt should be present");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task DownloadArtifact_Endpoint_Returns_GzipMediaType()
|
||||
{
|
||||
// Arrange
|
||||
@@ -129,7 +133,8 @@ public sealed class EvidenceLockerWebServiceContractTests : IDisposable
|
||||
response.Content.Headers.ContentType?.MediaType.Should().Be("application/gzip");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Contract_ErrorResponse_Schema_Is_Consistent()
|
||||
{
|
||||
// Arrange - No auth headers (should fail)
|
||||
@@ -144,7 +149,8 @@ public sealed class EvidenceLockerWebServiceContractTests : IDisposable
|
||||
response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Contract_NotFound_Response_Schema()
|
||||
{
|
||||
// Arrange
|
||||
@@ -163,7 +169,8 @@ public sealed class EvidenceLockerWebServiceContractTests : IDisposable
|
||||
|
||||
#region EVIDENCE-5100-005: Auth Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task StoreArtifact_Without_Auth_Returns_Unauthorized()
|
||||
{
|
||||
// Arrange - No auth headers
|
||||
@@ -178,7 +185,8 @@ public sealed class EvidenceLockerWebServiceContractTests : IDisposable
|
||||
response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task StoreArtifact_Without_CreateScope_Returns_Forbidden()
|
||||
{
|
||||
// Arrange - Auth but no create scope
|
||||
@@ -195,7 +203,8 @@ public sealed class EvidenceLockerWebServiceContractTests : IDisposable
|
||||
response.StatusCode.Should().BeOneOf(HttpStatusCode.Forbidden, HttpStatusCode.Unauthorized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task StoreArtifact_With_CreateScope_Succeeds()
|
||||
{
|
||||
// Arrange
|
||||
@@ -212,7 +221,8 @@ public sealed class EvidenceLockerWebServiceContractTests : IDisposable
|
||||
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task RetrieveArtifact_Without_ReadScope_Returns_Forbidden()
|
||||
{
|
||||
// Arrange - Create with proper scope
|
||||
@@ -238,7 +248,8 @@ public sealed class EvidenceLockerWebServiceContractTests : IDisposable
|
||||
response.StatusCode.Should().BeOneOf(HttpStatusCode.Forbidden, HttpStatusCode.Unauthorized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CrossTenant_Access_Returns_NotFound_Or_Forbidden()
|
||||
{
|
||||
// Arrange - Create bundle as tenant A
|
||||
@@ -265,7 +276,8 @@ public sealed class EvidenceLockerWebServiceContractTests : IDisposable
|
||||
response.StatusCode.Should().BeOneOf(HttpStatusCode.NotFound, HttpStatusCode.Forbidden);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Download_Without_ReadScope_Returns_Forbidden()
|
||||
{
|
||||
// Arrange
|
||||
@@ -295,7 +307,8 @@ public sealed class EvidenceLockerWebServiceContractTests : IDisposable
|
||||
|
||||
#region EVIDENCE-5100-006: OTel Trace Assertions
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task StoreArtifact_Emits_OTel_Trace_With_ArtifactId()
|
||||
{
|
||||
// Arrange
|
||||
@@ -342,7 +355,8 @@ public sealed class EvidenceLockerWebServiceContractTests : IDisposable
|
||||
listener.Dispose();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task StoreArtifact_Timeline_Contains_TenantId()
|
||||
{
|
||||
// Arrange
|
||||
@@ -364,7 +378,8 @@ public sealed class EvidenceLockerWebServiceContractTests : IDisposable
|
||||
// Note: Actual assertion depends on how tenant_id is encoded in timeline events
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task RetrieveArtifact_Emits_Trace_With_BundleId()
|
||||
{
|
||||
// Arrange
|
||||
@@ -391,7 +406,8 @@ public sealed class EvidenceLockerWebServiceContractTests : IDisposable
|
||||
// Timeline events may or may not be emitted on read depending on configuration
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Error_Response_Does_Not_Leak_Internal_Details()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -24,7 +24,8 @@ namespace StellaOps.EvidenceLocker.Tests;
|
||||
|
||||
public sealed class EvidenceLockerWebServiceTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Snapshot_ReturnsSignatureAndEmitsTimeline()
|
||||
{
|
||||
using var factory = new EvidenceLockerWebApplicationFactory();
|
||||
@@ -70,7 +71,8 @@ public sealed class EvidenceLockerWebServiceTests
|
||||
Assert.Equal(snapshot.Signature.TimestampToken, bundle.Signature.TimestampToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Snapshot_WithIncidentModeActive_ExtendsRetentionAndCapturesDebugArtifact()
|
||||
{
|
||||
using var baseFactory = new EvidenceLockerWebApplicationFactory();
|
||||
@@ -117,7 +119,8 @@ public sealed class EvidenceLockerWebServiceTests
|
||||
Assert.Contains("enabled", timeline.IncidentEvents);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Download_ReturnsPackageStream()
|
||||
{
|
||||
using var factory = new EvidenceLockerWebApplicationFactory();
|
||||
@@ -171,7 +174,8 @@ public sealed class EvidenceLockerWebServiceTests
|
||||
Assert.NotEmpty(factory.ObjectStore.StoredObjects);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task PortableDownload_ReturnsSanitizedBundle()
|
||||
{
|
||||
using var factory = new EvidenceLockerWebApplicationFactory();
|
||||
@@ -217,7 +221,8 @@ public sealed class EvidenceLockerWebServiceTests
|
||||
Assert.Contains("stella evidence verify", script, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Snapshot_ReturnsValidationError_WhenQuotaExceeded()
|
||||
{
|
||||
using var factory = new EvidenceLockerWebApplicationFactory();
|
||||
@@ -246,7 +251,8 @@ public sealed class EvidenceLockerWebServiceTests
|
||||
Assert.Contains(messages, m => m.Contains("exceeds", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Snapshot_ReturnsForbidden_WhenTenantMissing()
|
||||
{
|
||||
using var factory = new EvidenceLockerWebApplicationFactory();
|
||||
@@ -270,7 +276,8 @@ public sealed class EvidenceLockerWebServiceTests
|
||||
Assert.True(response.StatusCode == HttpStatusCode.Forbidden, $"Expected 403 but received {(int)response.StatusCode}: {responseContent}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Hold_ReturnsConflict_WhenCaseAlreadyExists()
|
||||
{
|
||||
using var factory = new EvidenceLockerWebApplicationFactory();
|
||||
@@ -297,7 +304,8 @@ public sealed class EvidenceLockerWebServiceTests
|
||||
Assert.Contains(messages, m => m.IndexOf("already exists", StringComparison.OrdinalIgnoreCase) >= 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Hold_CreatesTimelineEvent()
|
||||
{
|
||||
using var factory = new EvidenceLockerWebApplicationFactory();
|
||||
@@ -337,6 +345,7 @@ public sealed class EvidenceLockerWebServiceTests
|
||||
}
|
||||
|
||||
using var entryStream = new MemoryStream();
|
||||
using StellaOps.TestKit;
|
||||
entry.DataStream!.CopyTo(entryStream);
|
||||
var content = Encoding.UTF8.GetString(entryStream.ToArray());
|
||||
entries[entry.Name] = content;
|
||||
|
||||
@@ -18,7 +18,8 @@ public sealed class EvidencePortableBundleServiceTests
|
||||
private static readonly EvidenceBundleId BundleId = EvidenceBundleId.FromGuid(Guid.NewGuid());
|
||||
private static readonly DateTimeOffset CreatedAt = new(2025, 11, 4, 10, 30, 0, TimeSpan.Zero);
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EnsurePortablePackageAsync_ReturnsCached_WhenObjectExists()
|
||||
{
|
||||
var bundle = CreateSealedBundle(
|
||||
@@ -37,7 +38,8 @@ public sealed class EvidencePortableBundleServiceTests
|
||||
Assert.False(repository.PortableStorageKeyUpdated);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EnsurePortablePackageAsync_CreatesPortableArchiveWithRedactedMetadata()
|
||||
{
|
||||
var repository = new FakeRepository(CreateSealedBundle(), CreateSignature(includeTimestamp: true));
|
||||
@@ -84,7 +86,8 @@ public sealed class EvidencePortableBundleServiceTests
|
||||
Assert.Contains("stella evidence verify", script, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EnsurePortablePackageAsync_Throws_WhenSignatureMissing()
|
||||
{
|
||||
var repository = new FakeRepository(CreateSealedBundle(), signature: null);
|
||||
@@ -94,7 +97,8 @@ public sealed class EvidencePortableBundleServiceTests
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => service.EnsurePortablePackageAsync(TenantId, BundleId, CancellationToken.None));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task EnsurePortablePackageAsync_ProducesDeterministicTarEntryMetadata()
|
||||
{
|
||||
var repository = new FakeRepository(CreateSealedBundle(), CreateSignature(includeTimestamp: true));
|
||||
@@ -331,6 +335,7 @@ public sealed class EvidencePortableBundleServiceTests
|
||||
{
|
||||
Stored = true;
|
||||
using var memory = new MemoryStream();
|
||||
using StellaOps.TestKit;
|
||||
content.CopyTo(memory);
|
||||
StoredBytes = memory.ToArray();
|
||||
|
||||
|
||||
@@ -22,7 +22,8 @@ public sealed class EvidenceSignatureServiceTests
|
||||
{
|
||||
private static readonly SigningKeyMaterialOptions TestKeyMaterial = CreateKeyMaterial();
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SignManifestAsync_SignsManifestWithoutTimestamp_WhenTimestampingDisabled()
|
||||
{
|
||||
var timestampClient = new FakeTimestampAuthorityClient();
|
||||
@@ -46,7 +47,8 @@ public sealed class EvidenceSignatureServiceTests
|
||||
Assert.Equal(0, timestampClient.CallCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SignManifestAsync_AttachesTimestamp_WhenAuthorityClientSucceeds()
|
||||
{
|
||||
var timestampClient = new FakeTimestampAuthorityClient
|
||||
@@ -81,7 +83,8 @@ public sealed class EvidenceSignatureServiceTests
|
||||
Assert.Equal(1, timestampClient.CallCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SignManifestAsync_Throws_WhenTimestampRequiredAndClientFails()
|
||||
{
|
||||
var timestampClient = new FakeTimestampAuthorityClient
|
||||
@@ -107,7 +110,8 @@ public sealed class EvidenceSignatureServiceTests
|
||||
CancellationToken.None));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SignManifestAsync_ProducesDeterministicPayload()
|
||||
{
|
||||
var timestampClient = new FakeTimestampAuthorityClient();
|
||||
@@ -194,6 +198,7 @@ public sealed class EvidenceSignatureServiceTests
|
||||
private static SigningKeyMaterialOptions CreateKeyMaterial()
|
||||
{
|
||||
using var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
|
||||
using StellaOps.TestKit;
|
||||
var privatePem = ecdsa.ExportECPrivateKeyPem();
|
||||
var publicPem = ecdsa.ExportSubjectPublicKeyInfoPem();
|
||||
return new SigningKeyMaterialOptions
|
||||
|
||||
@@ -75,7 +75,8 @@ public sealed class EvidenceSnapshotServiceTests
|
||||
NullLogger<EvidenceSnapshotService>.Instance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CreateSnapshotAsync_PersistsBundleAndBuildsManifest()
|
||||
{
|
||||
var request = new EvidenceSnapshotRequest
|
||||
@@ -109,7 +110,8 @@ public sealed class EvidenceSnapshotServiceTests
|
||||
Assert.False(_timelinePublisher.BundleSealedPublished);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CreateSnapshotAsync_StoresSignature_WhenSignerReturnsEnvelope()
|
||||
{
|
||||
var tenantId = TenantId.FromGuid(Guid.NewGuid());
|
||||
@@ -148,7 +150,8 @@ public sealed class EvidenceSnapshotServiceTests
|
||||
Assert.Equal(_repository.LastCreatedBundleId?.Value ?? Guid.Empty, result.BundleId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CreateSnapshotAsync_ThrowsWhenMaterialQuotaExceeded()
|
||||
{
|
||||
var request = new EvidenceSnapshotRequest
|
||||
@@ -165,7 +168,8 @@ public sealed class EvidenceSnapshotServiceTests
|
||||
_service.CreateSnapshotAsync(TenantId.FromGuid(Guid.NewGuid()), request, CancellationToken.None));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CreateHoldAsync_ReturnsHoldWhenValid()
|
||||
{
|
||||
var tenantId = TenantId.FromGuid(Guid.NewGuid());
|
||||
@@ -188,7 +192,8 @@ public sealed class EvidenceSnapshotServiceTests
|
||||
Assert.True(_timelinePublisher.HoldPublished);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CreateHoldAsyncThrowsWhenBundleMissing()
|
||||
{
|
||||
var tenantId = TenantId.FromGuid(Guid.NewGuid());
|
||||
@@ -202,7 +207,8 @@ public sealed class EvidenceSnapshotServiceTests
|
||||
}, CancellationToken.None));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CreateHoldAsyncThrowsWhenCaseAlreadyExists()
|
||||
{
|
||||
_repository.ThrowUniqueViolationForHolds = true;
|
||||
@@ -215,7 +221,8 @@ public sealed class EvidenceSnapshotServiceTests
|
||||
CancellationToken.None));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CreateSnapshotAsync_ExtendsRetentionAndCapturesIncidentArtifacts_WhenIncidentModeActive()
|
||||
{
|
||||
_incidentState.SetState(true, retentionExtensionDays: 45, captureSnapshot: true);
|
||||
@@ -468,6 +475,7 @@ public sealed class EvidenceSnapshotServiceTests
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
using var memory = new MemoryStream();
|
||||
using StellaOps.TestKit;
|
||||
content.CopyTo(memory);
|
||||
var bytes = memory.ToArray();
|
||||
|
||||
|
||||
@@ -16,7 +16,8 @@ public sealed class FileSystemEvidenceObjectStoreTests : IDisposable
|
||||
_rootPath = Path.Combine(Path.GetTempPath(), $"evidence-locker-tests-{Guid.NewGuid():N}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task StoreAsync_EnforcesWriteOnceWhenConfigured()
|
||||
{
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
@@ -30,7 +31,8 @@ public sealed class FileSystemEvidenceObjectStoreTests : IDisposable
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => store.StoreAsync(second, options, cancellationToken));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task StoreAsync_AllowsOverwriteWhenWriteOnceDisabled()
|
||||
{
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
@@ -41,6 +43,7 @@ public sealed class FileSystemEvidenceObjectStoreTests : IDisposable
|
||||
var firstMetadata = await store.StoreAsync(first, options, cancellationToken);
|
||||
|
||||
using var second = CreateStream("payload-1");
|
||||
using StellaOps.TestKit;
|
||||
var secondMetadata = await store.StoreAsync(second, options, cancellationToken);
|
||||
|
||||
Assert.Equal(firstMetadata.Sha256, secondMetadata.Sha256);
|
||||
|
||||
@@ -10,7 +10,8 @@ public sealed class GoldenFixturesTests
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SealedBundle_Fixture_HashAndSubjectMatch()
|
||||
{
|
||||
var root = FixturePath("sealed");
|
||||
@@ -35,7 +36,8 @@ public sealed class GoldenFixturesTests
|
||||
Assert.Equal(rootFromChecksums, recomputedSubject);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PortableBundle_Fixture_RedactionAndSubjectMatch()
|
||||
{
|
||||
var root = FixturePath("portable");
|
||||
@@ -54,7 +56,8 @@ public sealed class GoldenFixturesTests
|
||||
Assert.Equal(rootFromChecksums, recomputedSubject);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ReplayFixture_RecordDigestMatches()
|
||||
{
|
||||
var root = FixturePath("replay");
|
||||
@@ -72,6 +75,7 @@ public sealed class GoldenFixturesTests
|
||||
private static JsonElement ReadJson(string path)
|
||||
{
|
||||
using var doc = JsonDocument.Parse(File.ReadAllText(path), new JsonDocumentOptions { AllowTrailingCommas = true });
|
||||
using StellaOps.TestKit;
|
||||
return doc.RootElement.Clone();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,11 +12,13 @@ using StellaOps.EvidenceLocker.Core.Signing;
|
||||
using StellaOps.EvidenceLocker.Infrastructure.Signing;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.EvidenceLocker.Tests;
|
||||
|
||||
public sealed class Rfc3161TimestampAuthorityClientTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task RequestTimestampAsync_ReturnsNull_WhenAuthorityFailsAndTimestampOptional()
|
||||
{
|
||||
var handler = new StubHttpMessageHandler(_ => new HttpResponseMessage(HttpStatusCode.InternalServerError));
|
||||
@@ -33,7 +35,8 @@ public sealed class Rfc3161TimestampAuthorityClientTests
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task RequestTimestampAsync_Throws_WhenAuthorityFailsAndTimestampRequired()
|
||||
{
|
||||
var handler = new StubHttpMessageHandler(_ => new HttpResponseMessage(HttpStatusCode.InternalServerError));
|
||||
|
||||
@@ -15,7 +15,8 @@ namespace StellaOps.EvidenceLocker.Tests;
|
||||
|
||||
public sealed class S3EvidenceObjectStoreTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task StoreAsync_SetsIfNoneMatchAndMetadataWhenEnforcingWriteOnce()
|
||||
{
|
||||
var fakeClient = new FakeAmazonS3Client();
|
||||
@@ -58,7 +59,8 @@ public sealed class S3EvidenceObjectStoreTests
|
||||
Assert.Equal("\"etag\"", metadata.ETag);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task StoreAsync_DoesNotSetIfNoneMatchWhenWriteOnceDisabled()
|
||||
{
|
||||
var fakeClient = new FakeAmazonS3Client();
|
||||
@@ -112,6 +114,7 @@ public sealed class S3EvidenceObjectStoreTests
|
||||
var ifNoneMatch = request.Headers?["If-None-Match"];
|
||||
|
||||
using var memory = new MemoryStream();
|
||||
using StellaOps.TestKit;
|
||||
request.InputStream.CopyTo(memory);
|
||||
|
||||
PutRequests.Add(new CapturedPutObjectRequest(
|
||||
|
||||
@@ -14,7 +14,8 @@ namespace StellaOps.EvidenceLocker.Tests;
|
||||
|
||||
public sealed class TimelineIndexerEvidenceTimelinePublisherTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task PublishBundleSealedAsync_SendsExpectedPayload()
|
||||
{
|
||||
var tenantId = TenantId.FromGuid(Guid.NewGuid());
|
||||
@@ -92,7 +93,8 @@ public sealed class TimelineIndexerEvidenceTimelinePublisherTests
|
||||
Assert.Equal("primary", entry.GetProperty("attributes").GetProperty("role").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task PublishHoldCreatedAsync_ProducesHoldPayload()
|
||||
{
|
||||
var tenantId = TenantId.FromGuid(Guid.NewGuid());
|
||||
@@ -121,6 +123,7 @@ public sealed class TimelineIndexerEvidenceTimelinePublisherTests
|
||||
Assert.Equal(HttpMethod.Post, request.Method);
|
||||
|
||||
using var json = JsonDocument.Parse(request.Content!);
|
||||
using StellaOps.TestKit;
|
||||
var root = json.RootElement;
|
||||
Assert.Equal("evidence.hold.created", root.GetProperty("kind").GetString());
|
||||
Assert.Equal(hold.CaseId, root.GetProperty("attributes").GetProperty("caseId").GetString());
|
||||
|
||||
Reference in New Issue
Block a user