Fix build and code structure improvements. New but essential UI functionality. CI improvements. Documentation improvements. AI module improvements.

This commit is contained in:
StellaOps Bot
2025-12-26 21:54:17 +02:00
parent 335ff7da16
commit c2b9cd8d1f
3717 changed files with 264714 additions and 48202 deletions

View File

@@ -11,7 +11,6 @@ using StellaOps.EvidenceLocker.Core.Domain;
using StellaOps.EvidenceLocker.Infrastructure.Db;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.EvidenceLocker.Tests;
@@ -44,7 +43,7 @@ public sealed class DatabaseMigrationTests : IAsyncLifetime
Assert.Skip(_skipReason);
}
var cancellationToken = TestContext.Current.CancellationToken;
var cancellationToken = CancellationToken.None;
await _migrationRunner!.ApplyAsync(cancellationToken);
@@ -101,7 +100,6 @@ 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)

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Cryptography;
using StellaOps.EvidenceLocker.Core.Builders;
using StellaOps.EvidenceLocker.Core.Domain;
using StellaOps.EvidenceLocker.Core.Repositories;
@@ -19,7 +20,7 @@ public sealed class EvidenceBundleBuilderTests
public EvidenceBundleBuilderTests()
{
_builder = new EvidenceBundleBuilder(_repository, new MerkleTreeCalculator());
_builder = new EvidenceBundleBuilder(_repository, new MerkleTreeCalculator(new DefaultCryptoHasher(HashAlgorithms.Sha256)));
}
[Trait("Category", TestCategories.Unit)]

View File

@@ -64,7 +64,7 @@ public sealed class EvidenceBundleImmutabilityTests : IAsyncLifetime
Assert.Skip(_skipReason);
}
var cancellationToken = TestContext.Current.CancellationToken;
var cancellationToken = CancellationToken.None;
var tenantId = TenantId.FromGuid(Guid.NewGuid());
var bundleId = EvidenceBundleId.FromGuid(Guid.NewGuid());
var now = DateTimeOffset.UtcNow;
@@ -107,7 +107,7 @@ public sealed class EvidenceBundleImmutabilityTests : IAsyncLifetime
Assert.Skip(_skipReason);
}
var cancellationToken = TestContext.Current.CancellationToken;
var cancellationToken = CancellationToken.None;
var tenant1 = TenantId.FromGuid(Guid.NewGuid());
var tenant2 = TenantId.FromGuid(Guid.NewGuid());
var bundleId = EvidenceBundleId.FromGuid(Guid.NewGuid());
@@ -153,7 +153,7 @@ public sealed class EvidenceBundleImmutabilityTests : IAsyncLifetime
Assert.Skip(_skipReason);
}
var cancellationToken = TestContext.Current.CancellationToken;
var cancellationToken = CancellationToken.None;
var tenantId = TenantId.FromGuid(Guid.NewGuid());
var bundleId = EvidenceBundleId.FromGuid(Guid.NewGuid());
var now = DateTimeOffset.UtcNow;
@@ -193,7 +193,7 @@ public sealed class EvidenceBundleImmutabilityTests : IAsyncLifetime
Assert.Skip(_skipReason);
}
var cancellationToken = TestContext.Current.CancellationToken;
var cancellationToken = CancellationToken.None;
var tenantId = TenantId.FromGuid(Guid.NewGuid());
var bundleId = EvidenceBundleId.FromGuid(Guid.NewGuid());
var nonExistentBundleId = EvidenceBundleId.FromGuid(Guid.NewGuid());
@@ -235,7 +235,7 @@ public sealed class EvidenceBundleImmutabilityTests : IAsyncLifetime
Assert.Skip(_skipReason);
}
var cancellationToken = TestContext.Current.CancellationToken;
var cancellationToken = CancellationToken.None;
var tenantId = TenantId.FromGuid(Guid.NewGuid());
var bundleId = EvidenceBundleId.FromGuid(Guid.NewGuid());
var now = DateTimeOffset.UtcNow;
@@ -311,7 +311,7 @@ public sealed class EvidenceBundleImmutabilityTests : IAsyncLifetime
Assert.Skip(_skipReason);
}
var cancellationToken = TestContext.Current.CancellationToken;
var cancellationToken = CancellationToken.None;
var tenantId = TenantId.FromGuid(Guid.NewGuid());
var now = DateTimeOffset.UtcNow;
@@ -360,7 +360,7 @@ public sealed class EvidenceBundleImmutabilityTests : IAsyncLifetime
Assert.Skip(_skipReason);
}
var cancellationToken = TestContext.Current.CancellationToken;
var cancellationToken = CancellationToken.None;
var tenantId = TenantId.FromGuid(Guid.NewGuid());
var bundleId = EvidenceBundleId.FromGuid(Guid.NewGuid());
var now = DateTimeOffset.UtcNow;
@@ -407,7 +407,7 @@ public sealed class EvidenceBundleImmutabilityTests : IAsyncLifetime
Assert.Skip(_skipReason);
}
var cancellationToken = TestContext.Current.CancellationToken;
var cancellationToken = CancellationToken.None;
var tenantId = TenantId.FromGuid(Guid.NewGuid());
var bundleId = EvidenceBundleId.FromGuid(Guid.NewGuid());
var now = DateTimeOffset.UtcNow;
@@ -473,7 +473,7 @@ public sealed class EvidenceBundleImmutabilityTests : IAsyncLifetime
Assert.Skip(_skipReason);
}
var cancellationToken = TestContext.Current.CancellationToken;
var cancellationToken = CancellationToken.None;
var tenantId = TenantId.FromGuid(Guid.NewGuid());
var bundleId = EvidenceBundleId.FromGuid(Guid.NewGuid());
var now = DateTimeOffset.UtcNow;
@@ -515,7 +515,7 @@ public sealed class EvidenceBundleImmutabilityTests : IAsyncLifetime
Assert.Skip(_skipReason);
}
var cancellationToken = TestContext.Current.CancellationToken;
var cancellationToken = CancellationToken.None;
var tenantId = TenantId.FromGuid(Guid.NewGuid());
var bundleId = EvidenceBundleId.FromGuid(Guid.NewGuid());
var now = DateTimeOffset.UtcNow;
@@ -561,7 +561,7 @@ public sealed class EvidenceBundleImmutabilityTests : IAsyncLifetime
Assert.Skip(_skipReason);
}
var cancellationToken = TestContext.Current.CancellationToken;
var cancellationToken = CancellationToken.None;
var tenantId = TenantId.FromGuid(Guid.NewGuid());
var bundleId = EvidenceBundleId.FromGuid(Guid.NewGuid());
var now = DateTimeOffset.UtcNow;
@@ -589,7 +589,8 @@ public sealed class EvidenceBundleImmutabilityTests : IAsyncLifetime
CaseId: $"CASE-{i:D4}",
Reason: $"Legal hold reason {i}",
CreatedAt: now.AddMinutes(i),
ExpiresAt: now.AddDays(30 + i));
ExpiresAt: now.AddDays(30 + i),
ReleasedAt: null);
var createdHold = await _repository.CreateHoldAsync(hold, cancellationToken);
holds.Add(createdHold);

