Add unit tests for RabbitMq and Udp transport servers and clients
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
- Implemented comprehensive unit tests for RabbitMqTransportServer, covering constructor, disposal, connection management, event handlers, and exception handling. - Added configuration tests for RabbitMqTransportServer to validate SSL, durable queues, auto-recovery, and custom virtual host options. - Created unit tests for UdpFrameProtocol, including frame parsing and serialization, header size validation, and round-trip data preservation. - Developed tests for UdpTransportClient, focusing on connection handling, event subscriptions, and exception scenarios. - Established tests for UdpTransportServer, ensuring proper start/stop behavior, connection state management, and event handling. - Included tests for UdpTransportOptions to verify default values and modification capabilities. - Enhanced service registration tests for Udp transport services in the dependency injection container.
This commit is contained in:
@@ -69,8 +69,10 @@ public sealed record AuditEntry(
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new audit entry with computed hash.
|
||||
/// Uses the platform's compliance-aware crypto abstraction.
|
||||
/// </summary>
|
||||
public static AuditEntry Create(
|
||||
CanonicalJsonHasher hasher,
|
||||
string tenantId,
|
||||
AuditEventType eventType,
|
||||
string resourceType,
|
||||
@@ -89,12 +91,14 @@ public sealed record AuditEntry(
|
||||
long sequenceNumber = 0,
|
||||
string? metadata = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(hasher);
|
||||
|
||||
var entryId = Guid.NewGuid();
|
||||
var occurredAt = DateTimeOffset.UtcNow;
|
||||
|
||||
// Compute canonical hash from immutable content
|
||||
// Use the same property names and fields as VerifyIntegrity to keep the hash stable.
|
||||
var contentHash = CanonicalJsonHasher.ComputeCanonicalSha256(new
|
||||
var contentHash = hasher.ComputeCanonicalHash(new
|
||||
{
|
||||
EntryId = entryId,
|
||||
TenantId = tenantId,
|
||||
@@ -135,10 +139,13 @@ public sealed record AuditEntry(
|
||||
|
||||
/// <summary>
|
||||
/// Verifies the integrity of this entry's content hash.
|
||||
/// Uses the platform's compliance-aware crypto abstraction.
|
||||
/// </summary>
|
||||
public bool VerifyIntegrity()
|
||||
public bool VerifyIntegrity(CanonicalJsonHasher hasher)
|
||||
{
|
||||
var computed = CanonicalJsonHasher.ComputeCanonicalSha256(new
|
||||
ArgumentNullException.ThrowIfNull(hasher);
|
||||
|
||||
var computed = hasher.ComputeCanonicalHash(new
|
||||
{
|
||||
EntryId,
|
||||
TenantId,
|
||||
@@ -169,12 +176,6 @@ public sealed record AuditEntry(
|
||||
return string.Equals(PreviousEntryHash, previousEntry.ContentHash, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static string ComputeSha256(string content)
|
||||
{
|
||||
var bytes = System.Text.Encoding.UTF8.GetBytes(content);
|
||||
var hash = System.Security.Cryptography.SHA256.HashData(bytes);
|
||||
return Convert.ToHexString(hash).ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Orchestrator.Core.Hashing;
|
||||
|
||||
namespace StellaOps.Orchestrator.Core.Domain.Events;
|
||||
@@ -178,13 +178,18 @@ public sealed record EventEnvelope(
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Computes a digest of the envelope for signing.</summary>
|
||||
public string ComputeDigest()
|
||||
/// <summary>
|
||||
/// Computes a digest of the envelope for signing.
|
||||
/// Uses the platform's compliance-aware crypto abstraction.
|
||||
/// </summary>
|
||||
public string ComputeDigest(ICryptoHash cryptoHash)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(cryptoHash);
|
||||
|
||||
var canonicalJson = CanonicalJsonHasher.ToCanonicalJson(new { envelope = this });
|
||||
var bytes = Encoding.UTF8.GetBytes(canonicalJson);
|
||||
var hash = SHA256.HashData(bytes);
|
||||
return $"sha256:{Convert.ToHexStringLower(hash)}";
|
||||
var hash = cryptoHash.ComputePrefixedHashForPurpose(bytes, HashPurpose.Content);
|
||||
return hash;
|
||||
}
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Orchestrator.Core.Hashing;
|
||||
|
||||
namespace StellaOps.Orchestrator.Core.Domain;
|
||||
@@ -44,8 +44,10 @@ public sealed record PackRunLog(
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new log entry.
|
||||
/// Uses the platform's compliance-aware crypto abstraction.
|
||||
/// </summary>
|
||||
public static PackRunLog Create(
|
||||
ICryptoHash cryptoHash,
|
||||
Guid packRunId,
|
||||
string tenantId,
|
||||
long sequence,
|
||||
@@ -55,7 +57,9 @@ public sealed record PackRunLog(
|
||||
string? data = null,
|
||||
DateTimeOffset? timestamp = null)
|
||||
{
|
||||
var (digest, sizeBytes) = ComputeDigest(message, data, tenantId, packRunId, sequence, level, source);
|
||||
ArgumentNullException.ThrowIfNull(cryptoHash);
|
||||
|
||||
var (digest, sizeBytes) = ComputeDigest(cryptoHash, message, data, tenantId, packRunId, sequence, level, source);
|
||||
|
||||
return new PackRunLog(
|
||||
LogId: Guid.NewGuid(),
|
||||
@@ -75,32 +79,35 @@ public sealed record PackRunLog(
|
||||
/// Creates an info-level stdout log entry.
|
||||
/// </summary>
|
||||
public static PackRunLog Stdout(
|
||||
ICryptoHash cryptoHash,
|
||||
Guid packRunId,
|
||||
string tenantId,
|
||||
long sequence,
|
||||
string message,
|
||||
DateTimeOffset? timestamp = null)
|
||||
{
|
||||
return Create(packRunId, tenantId, sequence, LogLevel.Info, "stdout", message, null, timestamp);
|
||||
return Create(cryptoHash, packRunId, tenantId, sequence, LogLevel.Info, "stdout", message, null, timestamp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a warn-level stderr log entry.
|
||||
/// </summary>
|
||||
public static PackRunLog Stderr(
|
||||
ICryptoHash cryptoHash,
|
||||
Guid packRunId,
|
||||
string tenantId,
|
||||
long sequence,
|
||||
string message,
|
||||
DateTimeOffset? timestamp = null)
|
||||
{
|
||||
return Create(packRunId, tenantId, sequence, LogLevel.Warn, "stderr", message, null, timestamp);
|
||||
return Create(cryptoHash, packRunId, tenantId, sequence, LogLevel.Warn, "stderr", message, null, timestamp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a system-level log entry (lifecycle events).
|
||||
/// </summary>
|
||||
public static PackRunLog System(
|
||||
ICryptoHash cryptoHash,
|
||||
Guid packRunId,
|
||||
string tenantId,
|
||||
long sequence,
|
||||
@@ -109,10 +116,11 @@ public sealed record PackRunLog(
|
||||
string? data = null,
|
||||
DateTimeOffset? timestamp = null)
|
||||
{
|
||||
return Create(packRunId, tenantId, sequence, level, "system", message, data, timestamp);
|
||||
return Create(cryptoHash, packRunId, tenantId, sequence, level, "system", message, data, timestamp);
|
||||
}
|
||||
|
||||
private static (string Digest, long SizeBytes) ComputeDigest(
|
||||
ICryptoHash cryptoHash,
|
||||
string message,
|
||||
string? data,
|
||||
string tenantId,
|
||||
@@ -134,9 +142,9 @@ public sealed record PackRunLog(
|
||||
|
||||
var canonicalJson = CanonicalJsonHasher.ToCanonicalJson(payload);
|
||||
var bytes = Encoding.UTF8.GetBytes(canonicalJson);
|
||||
var hash = SHA256.HashData(bytes);
|
||||
var hash = cryptoHash.ComputeHashHexForPurpose(bytes, HashPurpose.Content);
|
||||
|
||||
return (Convert.ToHexString(hash).ToLowerInvariant(), bytes.LongLength);
|
||||
return (hash, bytes.LongLength);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,15 +18,17 @@ public sealed record ReplayInputsLock(
|
||||
|
||||
public static ReplayInputsLock Create(
|
||||
ReplayManifest manifest,
|
||||
CanonicalJsonHasher hasher,
|
||||
string? notes = null,
|
||||
DateTimeOffset? createdAt = null,
|
||||
string schemaVersion = DefaultSchemaVersion)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(manifest);
|
||||
ArgumentNullException.ThrowIfNull(hasher);
|
||||
|
||||
return new ReplayInputsLock(
|
||||
SchemaVersion: schemaVersion,
|
||||
ManifestHash: manifest.ComputeHash(),
|
||||
ManifestHash: manifest.ComputeHash(hasher),
|
||||
CreatedAt: createdAt ?? DateTimeOffset.UtcNow,
|
||||
Inputs: manifest.Inputs,
|
||||
Notes: string.IsNullOrWhiteSpace(notes) ? null : notes);
|
||||
@@ -34,6 +36,11 @@ public sealed record ReplayInputsLock(
|
||||
|
||||
/// <summary>
|
||||
/// Canonical hash of the lock content.
|
||||
/// Uses the platform's compliance-aware crypto abstraction.
|
||||
/// </summary>
|
||||
public string ComputeHash() => CanonicalJsonHasher.ComputeCanonicalSha256(this);
|
||||
public string ComputeHash(CanonicalJsonHasher hasher)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(hasher);
|
||||
return hasher.ComputeCanonicalHash(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,9 +41,14 @@ public sealed record ReplayManifest(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deterministic SHA-256 over canonical JSON representation of the manifest.
|
||||
/// Deterministic hash over canonical JSON representation of the manifest.
|
||||
/// Uses the platform's compliance-aware crypto abstraction.
|
||||
/// </summary>
|
||||
public string ComputeHash() => CanonicalJsonHasher.ComputeCanonicalSha256(this);
|
||||
public string ComputeHash(CanonicalJsonHasher hasher)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(hasher);
|
||||
return hasher.ComputeCanonicalHash(this);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record ReplayInputs(
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.Json.Serialization;
|
||||
using StellaOps.Cryptography;
|
||||
|
||||
namespace StellaOps.Orchestrator.Core.Hashing;
|
||||
|
||||
/// <summary>
|
||||
/// Produces deterministic, canonical JSON and hashes for orchestrator payloads (events, audit, manifests).
|
||||
/// Keys are sorted lexicographically; arrays preserve order; nulls are retained; timestamps remain ISO 8601 with offsets.
|
||||
/// Uses compliance-profile-aware hashing via <see cref="ICryptoHash"/>.
|
||||
/// </summary>
|
||||
public static class CanonicalJsonHasher
|
||||
public sealed class CanonicalJsonHasher
|
||||
{
|
||||
private readonly ICryptoHash _cryptoHash;
|
||||
|
||||
private static readonly JsonSerializerOptions SerializerOptions = new()
|
||||
{
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
|
||||
@@ -20,6 +23,15 @@ public static class CanonicalJsonHasher
|
||||
Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) }
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new CanonicalJsonHasher with the specified crypto hash service.
|
||||
/// </summary>
|
||||
/// <param name="cryptoHash">Crypto hash service for compliance-aware hashing.</param>
|
||||
public CanonicalJsonHasher(ICryptoHash cryptoHash)
|
||||
{
|
||||
_cryptoHash = cryptoHash ?? throw new ArgumentNullException(nameof(cryptoHash));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize the value to canonical JSON (sorted object keys, stable formatting).
|
||||
/// </summary>
|
||||
@@ -32,14 +44,14 @@ public static class CanonicalJsonHasher
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute SHA-256 over canonical JSON (lowercase hex).
|
||||
/// Compute hash over canonical JSON using the active compliance profile (lowercase hex).
|
||||
/// Uses <see cref="HashPurpose.Content"/> for content hashing.
|
||||
/// </summary>
|
||||
public static string ComputeCanonicalSha256<T>(T value)
|
||||
public string ComputeCanonicalHash<T>(T value)
|
||||
{
|
||||
var canonicalJson = ToCanonicalJson(value);
|
||||
var bytes = Encoding.UTF8.GetBytes(canonicalJson);
|
||||
var hash = SHA256.HashData(bytes);
|
||||
return Convert.ToHexString(hash).ToLowerInvariant();
|
||||
return _cryptoHash.ComputeHashHexForPurpose(bytes, HashPurpose.Content);
|
||||
}
|
||||
|
||||
private static JsonNode OrderNode(JsonNode node)
|
||||
|
||||
@@ -2,11 +2,21 @@ using StellaOps.Orchestrator.Core.Domain.Events;
|
||||
|
||||
namespace StellaOps.Orchestrator.Core.Hashing;
|
||||
|
||||
public static class EventEnvelopeHasher
|
||||
/// <summary>
|
||||
/// Computes compliance-aware hashes for event envelopes using the platform's crypto abstraction.
|
||||
/// </summary>
|
||||
public sealed class EventEnvelopeHasher
|
||||
{
|
||||
public static string Compute(EventEnvelope envelope)
|
||||
private readonly CanonicalJsonHasher _hasher;
|
||||
|
||||
public EventEnvelopeHasher(CanonicalJsonHasher hasher)
|
||||
{
|
||||
_hasher = hasher ?? throw new ArgumentNullException(nameof(hasher));
|
||||
}
|
||||
|
||||
public string Compute(EventEnvelope envelope)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(envelope);
|
||||
return CanonicalJsonHasher.ComputeCanonicalSha256(envelope);
|
||||
return _hasher.ComputeCanonicalHash(envelope);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,4 +17,8 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Cryptography\StellaOps.Cryptography.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Text;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Npgsql;
|
||||
using StellaOps.Orchestrator.Core.Domain;
|
||||
using StellaOps.Orchestrator.Core.Hashing;
|
||||
using StellaOps.Orchestrator.Infrastructure.Repositories;
|
||||
|
||||
namespace StellaOps.Orchestrator.Infrastructure.Postgres;
|
||||
@@ -61,13 +62,16 @@ public sealed class PostgresAuditRepository : IAuditRepository
|
||||
""";
|
||||
|
||||
private readonly OrchestratorDataSource _dataSource;
|
||||
private readonly CanonicalJsonHasher _hasher;
|
||||
private readonly ILogger<PostgresAuditRepository> _logger;
|
||||
|
||||
public PostgresAuditRepository(
|
||||
OrchestratorDataSource dataSource,
|
||||
CanonicalJsonHasher hasher,
|
||||
ILogger<PostgresAuditRepository> logger)
|
||||
{
|
||||
_dataSource = dataSource ?? throw new ArgumentNullException(nameof(dataSource));
|
||||
_hasher = hasher ?? throw new ArgumentNullException(nameof(hasher));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
@@ -115,6 +119,7 @@ public sealed class PostgresAuditRepository : IAuditRepository
|
||||
|
||||
// Create the entry
|
||||
var entry = AuditEntry.Create(
|
||||
hasher: _hasher,
|
||||
tenantId: tenantId,
|
||||
eventType: eventType,
|
||||
resourceType: resourceType,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Orchestrator.Core.Domain;
|
||||
using StellaOps.Orchestrator.Core.Hashing;
|
||||
|
||||
namespace StellaOps.Orchestrator.Tests.AuditLedger;
|
||||
|
||||
@@ -7,6 +9,13 @@ namespace StellaOps.Orchestrator.Tests.AuditLedger;
|
||||
/// </summary>
|
||||
public sealed class AuditEntryTests
|
||||
{
|
||||
private readonly ICryptoHash _cryptoHash = DefaultCryptoHash.CreateForTests();
|
||||
private readonly CanonicalJsonHasher _hasher;
|
||||
|
||||
public AuditEntryTests()
|
||||
{
|
||||
_hasher = new CanonicalJsonHasher(_cryptoHash);
|
||||
}
|
||||
[Fact]
|
||||
public void Create_WithValidParameters_SetsAllProperties()
|
||||
{
|
||||
@@ -16,6 +25,7 @@ public sealed class AuditEntryTests
|
||||
|
||||
// Act
|
||||
var entry = AuditEntry.Create(
|
||||
hasher: _hasher,
|
||||
tenantId: tenantId,
|
||||
eventType: AuditEventType.JobCreated,
|
||||
resourceType: "job",
|
||||
@@ -62,6 +72,7 @@ public sealed class AuditEntryTests
|
||||
{
|
||||
// Arrange & Act
|
||||
var entry = AuditEntry.Create(
|
||||
hasher: _hasher,
|
||||
tenantId: "test-tenant",
|
||||
eventType: AuditEventType.RunCreated,
|
||||
resourceType: "run",
|
||||
@@ -82,6 +93,7 @@ public sealed class AuditEntryTests
|
||||
{
|
||||
// Arrange
|
||||
var entry = AuditEntry.Create(
|
||||
hasher: _hasher,
|
||||
tenantId: "test-tenant",
|
||||
eventType: AuditEventType.SourceCreated,
|
||||
resourceType: "source",
|
||||
@@ -92,7 +104,7 @@ public sealed class AuditEntryTests
|
||||
sequenceNumber: 5);
|
||||
|
||||
// Act
|
||||
var isValid = entry.VerifyIntegrity();
|
||||
var isValid = entry.VerifyIntegrity(_hasher);
|
||||
|
||||
// Assert
|
||||
Assert.True(isValid);
|
||||
@@ -103,6 +115,7 @@ public sealed class AuditEntryTests
|
||||
{
|
||||
// Arrange
|
||||
var entry = AuditEntry.Create(
|
||||
hasher: _hasher,
|
||||
tenantId: "test-tenant",
|
||||
eventType: AuditEventType.QuotaCreated,
|
||||
resourceType: "quota",
|
||||
@@ -116,7 +129,7 @@ public sealed class AuditEntryTests
|
||||
var tamperedEntry = entry with { Description = "Tampered description" };
|
||||
|
||||
// Act
|
||||
var isValid = tamperedEntry.VerifyIntegrity();
|
||||
var isValid = tamperedEntry.VerifyIntegrity(_hasher);
|
||||
|
||||
// Assert
|
||||
Assert.False(isValid);
|
||||
@@ -127,6 +140,7 @@ public sealed class AuditEntryTests
|
||||
{
|
||||
// Arrange
|
||||
var entry = AuditEntry.Create(
|
||||
hasher: _hasher,
|
||||
tenantId: "test-tenant",
|
||||
eventType: AuditEventType.JobScheduled,
|
||||
resourceType: "job",
|
||||
@@ -149,6 +163,7 @@ public sealed class AuditEntryTests
|
||||
{
|
||||
// Arrange
|
||||
var first = AuditEntry.Create(
|
||||
hasher: _hasher,
|
||||
tenantId: "test-tenant",
|
||||
eventType: AuditEventType.JobCreated,
|
||||
resourceType: "job",
|
||||
@@ -160,6 +175,7 @@ public sealed class AuditEntryTests
|
||||
sequenceNumber: 1);
|
||||
|
||||
var second = AuditEntry.Create(
|
||||
hasher: _hasher,
|
||||
tenantId: "test-tenant",
|
||||
eventType: AuditEventType.JobLeased,
|
||||
resourceType: "job",
|
||||
@@ -182,6 +198,7 @@ public sealed class AuditEntryTests
|
||||
{
|
||||
// Arrange
|
||||
var first = AuditEntry.Create(
|
||||
hasher: _hasher,
|
||||
tenantId: "test-tenant",
|
||||
eventType: AuditEventType.JobCreated,
|
||||
resourceType: "job",
|
||||
@@ -193,6 +210,7 @@ public sealed class AuditEntryTests
|
||||
sequenceNumber: 1);
|
||||
|
||||
var second = AuditEntry.Create(
|
||||
hasher: _hasher,
|
||||
tenantId: "test-tenant",
|
||||
eventType: AuditEventType.JobCompleted,
|
||||
resourceType: "job",
|
||||
@@ -225,6 +243,7 @@ public sealed class AuditEntryTests
|
||||
{
|
||||
// Act
|
||||
var entry = AuditEntry.Create(
|
||||
hasher: _hasher,
|
||||
tenantId: "test-tenant",
|
||||
eventType: eventType,
|
||||
resourceType: resourceType,
|
||||
@@ -237,7 +256,7 @@ public sealed class AuditEntryTests
|
||||
// Assert
|
||||
Assert.Equal(eventType, entry.EventType);
|
||||
Assert.Equal(resourceType, entry.ResourceType);
|
||||
Assert.True(entry.VerifyIntegrity());
|
||||
Assert.True(entry.VerifyIntegrity(_hasher));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -251,6 +270,7 @@ public sealed class AuditEntryTests
|
||||
{
|
||||
// Act
|
||||
var entry = AuditEntry.Create(
|
||||
hasher: _hasher,
|
||||
tenantId: "test-tenant",
|
||||
eventType: AuditEventType.JobCreated,
|
||||
resourceType: "job",
|
||||
@@ -262,7 +282,7 @@ public sealed class AuditEntryTests
|
||||
|
||||
// Assert
|
||||
Assert.Equal(actorType, entry.ActorType);
|
||||
Assert.True(entry.VerifyIntegrity());
|
||||
Assert.True(entry.VerifyIntegrity(_hasher));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -274,6 +294,7 @@ public sealed class AuditEntryTests
|
||||
|
||||
// Act
|
||||
var entry = AuditEntry.Create(
|
||||
hasher: _hasher,
|
||||
tenantId: "test-tenant",
|
||||
eventType: AuditEventType.JobLeased,
|
||||
resourceType: "job",
|
||||
@@ -295,6 +316,7 @@ public sealed class AuditEntryTests
|
||||
{
|
||||
// Act
|
||||
var entry1 = AuditEntry.Create(
|
||||
hasher: _hasher,
|
||||
tenantId: "test-tenant",
|
||||
eventType: AuditEventType.JobCreated,
|
||||
resourceType: "job",
|
||||
@@ -305,6 +327,7 @@ public sealed class AuditEntryTests
|
||||
sequenceNumber: 1);
|
||||
|
||||
var entry2 = AuditEntry.Create(
|
||||
hasher: _hasher,
|
||||
tenantId: "test-tenant",
|
||||
eventType: AuditEventType.JobCreated,
|
||||
resourceType: "job",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Text.Json;
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Orchestrator.Core.Domain;
|
||||
using StellaOps.Orchestrator.Core.Hashing;
|
||||
|
||||
@@ -6,14 +7,22 @@ namespace StellaOps.Orchestrator.Tests;
|
||||
|
||||
public class CanonicalJsonHasherTests
|
||||
{
|
||||
private readonly ICryptoHash _cryptoHash = DefaultCryptoHash.CreateForTests();
|
||||
private readonly CanonicalJsonHasher _hasher;
|
||||
|
||||
public CanonicalJsonHasherTests()
|
||||
{
|
||||
_hasher = new CanonicalJsonHasher(_cryptoHash);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProducesStableHash_WhenObjectPropertyOrderDiffers()
|
||||
{
|
||||
var first = new { b = 1, a = 2 };
|
||||
var second = new { a = 2, b = 1 };
|
||||
|
||||
var firstHash = CanonicalJsonHasher.ComputeCanonicalSha256(first);
|
||||
var secondHash = CanonicalJsonHasher.ComputeCanonicalSha256(second);
|
||||
var firstHash = _hasher.ComputeCanonicalHash(first);
|
||||
var secondHash = _hasher.ComputeCanonicalHash(second);
|
||||
|
||||
Assert.Equal(firstHash, secondHash);
|
||||
}
|
||||
@@ -37,6 +46,7 @@ public class CanonicalJsonHasherTests
|
||||
public void AuditEntry_UsesCanonicalHash()
|
||||
{
|
||||
var entry = AuditEntry.Create(
|
||||
hasher: _hasher,
|
||||
tenantId: "tenant-1",
|
||||
eventType: AuditEventType.JobCreated,
|
||||
resourceType: "job",
|
||||
@@ -45,10 +55,10 @@ public class CanonicalJsonHasherTests
|
||||
actorType: ActorType.User,
|
||||
description: "created job");
|
||||
|
||||
Assert.True(entry.VerifyIntegrity());
|
||||
Assert.True(entry.VerifyIntegrity(_hasher));
|
||||
|
||||
// Changing description should invalidate hash
|
||||
var tampered = entry with { Description = "tampered" };
|
||||
Assert.False(tampered.VerifyIntegrity());
|
||||
Assert.False(tampered.VerifyIntegrity(_hasher));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Text.Json;
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Orchestrator.Core;
|
||||
using StellaOps.Orchestrator.Core.Hashing;
|
||||
|
||||
@@ -7,6 +8,15 @@ namespace StellaOps.Orchestrator.Tests;
|
||||
|
||||
public class EventEnvelopeTests
|
||||
{
|
||||
private readonly ICryptoHash _cryptoHash = DefaultCryptoHash.CreateForTests();
|
||||
private readonly CanonicalJsonHasher _hasher;
|
||||
private readonly EventEnvelopeHasher _envelopeHasher;
|
||||
|
||||
public EventEnvelopeTests()
|
||||
{
|
||||
_hasher = new CanonicalJsonHasher(_cryptoHash);
|
||||
_envelopeHasher = new EventEnvelopeHasher(_hasher);
|
||||
}
|
||||
[Fact]
|
||||
public void ComputeIdempotencyKey_IsDeterministicAndLowercase()
|
||||
{
|
||||
@@ -83,8 +93,8 @@ public class EventEnvelopeTests
|
||||
eventId: "evt-fixed",
|
||||
idempotencyKey: "fixed-key");
|
||||
|
||||
var hash1 = EventEnvelopeHasher.Compute(envelope);
|
||||
var hash2 = EventEnvelopeHasher.Compute(envelope);
|
||||
var hash1 = _envelopeHasher.Compute(envelope);
|
||||
var hash2 = _envelopeHasher.Compute(envelope);
|
||||
|
||||
Assert.Equal(hash1, hash2);
|
||||
Assert.Equal(64, hash1.Length);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Orchestrator.Core.Domain.Events;
|
||||
using StellaOps.Orchestrator.Infrastructure.Events;
|
||||
|
||||
@@ -11,6 +12,7 @@ namespace StellaOps.Orchestrator.Tests.Events;
|
||||
public class EventPublishingTests
|
||||
{
|
||||
private static readonly CancellationToken CT = CancellationToken.None;
|
||||
private readonly ICryptoHash _cryptoHash = DefaultCryptoHash.CreateForTests();
|
||||
|
||||
#region EventEnvelope Tests
|
||||
|
||||
@@ -144,7 +146,7 @@ public class EventPublishingTests
|
||||
tenantId: "tenant-1",
|
||||
actor: actor);
|
||||
|
||||
var digest = envelope.ComputeDigest();
|
||||
var digest = envelope.ComputeDigest(_cryptoHash);
|
||||
|
||||
Assert.StartsWith("sha256:", digest);
|
||||
Assert.Equal(64 + 7, digest.Length); // "sha256:" + 64 hex chars
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Orchestrator.Core.Domain;
|
||||
using StellaOps.Orchestrator.WebService.Contracts;
|
||||
|
||||
@@ -5,6 +6,7 @@ namespace StellaOps.Orchestrator.Tests.PackRun;
|
||||
|
||||
public sealed class PackRunContractTests
|
||||
{
|
||||
private readonly ICryptoHash _cryptoHash = DefaultCryptoHash.CreateForTests();
|
||||
[Fact]
|
||||
public void PackRunResponse_FromDomain_MapsAllFields()
|
||||
{
|
||||
@@ -88,6 +90,7 @@ public sealed class PackRunContractTests
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
|
||||
var log = PackRunLog.Create(
|
||||
cryptoHash: _cryptoHash,
|
||||
packRunId: packRunId,
|
||||
tenantId: "tenant-1",
|
||||
sequence: 42,
|
||||
@@ -121,6 +124,7 @@ public sealed class PackRunContractTests
|
||||
public void LogEntryResponse_FromDomain_LevelIsLowercase(LogLevel level, string expectedLevelString)
|
||||
{
|
||||
var log = PackRunLog.Create(
|
||||
cryptoHash: _cryptoHash,
|
||||
packRunId: Guid.NewGuid(),
|
||||
tenantId: "t1",
|
||||
sequence: 0,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Orchestrator.Core.Domain;
|
||||
|
||||
namespace StellaOps.Orchestrator.Tests.PackRun;
|
||||
@@ -6,6 +7,7 @@ public sealed class PackRunLogTests
|
||||
{
|
||||
private const string TestTenantId = "tenant-test";
|
||||
private readonly Guid _packRunId = Guid.NewGuid();
|
||||
private readonly ICryptoHash _cryptoHash = DefaultCryptoHash.CreateForTests();
|
||||
|
||||
[Fact]
|
||||
public void Create_InitializesAllFields()
|
||||
@@ -13,6 +15,7 @@ public sealed class PackRunLogTests
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
|
||||
var log = PackRunLog.Create(
|
||||
cryptoHash: _cryptoHash,
|
||||
packRunId: _packRunId,
|
||||
tenantId: TestTenantId,
|
||||
sequence: 5,
|
||||
@@ -41,6 +44,7 @@ public sealed class PackRunLogTests
|
||||
var beforeCreate = DateTimeOffset.UtcNow;
|
||||
|
||||
var log = PackRunLog.Create(
|
||||
cryptoHash: _cryptoHash,
|
||||
packRunId: _packRunId,
|
||||
tenantId: TestTenantId,
|
||||
sequence: 0,
|
||||
@@ -59,7 +63,7 @@ public sealed class PackRunLogTests
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
|
||||
var log = PackRunLog.Stdout(_packRunId, TestTenantId, 10, "Hello stdout", now);
|
||||
var log = PackRunLog.Stdout(_cryptoHash, _packRunId, TestTenantId, 10, "Hello stdout", now);
|
||||
|
||||
Assert.Equal(LogLevel.Info, log.Level);
|
||||
Assert.Equal("stdout", log.Source);
|
||||
@@ -73,7 +77,7 @@ public sealed class PackRunLogTests
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
|
||||
var log = PackRunLog.Stderr(_packRunId, TestTenantId, 20, "Warning message", now);
|
||||
var log = PackRunLog.Stderr(_cryptoHash, _packRunId, TestTenantId, 20, "Warning message", now);
|
||||
|
||||
Assert.Equal(LogLevel.Warn, log.Level);
|
||||
Assert.Equal("stderr", log.Source);
|
||||
@@ -86,7 +90,7 @@ public sealed class PackRunLogTests
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
|
||||
var log = PackRunLog.System(_packRunId, TestTenantId, 30, LogLevel.Error, "System error", "{\"code\":500}", now);
|
||||
var log = PackRunLog.System(_cryptoHash, _packRunId, TestTenantId, 30, LogLevel.Error, "System error", "{\"code\":500}", now);
|
||||
|
||||
Assert.Equal(LogLevel.Error, log.Level);
|
||||
Assert.Equal("system", log.Source);
|
||||
@@ -112,6 +116,7 @@ public sealed class PackRunLogBatchTests
|
||||
{
|
||||
private const string TestTenantId = "tenant-test";
|
||||
private readonly Guid _packRunId = Guid.NewGuid();
|
||||
private readonly ICryptoHash _cryptoHash = DefaultCryptoHash.CreateForTests();
|
||||
|
||||
[Fact]
|
||||
public void FromLogs_EmptyList_ReturnsEmptyBatch()
|
||||
@@ -130,9 +135,9 @@ public sealed class PackRunLogBatchTests
|
||||
{
|
||||
var logs = new List<PackRunLog>
|
||||
{
|
||||
PackRunLog.Create(_packRunId, TestTenantId, 5, LogLevel.Info, "src", "msg1"),
|
||||
PackRunLog.Create(_packRunId, TestTenantId, 6, LogLevel.Info, "src", "msg2"),
|
||||
PackRunLog.Create(_packRunId, TestTenantId, 7, LogLevel.Info, "src", "msg3")
|
||||
PackRunLog.Create(_cryptoHash, _packRunId, TestTenantId, 5, LogLevel.Info, "src", "msg1"),
|
||||
PackRunLog.Create(_cryptoHash, _packRunId, TestTenantId, 6, LogLevel.Info, "src", "msg2"),
|
||||
PackRunLog.Create(_cryptoHash, _packRunId, TestTenantId, 7, LogLevel.Info, "src", "msg3")
|
||||
};
|
||||
|
||||
var batch = PackRunLogBatch.FromLogs(_packRunId, TestTenantId, logs);
|
||||
@@ -151,8 +156,8 @@ public sealed class PackRunLogBatchTests
|
||||
StartSequence: 100,
|
||||
Logs:
|
||||
[
|
||||
PackRunLog.Create(_packRunId, TestTenantId, 100, LogLevel.Info, "src", "msg1"),
|
||||
PackRunLog.Create(_packRunId, TestTenantId, 101, LogLevel.Info, "src", "msg2")
|
||||
PackRunLog.Create(_cryptoHash, _packRunId, TestTenantId, 100, LogLevel.Info, "src", "msg1"),
|
||||
PackRunLog.Create(_cryptoHash, _packRunId, TestTenantId, 101, LogLevel.Info, "src", "msg2")
|
||||
]);
|
||||
|
||||
Assert.Equal(102, batch.NextSequence);
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
using System.Collections.Immutable;
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Orchestrator.Core.Domain.Replay;
|
||||
using StellaOps.Orchestrator.Core.Hashing;
|
||||
|
||||
namespace StellaOps.Orchestrator.Tests;
|
||||
|
||||
public class ReplayInputsLockTests
|
||||
{
|
||||
private readonly ICryptoHash _cryptoHash = DefaultCryptoHash.CreateForTests();
|
||||
private readonly CanonicalJsonHasher _hasher;
|
||||
|
||||
public ReplayInputsLockTests()
|
||||
{
|
||||
_hasher = new CanonicalJsonHasher(_cryptoHash);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReplayInputsLock_ComputesStableHash()
|
||||
{
|
||||
@@ -22,10 +32,10 @@ public class ReplayInputsLockTests
|
||||
artifacts: null,
|
||||
createdAt: new DateTimeOffset(2025, 01, 01, 0, 0, 0, TimeSpan.Zero));
|
||||
|
||||
var lock1 = ReplayInputsLock.Create(manifest, createdAt: new DateTimeOffset(2025, 01, 01, 0, 0, 5, TimeSpan.Zero));
|
||||
var lock2 = ReplayInputsLock.Create(manifest, createdAt: new DateTimeOffset(2025, 01, 01, 0, 0, 5, TimeSpan.Zero));
|
||||
var lock1 = ReplayInputsLock.Create(manifest, _hasher, createdAt: new DateTimeOffset(2025, 01, 01, 0, 0, 5, TimeSpan.Zero));
|
||||
var lock2 = ReplayInputsLock.Create(manifest, _hasher, createdAt: new DateTimeOffset(2025, 01, 01, 0, 0, 5, TimeSpan.Zero));
|
||||
|
||||
Assert.Equal(lock1.ComputeHash(), lock2.ComputeHash());
|
||||
Assert.Equal(lock1.ComputeHash(_hasher), lock2.ComputeHash(_hasher));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -43,8 +53,8 @@ public class ReplayInputsLockTests
|
||||
TimeSource: ReplayTimeSource.wall,
|
||||
Env: ImmutableDictionary<string, string>.Empty));
|
||||
|
||||
var inputsLock = ReplayInputsLock.Create(manifest);
|
||||
var inputsLock = ReplayInputsLock.Create(manifest, _hasher);
|
||||
|
||||
Assert.Equal(manifest.ComputeHash(), inputsLock.ManifestHash);
|
||||
Assert.Equal(manifest.ComputeHash(_hasher), inputsLock.ManifestHash);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
using System.Collections.Immutable;
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Orchestrator.Core.Domain.Replay;
|
||||
using StellaOps.Orchestrator.Core.Hashing;
|
||||
|
||||
namespace StellaOps.Orchestrator.Tests;
|
||||
|
||||
public class ReplayManifestTests
|
||||
{
|
||||
private readonly ICryptoHash _cryptoHash = DefaultCryptoHash.CreateForTests();
|
||||
private readonly CanonicalJsonHasher _hasher;
|
||||
|
||||
public ReplayManifestTests()
|
||||
{
|
||||
_hasher = new CanonicalJsonHasher(_cryptoHash);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeHash_IsStableWithCanonicalOrdering()
|
||||
{
|
||||
@@ -31,8 +41,8 @@ public class ReplayManifestTests
|
||||
artifacts: new[] { new ReplayArtifact("ledger.ndjson", "sha256:abc", "application/x-ndjson") },
|
||||
createdAt: new DateTimeOffset(2025, 12, 1, 0, 0, 0, TimeSpan.Zero));
|
||||
|
||||
var hashA = manifestA.ComputeHash();
|
||||
var hashB = manifestB.ComputeHash();
|
||||
var hashA = manifestA.ComputeHash(_hasher);
|
||||
var hashB = manifestB.ComputeHash(_hasher);
|
||||
|
||||
Assert.Equal(hashA, hashB);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Text.Json;
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Orchestrator.Core;
|
||||
using StellaOps.Orchestrator.Core.Hashing;
|
||||
|
||||
@@ -7,6 +8,14 @@ namespace StellaOps.Orchestrator.Tests;
|
||||
|
||||
public class SchemaSmokeTests
|
||||
{
|
||||
private readonly ICryptoHash _cryptoHash = DefaultCryptoHash.CreateForTests();
|
||||
private readonly CanonicalJsonHasher _hasher;
|
||||
|
||||
public SchemaSmokeTests()
|
||||
{
|
||||
_hasher = new CanonicalJsonHasher(_cryptoHash);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("event-envelope.schema.json")]
|
||||
[InlineData("audit-bundle.schema.json")]
|
||||
@@ -47,8 +56,8 @@ public class SchemaSmokeTests
|
||||
eventId: "evt-1",
|
||||
idempotencyKey: "fixed");
|
||||
|
||||
var hash1 = CanonicalJsonHasher.ComputeCanonicalSha256(envelope);
|
||||
var hash2 = CanonicalJsonHasher.ComputeCanonicalSha256(envelope);
|
||||
var hash1 = _hasher.ComputeCanonicalHash(envelope);
|
||||
var hash2 = _hasher.ComputeCanonicalHash(envelope);
|
||||
|
||||
Assert.Equal(hash1, hash2);
|
||||
Assert.Equal(64, hash1.Length);
|
||||
|
||||
@@ -53,27 +53,27 @@
|
||||
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1"/>
|
||||
|
||||
|
||||
|
||||
|
||||
<PackageReference Include="xunit.v3" Version="3.0.0"/>
|
||||
|
||||
|
||||
|
||||
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.3"/>
|
||||
|
||||
|
||||
|
||||
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1"/>
|
||||
|
||||
|
||||
|
||||
|
||||
<PackageReference Include="xunit.v3" Version="3.0.0"/>
|
||||
|
||||
|
||||
|
||||
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.3"/>
|
||||
|
||||
|
||||
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
||||
@@ -117,12 +117,14 @@
|
||||
|
||||
|
||||
<ProjectReference Include="..\StellaOps.Orchestrator.Core\StellaOps.Orchestrator.Core.csproj"/>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<ProjectReference Include="..\StellaOps.Orchestrator.Infrastructure\StellaOps.Orchestrator.Infrastructure.csproj"/>
|
||||
|
||||
<ProjectReference Include="..\..\..\..\__Libraries\StellaOps.Cryptography\StellaOps.Cryptography.csproj"/>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using System.Globalization;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Orchestrator.Core.Domain;
|
||||
using StellaOps.Orchestrator.Core.Domain.Events;
|
||||
using StellaOps.Orchestrator.Infrastructure;
|
||||
@@ -102,6 +102,7 @@ public static class PackRunEndpoints
|
||||
[FromServices] IPackRunRepository packRunRepository,
|
||||
[FromServices] IQuotaRepository quotaRepository,
|
||||
[FromServices] IEventPublisher eventPublisher,
|
||||
[FromServices] ICryptoHash cryptoHash,
|
||||
[FromServices] TimeProvider timeProvider,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -127,7 +128,7 @@ public static class PackRunEndpoints
|
||||
var tenantId = tenantResolver.Resolve(context);
|
||||
var now = timeProvider.GetUtcNow();
|
||||
var parameters = request.Parameters ?? "{}";
|
||||
var parametersDigest = ComputeDigest(parameters);
|
||||
var parametersDigest = ComputeDigest(cryptoHash, parameters);
|
||||
var idempotencyKey = request.IdempotencyKey ?? $"pack-run:{request.PackId}:{parametersDigest}:{now:yyyyMMddHHmm}";
|
||||
|
||||
// Check for existing pack run with same idempotency key
|
||||
@@ -429,6 +430,7 @@ public static class PackRunEndpoints
|
||||
[FromServices] IPackRunRepository packRunRepository,
|
||||
[FromServices] IPackRunLogRepository logRepository,
|
||||
[FromServices] IEventPublisher eventPublisher,
|
||||
[FromServices] ICryptoHash cryptoHash,
|
||||
[FromServices] TimeProvider timeProvider,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -471,7 +473,7 @@ public static class PackRunEndpoints
|
||||
cancellationToken);
|
||||
|
||||
// Append system log entry
|
||||
var log = PackRunLog.System(packRunId, tenantId, 0, PackLogLevel.Info, "Pack run started", null, now);
|
||||
var log = PackRunLog.System(cryptoHash, packRunId, tenantId, 0, PackLogLevel.Info, "Pack run started", null, now);
|
||||
await logRepository.AppendAsync(log, cancellationToken);
|
||||
|
||||
OrchestratorMetrics.PackRunStarted(tenantId, packRun.PackId);
|
||||
@@ -499,6 +501,7 @@ public static class PackRunEndpoints
|
||||
[FromServices] IQuotaRepository quotaRepository,
|
||||
[FromServices] IArtifactRepository artifactRepository,
|
||||
[FromServices] IEventPublisher eventPublisher,
|
||||
[FromServices] ICryptoHash cryptoHash,
|
||||
[FromServices] TimeProvider timeProvider,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -587,7 +590,7 @@ public static class PackRunEndpoints
|
||||
// Append system log entry
|
||||
var (logCount, latestSeq) = await logRepository.GetLogStatsAsync(tenantId, packRunId, cancellationToken);
|
||||
var completionLog = PackRunLog.System(
|
||||
packRunId, tenantId, latestSeq + 1,
|
||||
cryptoHash, packRunId, tenantId, latestSeq + 1,
|
||||
request.Success ? PackLogLevel.Info : PackLogLevel.Error,
|
||||
$"Pack run {(request.Success ? "succeeded" : "failed")} with exit code {request.ExitCode}",
|
||||
null, now);
|
||||
@@ -649,6 +652,7 @@ public static class PackRunEndpoints
|
||||
[FromServices] IPackRunRepository packRunRepository,
|
||||
[FromServices] IPackRunLogRepository logRepository,
|
||||
[FromServices] IEventPublisher eventPublisher,
|
||||
[FromServices] ICryptoHash cryptoHash,
|
||||
[FromServices] TimeProvider timeProvider,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -687,7 +691,7 @@ public static class PackRunEndpoints
|
||||
: PackLogLevel.Info;
|
||||
|
||||
logs.Add(PackRunLog.Create(
|
||||
packRunId, tenantId, seq, level,
|
||||
cryptoHash, packRunId, tenantId, seq, level,
|
||||
entry.Source,
|
||||
entry.Message,
|
||||
entry.Data,
|
||||
@@ -773,6 +777,7 @@ public static class PackRunEndpoints
|
||||
[FromServices] IPackRunLogRepository logRepository,
|
||||
[FromServices] IQuotaRepository quotaRepository,
|
||||
[FromServices] IEventPublisher eventPublisher,
|
||||
[FromServices] ICryptoHash cryptoHash,
|
||||
[FromServices] TimeProvider timeProvider,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -811,7 +816,7 @@ public static class PackRunEndpoints
|
||||
// Append system log entry
|
||||
var (_, latestSeq) = await logRepository.GetLogStatsAsync(tenantId, packRunId, cancellationToken);
|
||||
var cancelLog = PackRunLog.System(
|
||||
packRunId, tenantId, latestSeq + 1,
|
||||
cryptoHash, packRunId, tenantId, latestSeq + 1,
|
||||
PackLogLevel.Warn, $"Pack run canceled: {request.Reason}", null, now);
|
||||
await logRepository.AppendAsync(cancelLog, cancellationToken);
|
||||
|
||||
@@ -839,6 +844,7 @@ public static class PackRunEndpoints
|
||||
[FromServices] TenantResolver tenantResolver,
|
||||
[FromServices] IPackRunRepository packRunRepository,
|
||||
[FromServices] IEventPublisher eventPublisher,
|
||||
[FromServices] ICryptoHash cryptoHash,
|
||||
[FromServices] TimeProvider timeProvider,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -868,7 +874,7 @@ public static class PackRunEndpoints
|
||||
var now = timeProvider.GetUtcNow();
|
||||
var newPackRunId = Guid.NewGuid();
|
||||
var parameters = request.Parameters ?? packRun.Parameters;
|
||||
var parametersDigest = request.Parameters != null ? ComputeDigest(parameters) : packRun.ParametersDigest;
|
||||
var parametersDigest = request.Parameters != null ? ComputeDigest(cryptoHash, parameters) : packRun.ParametersDigest;
|
||||
var idempotencyKey = request.IdempotencyKey ?? $"retry:{packRunId}:{now:yyyyMMddHHmmss}";
|
||||
|
||||
var newPackRun = PackRun.Create(
|
||||
@@ -1024,11 +1030,10 @@ public static class PackRunEndpoints
|
||||
return quota;
|
||||
}
|
||||
|
||||
private static string ComputeDigest(string content)
|
||||
private static string ComputeDigest(ICryptoHash cryptoHash, string content)
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes(content);
|
||||
var hash = SHA256.HashData(bytes);
|
||||
return Convert.ToHexStringLower(hash);
|
||||
return cryptoHash.ComputeHashHexForPurpose(bytes, HashPurpose.Content);
|
||||
}
|
||||
|
||||
private static JsonElement? ToPayload<T>(T value)
|
||||
|
||||
Reference in New Issue
Block a user