View File

@@ -1,4 +1,4 @@
using System.Buffers.Binary;
using System.Buffers.Binary;
using System.Formats.Tar;
using System.IO.Compression;
using System.Security.Cryptography;
@@ -348,11 +348,12 @@ public sealed class EvidenceBundlePackagingServiceTests
continue;
}
var posixEntry = entry as PosixTarEntry;
entries[entry.Name] = new TarEntryMetadata(
entry.Uid,
entry.Gid,
entry.UserName ?? string.Empty,
entry.GroupName ?? string.Empty,
posixEntry?.UserName ?? string.Empty,
posixEntry?.GroupName ?? string.Empty,
entry.ModificationTime);
}
@@ -443,7 +444,6 @@ public sealed class EvidenceBundlePackagingServiceTests
{
Stored = true;
using var memory = new MemoryStream();
using StellaOps.TestKit;
content.CopyTo(memory);
StoredBytes = memory.ToArray();

View File

@@ -1,8 +1,8 @@
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// EvidenceLockerIntegrationTests.cs
// Sprint: SPRINT_5100_0010_0001_evidencelocker_tests
// Task: EVIDENCE-5100-007
// Description: Integration test: store artifact retrieve artifact verify hash matches
// Description: Integration test: store artifact → retrieve artifact → verify hash matches
// -----------------------------------------------------------------------------
using System.Net;
@@ -20,7 +20,7 @@ namespace StellaOps.EvidenceLocker.Tests;
/// <summary>
/// Integration Tests for EvidenceLocker
/// Task EVIDENCE-5100-007: store artifact retrieve artifact verify hash matches
/// Task EVIDENCE-5100-007: store artifact → retrieve artifact → verify hash matches
/// </summary>
public sealed class EvidenceLockerIntegrationTests : IDisposable
{
@@ -34,7 +34,7 @@ public sealed class EvidenceLockerIntegrationTests : IDisposable
_client = _factory.CreateClient();
}
#region EVIDENCE-5100-007: Store Retrieve Verify Hash
#region EVIDENCE-5100-007: Store â Retrieve â Verify Hash
[Trait("Category", TestCategories.Unit)]
[Fact]
@@ -72,10 +72,10 @@ public sealed class EvidenceLockerIntegrationTests : IDisposable
var storeResponse = await _client.PostAsJsonAsync(
"/evidence/snapshot",
payload,
TestContext.Current.CancellationToken);
CancellationToken.None);
storeResponse.EnsureSuccessStatusCode();
var storeResult = await storeResponse.Content.ReadFromJsonAsync<JsonElement>(TestContext.Current.CancellationToken);
var storeResult = await storeResponse.Content.ReadFromJsonAsync<JsonElement>(CancellationToken.None);
var bundleId = storeResult.GetProperty("bundleId").GetString();
var storedRootHash = storeResult.GetProperty("rootHash").GetString();
@@ -85,10 +85,10 @@ public sealed class EvidenceLockerIntegrationTests : IDisposable
// Act - Retrieve
var retrieveResponse = await _client.GetAsync(
$"/evidence/{bundleId}",
TestContext.Current.CancellationToken);
CancellationToken.None);
retrieveResponse.EnsureSuccessStatusCode();
var retrieveResult = await retrieveResponse.Content.ReadFromJsonAsync<JsonElement>(TestContext.Current.CancellationToken);
var retrieveResult = await retrieveResponse.Content.ReadFromJsonAsync<JsonElement>(CancellationToken.None);
var retrievedRootHash = retrieveResult.GetProperty("rootHash").GetString();
var retrievedBundleId = retrieveResult.GetProperty("bundleId").GetString();
@@ -111,22 +111,22 @@ public sealed class EvidenceLockerIntegrationTests : IDisposable
var storeResponse = await _client.PostAsJsonAsync(
"/evidence/snapshot",
payload,
TestContext.Current.CancellationToken);
CancellationToken.None);
storeResponse.EnsureSuccessStatusCode();
var storeResult = await storeResponse.Content.ReadFromJsonAsync<JsonElement>(TestContext.Current.CancellationToken);
var storeResult = await storeResponse.Content.ReadFromJsonAsync<JsonElement>(CancellationToken.None);
var bundleId = storeResult.GetProperty("bundleId").GetString();
// Act - Download
var downloadResponse = await _client.GetAsync(
$"/evidence/{bundleId}/download",
TestContext.Current.CancellationToken);
CancellationToken.None);
downloadResponse.EnsureSuccessStatusCode();
// Assert
downloadResponse.Content.Headers.ContentType?.MediaType.Should().Be("application/gzip");
var archiveBytes = await downloadResponse.Content.ReadAsByteArrayAsync(TestContext.Current.CancellationToken);
var archiveBytes = await downloadResponse.Content.ReadAsByteArrayAsync(CancellationToken.None);
archiveBytes.Should().NotBeEmpty();
// Verify archive contains manifest with correct bundleId
@@ -174,10 +174,10 @@ public sealed class EvidenceLockerIntegrationTests : IDisposable
var response = await _client.PostAsJsonAsync(
"/evidence/snapshot",
payload,
TestContext.Current.CancellationToken);
CancellationToken.None);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<JsonElement>(TestContext.Current.CancellationToken);
var result = await response.Content.ReadFromJsonAsync<JsonElement>(CancellationToken.None);
hashes.Add(result.GetProperty("rootHash").GetString()!);
}
@@ -199,10 +199,10 @@ public sealed class EvidenceLockerIntegrationTests : IDisposable
var storeResponse = await _client.PostAsJsonAsync(
"/evidence/snapshot",
payload,
TestContext.Current.CancellationToken);
CancellationToken.None);
storeResponse.EnsureSuccessStatusCode();
var storeResult = await storeResponse.Content.ReadFromJsonAsync<JsonElement>(TestContext.Current.CancellationToken);
var storeResult = await storeResponse.Content.ReadFromJsonAsync<JsonElement>(CancellationToken.None);
// Assert - Signature should be present and valid
storeResult.TryGetProperty("signature", out var signature).Should().BeTrue();
@@ -249,19 +249,19 @@ public sealed class EvidenceLockerIntegrationTests : IDisposable
var storeResponse = await _client.PostAsJsonAsync(
"/evidence/snapshot",
payload,
TestContext.Current.CancellationToken);
CancellationToken.None);
storeResponse.EnsureSuccessStatusCode();
var storeResult = await storeResponse.Content.ReadFromJsonAsync<JsonElement>(TestContext.Current.CancellationToken);
var storeResult = await storeResponse.Content.ReadFromJsonAsync<JsonElement>(CancellationToken.None);
var bundleId = storeResult.GetProperty("bundleId").GetString();
// Act - Retrieve
var retrieveResponse = await _client.GetAsync(
$"/evidence/{bundleId}",
TestContext.Current.CancellationToken);
CancellationToken.None);
retrieveResponse.EnsureSuccessStatusCode();
var retrieveResult = await retrieveResponse.Content.ReadFromJsonAsync<JsonElement>(TestContext.Current.CancellationToken);
var retrieveResult = await retrieveResponse.Content.ReadFromJsonAsync<JsonElement>(CancellationToken.None);
// Assert - Metadata preserved
retrieveResult.TryGetProperty("metadata", out var retrievedMetadata).Should().BeTrue();
@@ -289,10 +289,10 @@ public sealed class EvidenceLockerIntegrationTests : IDisposable
var storeResponse = await _client.PostAsJsonAsync(
"/evidence/snapshot",
payload,
TestContext.Current.CancellationToken);
CancellationToken.None);
storeResponse.EnsureSuccessStatusCode();
var storeResult = await storeResponse.Content.ReadFromJsonAsync<JsonElement>(TestContext.Current.CancellationToken);
var storeResult = await storeResponse.Content.ReadFromJsonAsync<JsonElement>(CancellationToken.None);
var bundleId = storeResult.GetProperty("bundleId").GetString();
// Assert - Timeline event emitted
@@ -318,22 +318,22 @@ public sealed class EvidenceLockerIntegrationTests : IDisposable
var storeResponse = await _client.PostAsJsonAsync(
"/evidence/snapshot",
payload,
TestContext.Current.CancellationToken);
CancellationToken.None);
storeResponse.EnsureSuccessStatusCode();
var storeResult = await storeResponse.Content.ReadFromJsonAsync<JsonElement>(TestContext.Current.CancellationToken);
var storeResult = await storeResponse.Content.ReadFromJsonAsync<JsonElement>(CancellationToken.None);
var bundleId = storeResult.GetProperty("bundleId").GetString();
// Act - Portable download
var portableResponse = await _client.GetAsync(
$"/evidence/{bundleId}/portable",
TestContext.Current.CancellationToken);
CancellationToken.None);
portableResponse.EnsureSuccessStatusCode();
// Assert
portableResponse.Content.Headers.ContentType?.MediaType.Should().Be("application/gzip");
var archiveBytes = await portableResponse.Content.ReadAsByteArrayAsync(TestContext.Current.CancellationToken);
var archiveBytes = await portableResponse.Content.ReadAsByteArrayAsync(CancellationToken.None);
var entries = ReadGzipTarEntries(archiveBytes);
// Portable bundle should have manifest but be sanitized
@@ -395,7 +395,6 @@ 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());
}

View File

@@ -29,9 +29,11 @@ using StellaOps.EvidenceLocker.Core.Incident;
using StellaOps.EvidenceLocker.Core.Timeline;
using StellaOps.EvidenceLocker.Core.Storage;
using EvidenceLockerProgram = StellaOps.EvidenceLocker.WebService.Program;
namespace StellaOps.EvidenceLocker.Tests;
internal sealed class EvidenceLockerWebApplicationFactory : WebApplicationFactory<Program>
internal sealed class EvidenceLockerWebApplicationFactory : WebApplicationFactory<EvidenceLockerProgram>
{
private readonly string _contentRoot;

View File

@@ -1,4 +1,4 @@
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// EvidenceLockerWebServiceContractTests.cs
// Sprint: SPRINT_5100_0010_0001_evidencelocker_tests
// Tasks: EVIDENCE-5100-004, EVIDENCE-5100-005, EVIDENCE-5100-006
@@ -53,12 +53,12 @@ public sealed class EvidenceLockerWebServiceContractTests : IDisposable
var payload = CreateValidSnapshotPayload();
// Act
var response = await _client.PostAsJsonAsync("/evidence/snapshot", payload, TestContext.Current.CancellationToken);
var response = await _client.PostAsJsonAsync("/evidence/snapshot", payload, CancellationToken.None);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
var content = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
var content = await response.Content.ReadAsStringAsync(CancellationToken.None);
using var doc = JsonDocument.Parse(content);
var root = doc.RootElement;
@@ -85,21 +85,20 @@ public sealed class EvidenceLockerWebServiceContractTests : IDisposable
var createResponse = await _client.PostAsJsonAsync(
"/evidence/snapshot",
CreateValidSnapshotPayload(),
TestContext.Current.CancellationToken);
CancellationToken.None);
createResponse.EnsureSuccessStatusCode();
var created = await createResponse.Content.ReadFromJsonAsync<JsonElement>(TestContext.Current.CancellationToken);
var created = await createResponse.Content.ReadFromJsonAsync<JsonElement>(CancellationToken.None);
var bundleId = created.GetProperty("bundleId").GetString();
// Act
var response = await _client.GetAsync($"/evidence/{bundleId}", TestContext.Current.CancellationToken);
var response = await _client.GetAsync($"/evidence/{bundleId}", CancellationToken.None);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
var content = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
var content = await response.Content.ReadAsStringAsync(CancellationToken.None);
using var doc = JsonDocument.Parse(content);
using StellaOps.TestKit;
var root = doc.RootElement;
// Verify contract schema for retrieved bundle
@@ -121,14 +120,14 @@ using StellaOps.TestKit;
var createResponse = await _client.PostAsJsonAsync(
"/evidence/snapshot",
CreateValidSnapshotPayload(),
TestContext.Current.CancellationToken);
CancellationToken.None);
createResponse.EnsureSuccessStatusCode();
var created = await createResponse.Content.ReadFromJsonAsync<JsonElement>(TestContext.Current.CancellationToken);
var created = await createResponse.Content.ReadFromJsonAsync<JsonElement>(CancellationToken.None);
var bundleId = created.GetProperty("bundleId").GetString();
// Act
var response = await _client.GetAsync($"/evidence/{bundleId}/download", TestContext.Current.CancellationToken);
var response = await _client.GetAsync($"/evidence/{bundleId}/download", CancellationToken.None);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
@@ -145,7 +144,7 @@ using StellaOps.TestKit;
var response = await _client.PostAsJsonAsync(
"/evidence/snapshot",
CreateValidSnapshotPayload(),
TestContext.Current.CancellationToken);
CancellationToken.None);
// Assert - Unauthorized should return consistent error schema
response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
@@ -161,7 +160,7 @@ using StellaOps.TestKit;
var nonExistentId = Guid.NewGuid();
// Act
var response = await _client.GetAsync($"/evidence/{nonExistentId}", TestContext.Current.CancellationToken);
var response = await _client.GetAsync($"/evidence/{nonExistentId}", CancellationToken.None);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
@@ -181,7 +180,7 @@ using StellaOps.TestKit;
var response = await _client.PostAsJsonAsync(
"/evidence/snapshot",
CreateValidSnapshotPayload(),
TestContext.Current.CancellationToken);
CancellationToken.None);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
@@ -199,7 +198,7 @@ using StellaOps.TestKit;
var response = await _client.PostAsJsonAsync(
"/evidence/snapshot",
CreateValidSnapshotPayload(),
TestContext.Current.CancellationToken);
CancellationToken.None);
// Assert
response.StatusCode.Should().BeOneOf(HttpStatusCode.Forbidden, HttpStatusCode.Unauthorized);
@@ -217,7 +216,7 @@ using StellaOps.TestKit;
var response = await _client.PostAsJsonAsync(
"/evidence/snapshot",
CreateValidSnapshotPayload(),
TestContext.Current.CancellationToken);
CancellationToken.None);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
@@ -234,17 +233,17 @@ using StellaOps.TestKit;
var createResponse = await _client.PostAsJsonAsync(
"/evidence/snapshot",
CreateValidSnapshotPayload(),
TestContext.Current.CancellationToken);
CancellationToken.None);
createResponse.EnsureSuccessStatusCode();
var created = await createResponse.Content.ReadFromJsonAsync<JsonElement>(TestContext.Current.CancellationToken);
var created = await createResponse.Content.ReadFromJsonAsync<JsonElement>(CancellationToken.None);
var bundleId = created.GetProperty("bundleId").GetString();
// Change to no read scope
ConfigureAuthHeaders(_client, tenantId, scopes: StellaOpsScopes.EvidenceCreate);
// Act
var response = await _client.GetAsync($"/evidence/{bundleId}", TestContext.Current.CancellationToken);
var response = await _client.GetAsync($"/evidence/{bundleId}", CancellationToken.None);
// Assert
response.StatusCode.Should().BeOneOf(HttpStatusCode.Forbidden, HttpStatusCode.Unauthorized);
@@ -261,10 +260,10 @@ using StellaOps.TestKit;
var createResponse = await _client.PostAsJsonAsync(
"/evidence/snapshot",
CreateValidSnapshotPayload(),
TestContext.Current.CancellationToken);
CancellationToken.None);
createResponse.EnsureSuccessStatusCode();
var created = await createResponse.Content.ReadFromJsonAsync<JsonElement>(TestContext.Current.CancellationToken);
var created = await createResponse.Content.ReadFromJsonAsync<JsonElement>(CancellationToken.None);
var bundleId = created.GetProperty("bundleId").GetString();
// Try to access as tenant B
@@ -272,7 +271,7 @@ using StellaOps.TestKit;
ConfigureAuthHeaders(_client, tenantB, scopes: $"{StellaOpsScopes.EvidenceRead}");
// Act
var response = await _client.GetAsync($"/evidence/{bundleId}", TestContext.Current.CancellationToken);
var response = await _client.GetAsync($"/evidence/{bundleId}", CancellationToken.None);
// Assert - Should not be accessible across tenants
response.StatusCode.Should().BeOneOf(HttpStatusCode.NotFound, HttpStatusCode.Forbidden);
@@ -289,17 +288,17 @@ using StellaOps.TestKit;
var createResponse = await _client.PostAsJsonAsync(
"/evidence/snapshot",
CreateValidSnapshotPayload(),
TestContext.Current.CancellationToken);
CancellationToken.None);
createResponse.EnsureSuccessStatusCode();
var created = await createResponse.Content.ReadFromJsonAsync<JsonElement>(TestContext.Current.CancellationToken);
var created = await createResponse.Content.ReadFromJsonAsync<JsonElement>(CancellationToken.None);
var bundleId = created.GetProperty("bundleId").GetString();
// Remove read scope
ConfigureAuthHeaders(_client, tenantId, scopes: StellaOpsScopes.EvidenceCreate);
// Act
var response = await _client.GetAsync($"/evidence/{bundleId}/download", TestContext.Current.CancellationToken);
var response = await _client.GetAsync($"/evidence/{bundleId}/download", CancellationToken.None);
// Assert
response.StatusCode.Should().BeOneOf(HttpStatusCode.Forbidden, HttpStatusCode.Unauthorized);
@@ -340,10 +339,10 @@ using StellaOps.TestKit;
var response = await _client.PostAsJsonAsync(
"/evidence/snapshot",
CreateValidSnapshotPayload(),
TestContext.Current.CancellationToken);
CancellationToken.None);
response.EnsureSuccessStatusCode();
var created = await response.Content.ReadFromJsonAsync<JsonElement>(TestContext.Current.CancellationToken);
var created = await response.Content.ReadFromJsonAsync<JsonElement>(CancellationToken.None);
var bundleId = created.GetProperty("bundleId").GetString();
// Assert
@@ -369,7 +368,7 @@ using StellaOps.TestKit;
var response = await _client.PostAsJsonAsync(
"/evidence/snapshot",
CreateValidSnapshotPayload(),
TestContext.Current.CancellationToken);
CancellationToken.None);
response.EnsureSuccessStatusCode();
// Assert
@@ -391,17 +390,17 @@ using StellaOps.TestKit;
var createResponse = await _client.PostAsJsonAsync(
"/evidence/snapshot",
CreateValidSnapshotPayload(),
TestContext.Current.CancellationToken);
CancellationToken.None);
createResponse.EnsureSuccessStatusCode();
var created = await createResponse.Content.ReadFromJsonAsync<JsonElement>(TestContext.Current.CancellationToken);
var created = await createResponse.Content.ReadFromJsonAsync<JsonElement>(CancellationToken.None);
var bundleId = created.GetProperty("bundleId").GetString();
// Clear timeline events before retrieve
_factory.TimelinePublisher.ClearEvents();
// Act
var response = await _client.GetAsync($"/evidence/{bundleId}", TestContext.Current.CancellationToken);
var response = await _client.GetAsync($"/evidence/{bundleId}", CancellationToken.None);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
@@ -417,12 +416,12 @@ using StellaOps.TestKit;
ConfigureAuthHeaders(_client, tenantId, scopes: StellaOpsScopes.EvidenceRead);
// Act - Request non-existent bundle
var response = await _client.GetAsync($"/evidence/{Guid.NewGuid()}", TestContext.Current.CancellationToken);
var response = await _client.GetAsync($"/evidence/{Guid.NewGuid()}", CancellationToken.None);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
var content = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
var content = await response.Content.ReadAsStringAsync(CancellationToken.None);
content.Should().NotContain("Exception");
content.Should().NotContain("StackTrace");
content.Should().NotContain("InnerException");

View File

@@ -1,4 +1,4 @@
using System.Buffers.Binary;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Formats.Tar;
using System.IO;
@@ -50,10 +50,10 @@ public sealed class EvidenceLockerWebServiceTests
}
};
var snapshotResponse = await client.PostAsJsonAsync("/evidence/snapshot", payload, TestContext.Current.CancellationToken);
var snapshotResponse = await client.PostAsJsonAsync("/evidence/snapshot", payload, CancellationToken.None);
snapshotResponse.EnsureSuccessStatusCode();
var snapshot = await snapshotResponse.Content.ReadFromJsonAsync<EvidenceSnapshotResponseDto>(TestContext.Current.CancellationToken);
var snapshot = await snapshotResponse.Content.ReadFromJsonAsync<EvidenceSnapshotResponseDto>(CancellationToken.None);
Assert.NotNull(snapshot);
Assert.NotEqual(Guid.Empty, snapshot!.BundleId);
Assert.False(string.IsNullOrEmpty(snapshot.RootHash));
@@ -65,7 +65,7 @@ public sealed class EvidenceLockerWebServiceTests
Assert.Contains(snapshot.BundleId.ToString("D"), timelineEvent);
Assert.Contains(snapshot.RootHash, timelineEvent);
var bundle = await client.GetFromJsonAsync<EvidenceBundleResponseDto>($"/evidence/{snapshot.BundleId}", TestContext.Current.CancellationToken);
var bundle = await client.GetFromJsonAsync<EvidenceBundleResponseDto>($"/evidence/{snapshot.BundleId}", CancellationToken.None);
Assert.NotNull(bundle);
Assert.Equal(snapshot.RootHash, bundle!.RootHash);
Assert.NotNull(bundle.Signature);
@@ -105,13 +105,13 @@ public sealed class EvidenceLockerWebServiceTests
}
};
var snapshotResponse = await client.PostAsJsonAsync("/evidence/snapshot", payload, TestContext.Current.CancellationToken);
var snapshotResponse = await client.PostAsJsonAsync("/evidence/snapshot", payload, CancellationToken.None);
snapshotResponse.EnsureSuccessStatusCode();
var snapshot = await snapshotResponse.Content.ReadFromJsonAsync<EvidenceSnapshotResponseDto>(TestContext.Current.CancellationToken);
var snapshot = await snapshotResponse.Content.ReadFromJsonAsync<EvidenceSnapshotResponseDto>(CancellationToken.None);
Assert.NotNull(snapshot);
var bundle = await client.GetFromJsonAsync<EvidenceBundleResponseDto>($"/evidence/{snapshot!.BundleId}", TestContext.Current.CancellationToken);
var bundle = await client.GetFromJsonAsync<EvidenceBundleResponseDto>($"/evidence/{snapshot!.BundleId}", CancellationToken.None);
Assert.NotNull(bundle);
Assert.NotNull(bundle!.ExpiresAt);
Assert.True(bundle.ExpiresAt > bundle.CreatedAt);
@@ -141,17 +141,17 @@ public sealed class EvidenceLockerWebServiceTests
}
};
var snapshotResponse = await client.PostAsJsonAsync("/evidence/snapshot", payload, TestContext.Current.CancellationToken);
var snapshotResponse = await client.PostAsJsonAsync("/evidence/snapshot", payload, CancellationToken.None);
snapshotResponse.EnsureSuccessStatusCode();
var snapshot = await snapshotResponse.Content.ReadFromJsonAsync<EvidenceSnapshotResponseDto>(TestContext.Current.CancellationToken);
var snapshot = await snapshotResponse.Content.ReadFromJsonAsync<EvidenceSnapshotResponseDto>(CancellationToken.None);
Assert.NotNull(snapshot);
var downloadResponse = await client.GetAsync($"/evidence/{snapshot!.BundleId}/download", TestContext.Current.CancellationToken);
var downloadResponse = await client.GetAsync($"/evidence/{snapshot!.BundleId}/download", CancellationToken.None);
downloadResponse.EnsureSuccessStatusCode();
Assert.Equal("application/gzip", downloadResponse.Content.Headers.ContentType?.MediaType);
var archiveBytes = await downloadResponse.Content.ReadAsByteArrayAsync(TestContext.Current.CancellationToken);
var archiveBytes = await downloadResponse.Content.ReadAsByteArrayAsync(CancellationToken.None);
var mtime = BinaryPrimitives.ReadInt32LittleEndian(archiveBytes.AsSpan(4, 4));
var expectedSeconds = (int)(new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero) - DateTimeOffset.UnixEpoch).TotalSeconds;
Assert.Equal(expectedSeconds, mtime);
@@ -196,16 +196,16 @@ public sealed class EvidenceLockerWebServiceTests
}
};
var snapshotResponse = await client.PostAsJsonAsync("/evidence/snapshot", payload, TestContext.Current.CancellationToken);
var snapshotResponse = await client.PostAsJsonAsync("/evidence/snapshot", payload, CancellationToken.None);
snapshotResponse.EnsureSuccessStatusCode();
var snapshot = await snapshotResponse.Content.ReadFromJsonAsync<EvidenceSnapshotResponseDto>(TestContext.Current.CancellationToken);
var snapshot = await snapshotResponse.Content.ReadFromJsonAsync<EvidenceSnapshotResponseDto>(CancellationToken.None);
Assert.NotNull(snapshot);
var portableResponse = await client.GetAsync($"/evidence/{snapshot!.BundleId}/portable", TestContext.Current.CancellationToken);
var portableResponse = await client.GetAsync($"/evidence/{snapshot!.BundleId}/portable", CancellationToken.None);
portableResponse.EnsureSuccessStatusCode();
Assert.Equal("application/gzip", portableResponse.Content.Headers.ContentType?.MediaType);
var archiveBytes = await portableResponse.Content.ReadAsByteArrayAsync(TestContext.Current.CancellationToken);
var archiveBytes = await portableResponse.Content.ReadAsByteArrayAsync(CancellationToken.None);
var entries = ReadArchiveEntries(archiveBytes);
Assert.Contains("bundle.json", entries.Keys);
Assert.Contains("instructions-portable.txt", entries.Keys);
@@ -243,11 +243,11 @@ public sealed class EvidenceLockerWebServiceTests
}
};
var response = await client.PostAsJsonAsync("/evidence/snapshot", payload, TestContext.Current.CancellationToken);
var response = await client.PostAsJsonAsync("/evidence/snapshot", payload, CancellationToken.None);
var responseContent = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
var responseContent = await response.Content.ReadAsStringAsync(CancellationToken.None);
Assert.True(response.StatusCode == HttpStatusCode.BadRequest, $"Expected 400 but received {(int)response.StatusCode}: {responseContent}");
var problem = await response.Content.ReadFromJsonAsync<ValidationProblemDetails>(TestContext.Current.CancellationToken);
var problem = await response.Content.ReadFromJsonAsync<ValidationProblemDetails>(CancellationToken.None);
Assert.NotNull(problem);
Assert.True(problem!.Errors.TryGetValue("message", out var messages));
Assert.Contains(messages, m => m.Contains("exceeds", StringComparison.OrdinalIgnoreCase));
@@ -272,9 +272,9 @@ public sealed class EvidenceLockerWebServiceTests
}
};
var response = await client.PostAsJsonAsync("/evidence/snapshot", payload, TestContext.Current.CancellationToken);
var response = await client.PostAsJsonAsync("/evidence/snapshot", payload, CancellationToken.None);
var responseContent = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
var responseContent = await response.Content.ReadAsStringAsync(CancellationToken.None);
Assert.True(response.StatusCode == HttpStatusCode.Forbidden, $"Expected 403 but received {(int)response.StatusCode}: {responseContent}");
}
@@ -296,11 +296,11 @@ public sealed class EvidenceLockerWebServiceTests
{
reason = "legal-hold"
},
TestContext.Current.CancellationToken);
CancellationToken.None);
var responseContent = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
var responseContent = await response.Content.ReadAsStringAsync(CancellationToken.None);
Assert.True(response.StatusCode == HttpStatusCode.BadRequest, $"Expected 400 but received {(int)response.StatusCode}: {responseContent}");
var problem = await response.Content.ReadFromJsonAsync<ValidationProblemDetails>(TestContext.Current.CancellationToken);
var problem = await response.Content.ReadFromJsonAsync<ValidationProblemDetails>(CancellationToken.None);
Assert.NotNull(problem);
Assert.True(problem!.Errors.TryGetValue("message", out var messages));
Assert.Contains(messages, m => m.IndexOf("already exists", StringComparison.OrdinalIgnoreCase) >= 0);
@@ -323,10 +323,10 @@ public sealed class EvidenceLockerWebServiceTests
reason = "retention",
notes = "retain for investigation"
},
TestContext.Current.CancellationToken);
CancellationToken.None);
response.EnsureSuccessStatusCode();
var hold = await response.Content.ReadFromJsonAsync<EvidenceHoldResponseDto>(TestContext.Current.CancellationToken);
var hold = await response.Content.ReadFromJsonAsync<EvidenceHoldResponseDto>(CancellationToken.None);
Assert.NotNull(hold);
Assert.Contains($"hold:{hold!.CaseId}", factory.TimelinePublisher.PublishedEvents);
}
@@ -347,7 +347,6 @@ 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;

View File

@@ -1,4 +1,4 @@
using System.Formats.Tar;
using System.Formats.Tar;
using System.IO.Compression;
using System.Text;
using System.Text.Json;
@@ -248,11 +248,12 @@ public sealed class EvidencePortableBundleServiceTests
continue;
}
var posixEntry = entry as PosixTarEntry;
entries[entry.Name] = new TarEntryMetadata(
entry.Uid,
entry.Gid,
entry.UserName ?? string.Empty,
entry.GroupName ?? string.Empty,
posixEntry?.UserName ?? string.Empty,
posixEntry?.GroupName ?? string.Empty,
entry.ModificationTime);
}
@@ -337,7 +338,6 @@ public sealed class EvidencePortableBundleServiceTests
{
Stored = true;
using var memory = new MemoryStream();
using StellaOps.TestKit;
content.CopyTo(memory);
StoredBytes = memory.ToArray();

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Security.Cryptography;
@@ -200,7 +200,6 @@ 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

View File

@@ -1,4 +1,4 @@
using System.Reflection;
using System.Reflection;
using System.Runtime.Serialization;
using System.Security.Cryptography;
using System.Linq;
@@ -477,7 +477,6 @@ public sealed class EvidenceSnapshotServiceTests
CancellationToken cancellationToken)
{
using var memory = new MemoryStream();
using StellaOps.TestKit;
content.CopyTo(memory);
var bytes = memory.ToArray();

View File

@@ -1,4 +1,4 @@
using System.Text;
using System.Text;
using Microsoft.Extensions.Logging.Abstractions;
using StellaOps.EvidenceLocker.Core.Configuration;
using StellaOps.EvidenceLocker.Core.Domain;
@@ -22,7 +22,7 @@ public sealed class FileSystemEvidenceObjectStoreTests : IDisposable
[Fact]
public async Task StoreAsync_EnforcesWriteOnceWhenConfigured()
{
var cancellationToken = TestContext.Current.CancellationToken;
var cancellationToken = CancellationToken.None;
var store = CreateStore(enforceWriteOnce: true);
var options = CreateWriteOptions();
@@ -37,7 +37,7 @@ public sealed class FileSystemEvidenceObjectStoreTests : IDisposable
[Fact]
public async Task StoreAsync_AllowsOverwriteWhenWriteOnceDisabled()
{
var cancellationToken = TestContext.Current.CancellationToken;
var cancellationToken = CancellationToken.None;
var store = CreateStore(enforceWriteOnce: false);
var options = CreateWriteOptions() with { EnforceWriteOnce = false };
@@ -45,7 +45,6 @@ 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);

View File

@@ -1,4 +1,4 @@
using System.Linq;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
@@ -77,7 +77,6 @@ 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();
}
}

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Amazon;
@@ -116,7 +116,6 @@ 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(

View File

@@ -1,22 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<UseXunitV3>true</UseXunitV3>
<OutputType>Exe</OutputType>
<IsPackable>false</IsPackable>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UseConcelierTestInfra>false</UseConcelierTestInfra>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DotNet.Testcontainers" Version="1.6.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="Npgsql" Version="9.0.3" />
<PackageReference Include="xunit.v3" Version="3.0.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.3" />
<PackageReference Include="DotNet.Testcontainers" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" />
<PackageReference Include="Npgsql" />
<PackageReference Include="xunit.v3" />
</ItemGroup>
<ItemGroup>
@@ -35,5 +33,6 @@
<ProjectReference Include="..\StellaOps.EvidenceLocker.Infrastructure\StellaOps.EvidenceLocker.Infrastructure.csproj" />
<ProjectReference Include="..\StellaOps.EvidenceLocker.WebService\StellaOps.EvidenceLocker.WebService.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.Cryptography/StellaOps.Cryptography.csproj" />
</ItemGroup>
</Project>
</Project>

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Text.Json;
@@ -125,7 +125,6 @@ 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());