sln build fix (again), tests fixes, audit work and doctors work
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# StellaOps.Orchestrator.Core Task Board
|
||||
|
||||
This board mirrors active sprint tasks for this module.
|
||||
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
|
||||
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
|
||||
|
||||
| Task ID | Status | Notes |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# StellaOps.Orchestrator.Infrastructure Task Board
|
||||
|
||||
This board mirrors active sprint tasks for this module.
|
||||
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
|
||||
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
|
||||
|
||||
| Task ID | Status | Notes |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -33,7 +33,7 @@ public sealed class AuditEntryTests
|
||||
actorId: "user@example.com",
|
||||
actorType: ActorType.User,
|
||||
description: "Job created",
|
||||
oldState: null,
|
||||
occurredAt: DateTimeOffset.UtcNow,oldState: null,
|
||||
newState: """{"status":"pending"}""",
|
||||
actorIp: "192.168.1.1",
|
||||
userAgent: "TestClient/1.0",
|
||||
@@ -80,6 +80,7 @@ public sealed class AuditEntryTests
|
||||
actorId: "system",
|
||||
actorType: ActorType.System,
|
||||
description: "Run created",
|
||||
occurredAt: DateTimeOffset.UtcNow,
|
||||
sequenceNumber: 1);
|
||||
|
||||
// Assert
|
||||
@@ -101,6 +102,7 @@ public sealed class AuditEntryTests
|
||||
actorId: "admin",
|
||||
actorType: ActorType.User,
|
||||
description: "Source created",
|
||||
occurredAt: DateTimeOffset.UtcNow,
|
||||
sequenceNumber: 5);
|
||||
|
||||
// Act
|
||||
@@ -123,6 +125,7 @@ public sealed class AuditEntryTests
|
||||
actorId: "admin",
|
||||
actorType: ActorType.User,
|
||||
description: "Original description",
|
||||
occurredAt: DateTimeOffset.UtcNow,
|
||||
sequenceNumber: 1);
|
||||
|
||||
// Tamper with the entry by changing description but keeping original hash
|
||||
@@ -148,6 +151,7 @@ public sealed class AuditEntryTests
|
||||
actorId: "scheduler",
|
||||
actorType: ActorType.System,
|
||||
description: "Job scheduled",
|
||||
occurredAt: DateTimeOffset.UtcNow,
|
||||
previousEntryHash: null,
|
||||
sequenceNumber: 1);
|
||||
|
||||
@@ -171,6 +175,7 @@ public sealed class AuditEntryTests
|
||||
actorId: "user",
|
||||
actorType: ActorType.User,
|
||||
description: "First entry",
|
||||
occurredAt: DateTimeOffset.UtcNow,
|
||||
previousEntryHash: null,
|
||||
sequenceNumber: 1);
|
||||
|
||||
@@ -183,6 +188,7 @@ public sealed class AuditEntryTests
|
||||
actorId: "worker",
|
||||
actorType: ActorType.Worker,
|
||||
description: "Second entry",
|
||||
occurredAt: DateTimeOffset.UtcNow,
|
||||
previousEntryHash: first.ContentHash,
|
||||
sequenceNumber: 2);
|
||||
|
||||
@@ -206,6 +212,7 @@ public sealed class AuditEntryTests
|
||||
actorId: "user",
|
||||
actorType: ActorType.User,
|
||||
description: "First entry",
|
||||
occurredAt: DateTimeOffset.UtcNow,
|
||||
previousEntryHash: null,
|
||||
sequenceNumber: 1);
|
||||
|
||||
@@ -218,6 +225,7 @@ public sealed class AuditEntryTests
|
||||
actorId: "worker",
|
||||
actorType: ActorType.Worker,
|
||||
description: "Second entry with wrong hash",
|
||||
occurredAt: DateTimeOffset.UtcNow,
|
||||
previousEntryHash: "wrong_hash_value",
|
||||
sequenceNumber: 2);
|
||||
|
||||
@@ -251,6 +259,7 @@ public sealed class AuditEntryTests
|
||||
actorId: "test-actor",
|
||||
actorType: ActorType.System,
|
||||
description: $"Testing {eventType}",
|
||||
occurredAt: DateTimeOffset.UtcNow,
|
||||
sequenceNumber: 1);
|
||||
|
||||
// Assert
|
||||
@@ -278,6 +287,7 @@ public sealed class AuditEntryTests
|
||||
actorId: "test-actor",
|
||||
actorType: actorType,
|
||||
description: $"Testing actor type {actorType}",
|
||||
occurredAt: DateTimeOffset.UtcNow,
|
||||
sequenceNumber: 1);
|
||||
|
||||
// Assert
|
||||
@@ -302,6 +312,7 @@ public sealed class AuditEntryTests
|
||||
actorId: "worker-1",
|
||||
actorType: ActorType.Worker,
|
||||
description: "Job leased",
|
||||
occurredAt: DateTimeOffset.UtcNow,
|
||||
oldState: oldState,
|
||||
newState: newState,
|
||||
sequenceNumber: 1);
|
||||
@@ -324,6 +335,7 @@ public sealed class AuditEntryTests
|
||||
actorId: "user1",
|
||||
actorType: ActorType.User,
|
||||
description: "First job",
|
||||
occurredAt: DateTimeOffset.UtcNow,
|
||||
sequenceNumber: 1);
|
||||
|
||||
var entry2 = AuditEntry.Create(
|
||||
@@ -335,6 +347,7 @@ public sealed class AuditEntryTests
|
||||
actorId: "user2",
|
||||
actorType: ActorType.User,
|
||||
description: "Second job",
|
||||
occurredAt: DateTimeOffset.UtcNow,
|
||||
sequenceNumber: 2);
|
||||
|
||||
// Assert
|
||||
@@ -342,3 +355,6 @@ public sealed class AuditEntryTests
|
||||
Assert.NotEqual(entry1.EntryId, entry2.EntryId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -11,8 +11,7 @@ public sealed class LedgerExportTests
|
||||
public void CreateRequest_WithValidParameters_CreatesExport()
|
||||
{
|
||||
// Act
|
||||
var export = LedgerExport.CreateRequest(
|
||||
tenantId: "test-tenant",
|
||||
var export = LedgerExport.CreateRequest(requestedAt: DateTimeOffset.UtcNow, tenantId: "test-tenant",
|
||||
format: "json",
|
||||
requestedBy: "user@example.com",
|
||||
startTime: DateTimeOffset.UtcNow.AddDays(-7),
|
||||
@@ -46,8 +45,7 @@ public sealed class LedgerExportTests
|
||||
public void CreateRequest_WithValidFormats_NormalizesToLowerCase(string format)
|
||||
{
|
||||
// Act
|
||||
var export = LedgerExport.CreateRequest(
|
||||
tenantId: "test-tenant",
|
||||
var export = LedgerExport.CreateRequest(requestedAt: DateTimeOffset.UtcNow, tenantId: "test-tenant",
|
||||
format: format,
|
||||
requestedBy: "user");
|
||||
|
||||
@@ -64,8 +62,7 @@ public sealed class LedgerExportTests
|
||||
{
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
LedgerExport.CreateRequest(
|
||||
tenantId: "test-tenant",
|
||||
LedgerExport.CreateRequest(requestedAt: DateTimeOffset.UtcNow, tenantId: "test-tenant",
|
||||
format: format,
|
||||
requestedBy: "user"));
|
||||
}
|
||||
@@ -75,8 +72,7 @@ public sealed class LedgerExportTests
|
||||
{
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
LedgerExport.CreateRequest(
|
||||
tenantId: "test-tenant",
|
||||
LedgerExport.CreateRequest(requestedAt: DateTimeOffset.UtcNow, tenantId: "test-tenant",
|
||||
format: null!,
|
||||
requestedBy: "user"));
|
||||
}
|
||||
@@ -86,8 +82,7 @@ public sealed class LedgerExportTests
|
||||
{
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
LedgerExport.CreateRequest(
|
||||
tenantId: "test-tenant",
|
||||
LedgerExport.CreateRequest(requestedAt: DateTimeOffset.UtcNow, tenantId: "test-tenant",
|
||||
format: "",
|
||||
requestedBy: "user"));
|
||||
}
|
||||
@@ -96,13 +91,12 @@ public sealed class LedgerExportTests
|
||||
public void Start_SetsStatusAndStartedAt()
|
||||
{
|
||||
// Arrange
|
||||
var export = LedgerExport.CreateRequest(
|
||||
tenantId: "test-tenant",
|
||||
var export = LedgerExport.CreateRequest(requestedAt: DateTimeOffset.UtcNow, tenantId: "test-tenant",
|
||||
format: "json",
|
||||
requestedBy: "user");
|
||||
|
||||
// Act
|
||||
var started = export.Start();
|
||||
var started = export.Start(DateTimeOffset.UtcNow);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(LedgerExportStatus.Processing, started.Status);
|
||||
@@ -114,17 +108,17 @@ public sealed class LedgerExportTests
|
||||
public void Complete_SetsAllProperties()
|
||||
{
|
||||
// Arrange
|
||||
var export = LedgerExport.CreateRequest(
|
||||
tenantId: "test-tenant",
|
||||
var export = LedgerExport.CreateRequest(requestedAt: DateTimeOffset.UtcNow, tenantId: "test-tenant",
|
||||
format: "json",
|
||||
requestedBy: "user").Start();
|
||||
requestedBy: "user").Start(DateTimeOffset.UtcNow);
|
||||
|
||||
// Act
|
||||
var completed = export.Complete(
|
||||
outputUri: "file:///exports/test.json",
|
||||
outputDigest: "sha256:abc123",
|
||||
outputSizeBytes: 1024,
|
||||
entryCount: 100);
|
||||
entryCount: 100,
|
||||
completedAt: DateTimeOffset.UtcNow);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(LedgerExportStatus.Completed, completed.Status);
|
||||
@@ -140,13 +134,12 @@ public sealed class LedgerExportTests
|
||||
public void Fail_SetsStatusAndErrorMessage()
|
||||
{
|
||||
// Arrange
|
||||
var export = LedgerExport.CreateRequest(
|
||||
tenantId: "test-tenant",
|
||||
var export = LedgerExport.CreateRequest(requestedAt: DateTimeOffset.UtcNow, tenantId: "test-tenant",
|
||||
format: "json",
|
||||
requestedBy: "user").Start();
|
||||
requestedBy: "user").Start(DateTimeOffset.UtcNow);
|
||||
|
||||
// Act
|
||||
var failed = export.Fail("Database connection failed");
|
||||
var failed = export.Fail("Database connection failed", DateTimeOffset.UtcNow);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(LedgerExportStatus.Failed, failed.Status);
|
||||
@@ -159,8 +152,7 @@ public sealed class LedgerExportTests
|
||||
public void CreateRequest_WithMinimalParameters_CreatesExport()
|
||||
{
|
||||
// Act
|
||||
var export = LedgerExport.CreateRequest(
|
||||
tenantId: "test-tenant",
|
||||
var export = LedgerExport.CreateRequest(requestedAt: DateTimeOffset.UtcNow, tenantId: "test-tenant",
|
||||
format: "ndjson",
|
||||
requestedBy: "system");
|
||||
|
||||
@@ -176,19 +168,18 @@ public sealed class LedgerExportTests
|
||||
public void ExportLifecycle_FullFlow_TracksAllStates()
|
||||
{
|
||||
// Create
|
||||
var export = LedgerExport.CreateRequest(
|
||||
tenantId: "test-tenant",
|
||||
var export = LedgerExport.CreateRequest(requestedAt: DateTimeOffset.UtcNow, tenantId: "test-tenant",
|
||||
format: "csv",
|
||||
requestedBy: "user");
|
||||
Assert.Equal(LedgerExportStatus.Pending, export.Status);
|
||||
|
||||
// Start
|
||||
export = export.Start();
|
||||
export = export.Start(DateTimeOffset.UtcNow);
|
||||
Assert.Equal(LedgerExportStatus.Processing, export.Status);
|
||||
Assert.NotNull(export.StartedAt);
|
||||
|
||||
// Complete
|
||||
export = export.Complete("file:///out.csv", "sha256:xyz", 2048, 50);
|
||||
export = export.Complete("file:///out.csv", "sha256:xyz", 2048, 50, DateTimeOffset.UtcNow);
|
||||
Assert.Equal(LedgerExportStatus.Completed, export.Status);
|
||||
Assert.NotNull(export.CompletedAt);
|
||||
}
|
||||
@@ -197,16 +188,15 @@ public sealed class LedgerExportTests
|
||||
public void ExportLifecycle_FailedFlow_TracksStates()
|
||||
{
|
||||
// Create
|
||||
var export = LedgerExport.CreateRequest(
|
||||
tenantId: "test-tenant",
|
||||
var export = LedgerExport.CreateRequest(requestedAt: DateTimeOffset.UtcNow, tenantId: "test-tenant",
|
||||
format: "json",
|
||||
requestedBy: "user");
|
||||
|
||||
// Start
|
||||
export = export.Start();
|
||||
export = export.Start(DateTimeOffset.UtcNow);
|
||||
|
||||
// Fail
|
||||
export = export.Fail("Out of disk space");
|
||||
export = export.Fail("Out of disk space", DateTimeOffset.UtcNow);
|
||||
Assert.Equal(LedgerExportStatus.Failed, export.Status);
|
||||
Assert.Equal("Out of disk space", export.ErrorMessage);
|
||||
}
|
||||
@@ -216,17 +206,16 @@ public sealed class LedgerExportTests
|
||||
{
|
||||
// Arrange
|
||||
var sourceId = Guid.NewGuid();
|
||||
var export = LedgerExport.CreateRequest(
|
||||
tenantId: "test-tenant",
|
||||
var export = LedgerExport.CreateRequest(requestedAt: DateTimeOffset.UtcNow, tenantId: "test-tenant",
|
||||
format: "json",
|
||||
requestedBy: "user",
|
||||
startTime: DateTimeOffset.UtcNow.AddDays(-1),
|
||||
endTime: DateTimeOffset.UtcNow,
|
||||
runTypeFilter: "scan",
|
||||
sourceIdFilter: sourceId).Start();
|
||||
sourceIdFilter: sourceId).Start(DateTimeOffset.UtcNow);
|
||||
|
||||
// Act
|
||||
var completed = export.Complete("uri", "digest", 100, 10);
|
||||
var completed = export.Complete("uri", "digest", 100, 10, DateTimeOffset.UtcNow);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("test-tenant", completed.TenantId);
|
||||
@@ -236,3 +225,4 @@ public sealed class LedgerExportTests
|
||||
Assert.Equal("user", completed.RequestedBy);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ public sealed class RunLedgerTests
|
||||
artifacts: artifacts,
|
||||
inputDigest: "abc123",
|
||||
sequenceNumber: 1,
|
||||
previousEntryHash: null);
|
||||
previousEntryHash: null, ledgerCreatedAt: DateTimeOffset.UtcNow);
|
||||
|
||||
// Assert
|
||||
Assert.NotEqual(Guid.Empty, entry.LedgerId);
|
||||
@@ -66,7 +66,7 @@ public sealed class RunLedgerTests
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
RunLedgerEntry.FromCompletedRun(run, [], "input", 1, null));
|
||||
RunLedgerEntry.FromCompletedRun(run, [], "input", 1, null, DateTimeOffset.UtcNow));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -74,7 +74,7 @@ public sealed class RunLedgerTests
|
||||
{
|
||||
// Arrange
|
||||
var run = CreateCompletedRun();
|
||||
var entry = RunLedgerEntry.FromCompletedRun(run, [], "input", 1, null);
|
||||
var entry = RunLedgerEntry.FromCompletedRun(run, [], "input", 1, null, DateTimeOffset.UtcNow);
|
||||
|
||||
// Act
|
||||
var isValid = entry.VerifyIntegrity();
|
||||
@@ -88,7 +88,7 @@ public sealed class RunLedgerTests
|
||||
{
|
||||
// Arrange
|
||||
var run = CreateCompletedRun();
|
||||
var entry = RunLedgerEntry.FromCompletedRun(run, [], "input", 1, null);
|
||||
var entry = RunLedgerEntry.FromCompletedRun(run, [], "input", 1, null, DateTimeOffset.UtcNow);
|
||||
|
||||
// Tamper with the entry
|
||||
var tamperedEntry = entry with { TotalJobs = 999 };
|
||||
@@ -105,7 +105,7 @@ public sealed class RunLedgerTests
|
||||
{
|
||||
// Arrange
|
||||
var run = CreateCompletedRun();
|
||||
var entry = RunLedgerEntry.FromCompletedRun(run, [], "input", 1, null);
|
||||
var entry = RunLedgerEntry.FromCompletedRun(run, [], "input", 1, null, DateTimeOffset.UtcNow);
|
||||
|
||||
// Act
|
||||
var isValid = entry.VerifyChainLink(null);
|
||||
@@ -119,10 +119,10 @@ public sealed class RunLedgerTests
|
||||
{
|
||||
// Arrange
|
||||
var run1 = CreateCompletedRun();
|
||||
var first = RunLedgerEntry.FromCompletedRun(run1, [], "input1", 1, null);
|
||||
var first = RunLedgerEntry.FromCompletedRun(run1, [], "input1", 1, null, DateTimeOffset.UtcNow);
|
||||
|
||||
var run2 = CreateCompletedRun();
|
||||
var second = RunLedgerEntry.FromCompletedRun(run2, [], "input2", 2, first.ContentHash);
|
||||
var second = RunLedgerEntry.FromCompletedRun(run2, [], "input2", 2, first.ContentHash, DateTimeOffset.UtcNow);
|
||||
|
||||
// Act
|
||||
var isValid = second.VerifyChainLink(first);
|
||||
@@ -136,10 +136,10 @@ public sealed class RunLedgerTests
|
||||
{
|
||||
// Arrange
|
||||
var run1 = CreateCompletedRun();
|
||||
var first = RunLedgerEntry.FromCompletedRun(run1, [], "input1", 1, null);
|
||||
var first = RunLedgerEntry.FromCompletedRun(run1, [], "input1", 1, null, DateTimeOffset.UtcNow);
|
||||
|
||||
var run2 = CreateCompletedRun();
|
||||
var second = RunLedgerEntry.FromCompletedRun(run2, [], "input2", 2, "invalid_hash");
|
||||
var second = RunLedgerEntry.FromCompletedRun(run2, [], "input2", 2, "invalid_hash", DateTimeOffset.UtcNow);
|
||||
|
||||
// Act
|
||||
var isValid = second.VerifyChainLink(first);
|
||||
@@ -173,7 +173,7 @@ public sealed class RunLedgerTests
|
||||
Metadata: null);
|
||||
|
||||
// Act
|
||||
var entry = RunLedgerEntry.FromCompletedRun(run, [], "input", 1, null);
|
||||
var entry = RunLedgerEntry.FromCompletedRun(run, [], "input", 1, null, DateTimeOffset.UtcNow);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(completedAt - startedAt, entry.ExecutionDuration);
|
||||
@@ -189,7 +189,7 @@ public sealed class RunLedgerTests
|
||||
var artifacts = CreateArtifacts(run.RunId, 3);
|
||||
|
||||
// Act
|
||||
var entry = RunLedgerEntry.FromCompletedRun(run, artifacts, "input", 1, null);
|
||||
var entry = RunLedgerEntry.FromCompletedRun(run, artifacts, "input", 1, null, DateTimeOffset.UtcNow);
|
||||
|
||||
// Assert
|
||||
Assert.NotEmpty(entry.ArtifactManifest);
|
||||
@@ -204,7 +204,7 @@ public sealed class RunLedgerTests
|
||||
var run = CreateCompletedRun();
|
||||
|
||||
// Act
|
||||
var entry = RunLedgerEntry.FromCompletedRun(run, [], "input", 1, null);
|
||||
var entry = RunLedgerEntry.FromCompletedRun(run, [], "input", 1, null, DateTimeOffset.UtcNow);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("[]", entry.ArtifactManifest);
|
||||
@@ -238,7 +238,7 @@ public sealed class RunLedgerTests
|
||||
Metadata: null);
|
||||
|
||||
// Act
|
||||
var entry = RunLedgerEntry.FromCompletedRun(run, [], "input", 1, null);
|
||||
var entry = RunLedgerEntry.FromCompletedRun(run, [], "input", 1, null, DateTimeOffset.UtcNow);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(status, entry.FinalStatus);
|
||||
@@ -253,7 +253,7 @@ public sealed class RunLedgerTests
|
||||
var metadata = """{"custom":"metadata","count":42}""";
|
||||
|
||||
// Act
|
||||
var entry = RunLedgerEntry.FromCompletedRun(run, [], "input", 1, null, metadata);
|
||||
var entry = RunLedgerEntry.FromCompletedRun(run, [], "input", 1, null, DateTimeOffset.UtcNow, metadata);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(metadata, entry.Metadata);
|
||||
@@ -266,7 +266,7 @@ public sealed class RunLedgerTests
|
||||
// The hash should be different because OccurredAt is included
|
||||
|
||||
var run1 = CreateCompletedRun();
|
||||
var entry1 = RunLedgerEntry.FromCompletedRun(run1, [], "same-input", 1, null);
|
||||
var entry1 = RunLedgerEntry.FromCompletedRun(run1, [], "same-input", 1, null, DateTimeOffset.UtcNow);
|
||||
|
||||
// Use the exact same run to ensure determinism
|
||||
var run2 = run1;
|
||||
@@ -316,3 +316,6 @@ public sealed class RunLedgerTests
|
||||
return artifacts;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -59,11 +59,7 @@ public sealed class SignedManifestTests
|
||||
public void CreateFromExport_WithIncompleteExport_ThrowsException()
|
||||
{
|
||||
// Arrange
|
||||
var export = LedgerExport.CreateRequest(
|
||||
tenantId: "test-tenant",
|
||||
format: "json",
|
||||
requestedBy: "user",
|
||||
requestedAt: BaseTime);
|
||||
var export = LedgerExport.CreateRequest(tenantId: "test-tenant", format: "json", requestedBy: "user", requestedAt: BaseTime);
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
@@ -352,11 +348,7 @@ public sealed class SignedManifestTests
|
||||
|
||||
private static LedgerExport CreateCompletedExport()
|
||||
{
|
||||
var export = LedgerExport.CreateRequest(
|
||||
tenantId: "test-tenant",
|
||||
format: "json",
|
||||
requestedBy: "user",
|
||||
requestedAt: BaseTime);
|
||||
var export = LedgerExport.CreateRequest(tenantId: "test-tenant", format: "json", requestedBy: "user", requestedAt: BaseTime);
|
||||
|
||||
return export
|
||||
.Start(BaseTime.AddMinutes(1))
|
||||
@@ -401,3 +393,5 @@ public sealed class SignedManifestTests
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -262,12 +262,12 @@ public class BackfillRequestTests
|
||||
public void Complete_TransitionsToCompleted()
|
||||
{
|
||||
var request = BackfillRequest.Create(TenantId, SourceId, null,
|
||||
BaseTime, BaseTime.AddDays(1), "Test", "admin")
|
||||
BaseTime, BaseTime.AddDays(1), "Test", "admin", BaseTime)
|
||||
.StartValidation("v")
|
||||
.WithSafetyChecks(BackfillSafetyChecks.AllPassed(), 1000, TimeSpan.FromMinutes(10), "v")
|
||||
.Start("worker");
|
||||
.Start("worker", DateTimeOffset.UtcNow);
|
||||
|
||||
var completed = request.Complete("worker");
|
||||
var completed = request.Complete("worker", DateTimeOffset.UtcNow);
|
||||
|
||||
Assert.Equal(BackfillStatus.Completed, completed.Status);
|
||||
Assert.NotNull(completed.CompletedAt);
|
||||
@@ -279,12 +279,12 @@ public class BackfillRequestTests
|
||||
public void Fail_TransitionsToFailed()
|
||||
{
|
||||
var request = BackfillRequest.Create(TenantId, SourceId, null,
|
||||
BaseTime, BaseTime.AddDays(1), "Test", "admin")
|
||||
BaseTime, BaseTime.AddDays(1), "Test", "admin", BaseTime)
|
||||
.StartValidation("v")
|
||||
.WithSafetyChecks(BackfillSafetyChecks.AllPassed(), 1000, TimeSpan.FromMinutes(10), "v")
|
||||
.Start("worker");
|
||||
.Start("worker", DateTimeOffset.UtcNow);
|
||||
|
||||
var failed = request.Fail("Connection timeout", "worker");
|
||||
var failed = request.Fail("Connection timeout", "worker", DateTimeOffset.UtcNow);
|
||||
|
||||
Assert.Equal(BackfillStatus.Failed, failed.Status);
|
||||
Assert.Equal("Connection timeout", failed.ErrorMessage);
|
||||
@@ -296,12 +296,12 @@ public class BackfillRequestTests
|
||||
public void Cancel_TransitionsToCanceled()
|
||||
{
|
||||
var request = BackfillRequest.Create(TenantId, SourceId, null,
|
||||
BaseTime, BaseTime.AddDays(1), "Test", "admin")
|
||||
BaseTime, BaseTime.AddDays(1), "Test", "admin", BaseTime)
|
||||
.StartValidation("v")
|
||||
.WithSafetyChecks(BackfillSafetyChecks.AllPassed(), 1000, TimeSpan.FromMinutes(10), "v")
|
||||
.Start("worker");
|
||||
.Start("worker", DateTimeOffset.UtcNow);
|
||||
|
||||
var canceled = request.Cancel("admin");
|
||||
var canceled = request.Cancel("admin", DateTimeOffset.UtcNow);
|
||||
|
||||
Assert.Equal(BackfillStatus.Canceled, canceled.Status);
|
||||
Assert.NotNull(canceled.CompletedAt);
|
||||
@@ -312,13 +312,13 @@ public class BackfillRequestTests
|
||||
public void Cancel_FromTerminalState_Throws()
|
||||
{
|
||||
var request = BackfillRequest.Create(TenantId, SourceId, null,
|
||||
BaseTime, BaseTime.AddDays(1), "Test", "admin")
|
||||
BaseTime, BaseTime.AddDays(1), "Test", "admin", BaseTime)
|
||||
.StartValidation("v")
|
||||
.WithSafetyChecks(BackfillSafetyChecks.AllPassed(), 1000, TimeSpan.FromMinutes(10), "v")
|
||||
.Start("worker")
|
||||
.Complete("worker");
|
||||
.Start("worker", DateTimeOffset.UtcNow)
|
||||
.Complete("worker", DateTimeOffset.UtcNow);
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => request.Cancel("admin"));
|
||||
Assert.Throws<InvalidOperationException>(() => request.Cancel("admin", DateTimeOffset.UtcNow));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -407,3 +407,6 @@ public class BackfillSafetyChecksTests
|
||||
Assert.False(checks.HasBlockingIssues);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ public class WatermarkTests
|
||||
[Fact]
|
||||
public void Create_WithSourceId_CreatesValidWatermark()
|
||||
{
|
||||
var watermark = Watermark.Create(TenantId, SourceId, null, BaseTime, "system");
|
||||
var watermark = Watermark.Create(TenantId, SourceId, null, BaseTime, "system", DateTimeOffset.UtcNow);
|
||||
|
||||
Assert.NotEqual(Guid.Empty, watermark.WatermarkId);
|
||||
Assert.Equal(TenantId, watermark.TenantId);
|
||||
@@ -55,7 +55,7 @@ public class WatermarkTests
|
||||
[Fact]
|
||||
public void Create_WithJobType_CreatesValidWatermark()
|
||||
{
|
||||
var watermark = Watermark.Create(TenantId, null, JobType, BaseTime, "system");
|
||||
var watermark = Watermark.Create(TenantId, null, JobType, BaseTime, "system", DateTimeOffset.UtcNow);
|
||||
|
||||
Assert.NotEqual(Guid.Empty, watermark.WatermarkId);
|
||||
Assert.Equal(TenantId, watermark.TenantId);
|
||||
@@ -67,7 +67,7 @@ public class WatermarkTests
|
||||
[Fact]
|
||||
public void Create_WithBothSourceIdAndJobType_CreatesCombinedScopeKey()
|
||||
{
|
||||
var watermark = Watermark.Create(TenantId, SourceId, JobType, BaseTime, "system");
|
||||
var watermark = Watermark.Create(TenantId, SourceId, JobType, BaseTime, "system", DateTimeOffset.UtcNow);
|
||||
|
||||
Assert.Equal(SourceId, watermark.SourceId);
|
||||
Assert.Equal(JobType, watermark.JobType);
|
||||
@@ -79,17 +79,17 @@ public class WatermarkTests
|
||||
public void Create_WithoutSourceIdOrJobType_Throws()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
Watermark.Create(TenantId, null, null, BaseTime, "system"));
|
||||
Watermark.Create(TenantId, null, null, BaseTime, "system", DateTimeOffset.UtcNow));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Advance_IncreasesHighWatermarkAndSequence()
|
||||
{
|
||||
var watermark = Watermark.Create(TenantId, SourceId, null, BaseTime, "system");
|
||||
var watermark = Watermark.Create(TenantId, SourceId, null, BaseTime, "system", DateTimeOffset.UtcNow);
|
||||
var newTime = BaseTime.AddHours(1);
|
||||
var batchHash = "abc123def456";
|
||||
|
||||
var advanced = watermark.Advance(newTime, 100, batchHash, "worker-1");
|
||||
var advanced = watermark.Advance(newTime, 100, batchHash, "worker-1", DateTimeOffset.UtcNow);
|
||||
|
||||
Assert.Equal(newTime, advanced.HighWatermark);
|
||||
Assert.Equal(1, advanced.SequenceNumber);
|
||||
@@ -101,10 +101,10 @@ public class WatermarkTests
|
||||
[Fact]
|
||||
public void Advance_AccumulatesProcessedCount()
|
||||
{
|
||||
var watermark = Watermark.Create(TenantId, SourceId, null, BaseTime, "system");
|
||||
var watermark = Watermark.Create(TenantId, SourceId, null, BaseTime, "system", DateTimeOffset.UtcNow);
|
||||
|
||||
var after1 = watermark.Advance(BaseTime.AddHours(1), 100, null, "worker");
|
||||
var after2 = after1.Advance(BaseTime.AddHours(2), 150, null, "worker");
|
||||
var after1 = watermark.Advance(BaseTime.AddHours(1), 100, null, "worker", DateTimeOffset.UtcNow);
|
||||
var after2 = after1.Advance(BaseTime.AddHours(2), 150, null, "worker", DateTimeOffset.UtcNow);
|
||||
|
||||
Assert.Equal(250, after2.ProcessedCount);
|
||||
Assert.Equal(2, after2.SequenceNumber);
|
||||
@@ -113,21 +113,21 @@ public class WatermarkTests
|
||||
[Fact]
|
||||
public void Advance_WithEarlierTime_Throws()
|
||||
{
|
||||
var watermark = Watermark.Create(TenantId, SourceId, null, BaseTime, "system");
|
||||
var watermark = Watermark.Create(TenantId, SourceId, null, BaseTime, "system", DateTimeOffset.UtcNow);
|
||||
var earlierTime = BaseTime.AddHours(-1);
|
||||
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
watermark.Advance(earlierTime, 100, null, "worker"));
|
||||
watermark.Advance(earlierTime, 100, null, "worker", DateTimeOffset.UtcNow));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WithWindow_SetsWindowBounds()
|
||||
{
|
||||
var watermark = Watermark.Create(TenantId, SourceId, null, BaseTime, "system");
|
||||
var watermark = Watermark.Create(TenantId, SourceId, null, BaseTime, "system", DateTimeOffset.UtcNow);
|
||||
var lowWm = BaseTime.AddHours(-1);
|
||||
var highWm = BaseTime.AddHours(1);
|
||||
|
||||
var windowed = watermark.WithWindow(lowWm, highWm);
|
||||
var windowed = watermark.WithWindow(lowWm, highWm, DateTimeOffset.UtcNow);
|
||||
|
||||
Assert.Equal(lowWm, windowed.LowWatermark);
|
||||
Assert.Equal(highWm, windowed.HighWatermark);
|
||||
@@ -136,16 +136,16 @@ public class WatermarkTests
|
||||
[Fact]
|
||||
public void WithWindow_HighBeforeLow_Throws()
|
||||
{
|
||||
var watermark = Watermark.Create(TenantId, SourceId, null, BaseTime, "system");
|
||||
var watermark = Watermark.Create(TenantId, SourceId, null, BaseTime, "system", DateTimeOffset.UtcNow);
|
||||
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
watermark.WithWindow(BaseTime.AddHours(1), BaseTime.AddHours(-1)));
|
||||
watermark.WithWindow(BaseTime.AddHours(1), BaseTime.AddHours(-1), DateTimeOffset.UtcNow));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WatermarkSnapshot_CalculatesLag()
|
||||
{
|
||||
var watermark = Watermark.Create(TenantId, SourceId, null, BaseTime, "system");
|
||||
var watermark = Watermark.Create(TenantId, SourceId, null, BaseTime, "system", DateTimeOffset.UtcNow);
|
||||
var now = BaseTime.AddHours(2);
|
||||
|
||||
var snapshot = WatermarkSnapshot.FromWatermark(watermark, now);
|
||||
@@ -155,3 +155,5 @@ public class WatermarkTests
|
||||
Assert.Equal(TimeSpan.FromHours(2), snapshot.Lag);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -57,7 +57,8 @@ public class CanonicalJsonHasherTests
|
||||
resourceId: Guid.Parse("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"),
|
||||
actorId: "user-1",
|
||||
actorType: ActorType.User,
|
||||
description: "created job");
|
||||
description: "created job",
|
||||
occurredAt: DateTimeOffset.UtcNow);
|
||||
|
||||
Assert.True(entry.VerifyIntegrity(_hasher));
|
||||
|
||||
@@ -66,3 +67,5 @@ public class CanonicalJsonHasherTests
|
||||
Assert.False(tampered.VerifyIntegrity(_hasher));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ public class NotificationRuleTests
|
||||
TenantId,
|
||||
NotificationChannel.Slack,
|
||||
"https://hooks.slack.com/test",
|
||||
"admin");
|
||||
"admin", DateTimeOffset.UtcNow);
|
||||
|
||||
Assert.NotEqual(Guid.Empty, rule.RuleId);
|
||||
Assert.Equal(TenantId, rule.TenantId);
|
||||
@@ -39,7 +39,7 @@ public class NotificationRuleTests
|
||||
TenantId,
|
||||
NotificationChannel.Email,
|
||||
"alerts@example.com",
|
||||
"admin",
|
||||
"admin", DateTimeOffset.UtcNow,
|
||||
jobTypePattern: "scan\\.*",
|
||||
errorCodePattern: "ORCH-TRN-.*",
|
||||
category: ErrorCategory.Transient,
|
||||
@@ -58,7 +58,7 @@ public class NotificationRuleTests
|
||||
TenantId,
|
||||
NotificationChannel.Webhook,
|
||||
"https://webhook.example.com",
|
||||
"admin",
|
||||
"admin", DateTimeOffset.UtcNow,
|
||||
cooldownMinutes: 30,
|
||||
maxPerHour: 5,
|
||||
aggregate: false);
|
||||
@@ -71,7 +71,7 @@ public class NotificationRuleTests
|
||||
[Fact]
|
||||
public void Matches_WithNoFilters_MatchesAll()
|
||||
{
|
||||
var rule = NotificationRule.Create(TenantId, NotificationChannel.Slack, "url", "admin");
|
||||
var rule = NotificationRule.Create(TenantId, NotificationChannel.Slack, "url", "admin", DateTimeOffset.UtcNow);
|
||||
var entry = CreateTestEntry();
|
||||
|
||||
Assert.True(rule.Matches(entry));
|
||||
@@ -80,7 +80,7 @@ public class NotificationRuleTests
|
||||
[Fact]
|
||||
public void Matches_WhenDisabled_ReturnsFalse()
|
||||
{
|
||||
var rule = NotificationRule.Create(TenantId, NotificationChannel.Slack, "url", "admin")
|
||||
var rule = NotificationRule.Create(TenantId, NotificationChannel.Slack, "url", "admin", DateTimeOffset.UtcNow)
|
||||
with { Enabled = false };
|
||||
var entry = CreateTestEntry();
|
||||
|
||||
@@ -91,7 +91,7 @@ public class NotificationRuleTests
|
||||
public void Matches_WithSourceIdFilter_MatchesOnlyMatchingSource()
|
||||
{
|
||||
var sourceId = Guid.NewGuid();
|
||||
var rule = NotificationRule.Create(TenantId, NotificationChannel.Slack, "url", "admin",
|
||||
var rule = NotificationRule.Create(TenantId, NotificationChannel.Slack, "url", "admin", DateTimeOffset.UtcNow,
|
||||
sourceId: sourceId);
|
||||
|
||||
var matchingEntry = CreateTestEntry() with { SourceId = sourceId };
|
||||
@@ -104,7 +104,7 @@ public class NotificationRuleTests
|
||||
[Fact]
|
||||
public void Matches_WithCategoryFilter_MatchesOnlyMatchingCategory()
|
||||
{
|
||||
var rule = NotificationRule.Create(TenantId, NotificationChannel.Slack, "url", "admin",
|
||||
var rule = NotificationRule.Create(TenantId, NotificationChannel.Slack, "url", "admin", DateTimeOffset.UtcNow,
|
||||
category: ErrorCategory.Transient);
|
||||
|
||||
var matchingEntry = CreateTestEntry() with { Category = ErrorCategory.Transient };
|
||||
@@ -117,7 +117,7 @@ public class NotificationRuleTests
|
||||
[Fact]
|
||||
public void Matches_WithJobTypePattern_MatchesRegex()
|
||||
{
|
||||
var rule = NotificationRule.Create(TenantId, NotificationChannel.Slack, "url", "admin",
|
||||
var rule = NotificationRule.Create(TenantId, NotificationChannel.Slack, "url", "admin", DateTimeOffset.UtcNow,
|
||||
jobTypePattern: @"scan\..*");
|
||||
|
||||
var matchingEntry1 = CreateTestEntry() with { JobType = "scan.image" };
|
||||
@@ -132,7 +132,7 @@ public class NotificationRuleTests
|
||||
[Fact]
|
||||
public void Matches_WithErrorCodePattern_MatchesRegex()
|
||||
{
|
||||
var rule = NotificationRule.Create(TenantId, NotificationChannel.Slack, "url", "admin",
|
||||
var rule = NotificationRule.Create(TenantId, NotificationChannel.Slack, "url", "admin", DateTimeOffset.UtcNow,
|
||||
errorCodePattern: @"ORCH-TRN-\d+");
|
||||
|
||||
var matchingEntry = CreateTestEntry() with { ErrorCode = "ORCH-TRN-001" };
|
||||
@@ -145,7 +145,7 @@ public class NotificationRuleTests
|
||||
[Fact]
|
||||
public void CanNotify_WhenDisabled_ReturnsFalse()
|
||||
{
|
||||
var rule = NotificationRule.Create(TenantId, NotificationChannel.Slack, "url", "admin")
|
||||
var rule = NotificationRule.Create(TenantId, NotificationChannel.Slack, "url", "admin", DateTimeOffset.UtcNow)
|
||||
with { Enabled = false };
|
||||
|
||||
Assert.False(rule.CanNotify(BaseTime, 0));
|
||||
@@ -154,7 +154,7 @@ public class NotificationRuleTests
|
||||
[Fact]
|
||||
public void CanNotify_WithinCooldown_ReturnsFalse()
|
||||
{
|
||||
var rule = NotificationRule.Create(TenantId, NotificationChannel.Slack, "url", "admin",
|
||||
var rule = NotificationRule.Create(TenantId, NotificationChannel.Slack, "url", "admin", DateTimeOffset.UtcNow,
|
||||
cooldownMinutes: 15) with { LastNotifiedAt = BaseTime };
|
||||
|
||||
Assert.False(rule.CanNotify(BaseTime.AddMinutes(10), 0));
|
||||
@@ -163,7 +163,7 @@ public class NotificationRuleTests
|
||||
[Fact]
|
||||
public void CanNotify_AfterCooldown_ReturnsTrue()
|
||||
{
|
||||
var rule = NotificationRule.Create(TenantId, NotificationChannel.Slack, "url", "admin",
|
||||
var rule = NotificationRule.Create(TenantId, NotificationChannel.Slack, "url", "admin", DateTimeOffset.UtcNow,
|
||||
cooldownMinutes: 15) with { LastNotifiedAt = BaseTime };
|
||||
|
||||
Assert.True(rule.CanNotify(BaseTime.AddMinutes(20), 0));
|
||||
@@ -172,7 +172,7 @@ public class NotificationRuleTests
|
||||
[Fact]
|
||||
public void CanNotify_AtMaxPerHour_ReturnsFalse()
|
||||
{
|
||||
var rule = NotificationRule.Create(TenantId, NotificationChannel.Slack, "url", "admin",
|
||||
var rule = NotificationRule.Create(TenantId, NotificationChannel.Slack, "url", "admin", DateTimeOffset.UtcNow,
|
||||
maxPerHour: 5);
|
||||
|
||||
Assert.False(rule.CanNotify(BaseTime, 5));
|
||||
@@ -181,7 +181,7 @@ public class NotificationRuleTests
|
||||
[Fact]
|
||||
public void CanNotify_BelowMaxPerHour_ReturnsTrue()
|
||||
{
|
||||
var rule = NotificationRule.Create(TenantId, NotificationChannel.Slack, "url", "admin",
|
||||
var rule = NotificationRule.Create(TenantId, NotificationChannel.Slack, "url", "admin", DateTimeOffset.UtcNow,
|
||||
maxPerHour: 5);
|
||||
|
||||
Assert.True(rule.CanNotify(BaseTime, 4));
|
||||
@@ -190,7 +190,7 @@ public class NotificationRuleTests
|
||||
[Fact]
|
||||
public void CanNotify_WithNoLastNotification_ReturnsTrue()
|
||||
{
|
||||
var rule = NotificationRule.Create(TenantId, NotificationChannel.Slack, "url", "admin");
|
||||
var rule = NotificationRule.Create(TenantId, NotificationChannel.Slack, "url", "admin", DateTimeOffset.UtcNow);
|
||||
|
||||
Assert.True(rule.CanNotify(BaseTime, 0));
|
||||
}
|
||||
@@ -198,7 +198,7 @@ public class NotificationRuleTests
|
||||
[Fact]
|
||||
public void RecordNotification_UpdatesFields()
|
||||
{
|
||||
var rule = NotificationRule.Create(TenantId, NotificationChannel.Slack, "url", "admin");
|
||||
var rule = NotificationRule.Create(TenantId, NotificationChannel.Slack, "url", "admin", DateTimeOffset.UtcNow);
|
||||
|
||||
var updated = rule.RecordNotification(BaseTime);
|
||||
|
||||
@@ -210,7 +210,7 @@ public class NotificationRuleTests
|
||||
[Fact]
|
||||
public void RecordNotification_IncrementsCount()
|
||||
{
|
||||
var rule = NotificationRule.Create(TenantId, NotificationChannel.Slack, "url", "admin")
|
||||
var rule = NotificationRule.Create(TenantId, NotificationChannel.Slack, "url", "admin", DateTimeOffset.UtcNow)
|
||||
with { NotificationsSent = 5 };
|
||||
|
||||
var updated = rule.RecordNotification(BaseTime);
|
||||
@@ -307,3 +307,6 @@ public class ReplayAuditRecordTests
|
||||
Assert.Equal(BaseTime.AddMinutes(1), failed.CompletedAt);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -884,16 +884,16 @@ public sealed class MirrorOperationRecorderTests
|
||||
public void Constructor_ThrowsOnNullDependencies()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() => new MirrorOperationRecorder(
|
||||
null!, _capsuleGenerator, _evidenceStore, NullLogger<MirrorOperationRecorder>.Instance));
|
||||
null!, _capsuleGenerator, _evidenceStore, TimeProvider.System, NullLogger<MirrorOperationRecorder>.Instance));
|
||||
|
||||
Assert.Throws<ArgumentNullException>(() => new MirrorOperationRecorder(
|
||||
_emitter, null!, _evidenceStore, NullLogger<MirrorOperationRecorder>.Instance));
|
||||
_emitter, null!, _evidenceStore, TimeProvider.System, NullLogger<MirrorOperationRecorder>.Instance));
|
||||
|
||||
Assert.Throws<ArgumentNullException>(() => new MirrorOperationRecorder(
|
||||
_emitter, _capsuleGenerator, null!, NullLogger<MirrorOperationRecorder>.Instance));
|
||||
_emitter, _capsuleGenerator, null!, TimeProvider.System, NullLogger<MirrorOperationRecorder>.Instance));
|
||||
|
||||
Assert.Throws<ArgumentNullException>(() => new MirrorOperationRecorder(
|
||||
_emitter, _capsuleGenerator, _evidenceStore, null!));
|
||||
_emitter, _capsuleGenerator, _evidenceStore, TimeProvider.System, null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -916,3 +916,5 @@ public sealed class MirrorOperationRecorderTests
|
||||
Assert.Equal(MirrorEventTypes.BundleCompleted, _emitter.EmittedEvents[3].EventType);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ public class EventPublishingTests
|
||||
var envelope = EventEnvelope.Create(
|
||||
eventType: OrchestratorEventType.JobCreated,
|
||||
tenantId: "tenant-1",
|
||||
actor: actor);
|
||||
actor: actor, occurredAt: DateTimeOffset.UtcNow);
|
||||
|
||||
Assert.NotNull(envelope.EventId);
|
||||
Assert.StartsWith("urn:orch:event:", envelope.EventId);
|
||||
@@ -46,7 +46,7 @@ public class EventPublishingTests
|
||||
tenantId: "tenant-1",
|
||||
actor: actor,
|
||||
job: job,
|
||||
correlationId: "corr-456",
|
||||
occurredAt: DateTimeOffset.UtcNow, correlationId: "corr-456",
|
||||
projectId: "proj-789");
|
||||
|
||||
Assert.NotNull(envelope.Job);
|
||||
@@ -68,7 +68,7 @@ public class EventPublishingTests
|
||||
eventType: OrchestratorEventType.ExportStarted,
|
||||
tenantId: "tenant-1",
|
||||
actor: actor,
|
||||
exportJob: exportJob);
|
||||
exportJob: exportJob, occurredAt: DateTimeOffset.UtcNow);
|
||||
|
||||
Assert.Equal(OrchestratorEventType.ExportStarted, envelope.EventType);
|
||||
Assert.NotNull(envelope.Job);
|
||||
@@ -84,7 +84,7 @@ public class EventPublishingTests
|
||||
eventType: OrchestratorEventType.PolicyUpdated,
|
||||
tenantId: "tenant-1",
|
||||
actor: actor,
|
||||
projectId: "proj-1");
|
||||
occurredAt: DateTimeOffset.UtcNow, projectId: "proj-1");
|
||||
|
||||
Assert.Equal(OrchestratorEventType.PolicyUpdated, envelope.EventType);
|
||||
Assert.Null(envelope.Job);
|
||||
@@ -98,7 +98,7 @@ public class EventPublishingTests
|
||||
var envelope = EventEnvelope.Create(
|
||||
eventType: OrchestratorEventType.AlertCreated,
|
||||
tenantId: "tenant-1",
|
||||
actor: actor);
|
||||
actor: actor, occurredAt: DateTimeOffset.UtcNow);
|
||||
|
||||
var json = envelope.ToJson();
|
||||
|
||||
@@ -117,7 +117,7 @@ public class EventPublishingTests
|
||||
eventType: OrchestratorEventType.ScheduleTriggered,
|
||||
tenantId: "tenant-1",
|
||||
actor: actor,
|
||||
projectId: "proj-1");
|
||||
occurredAt: DateTimeOffset.UtcNow, projectId: "proj-1");
|
||||
|
||||
var json = original.ToJson();
|
||||
var restored = EventEnvelope.FromJson(json);
|
||||
@@ -144,7 +144,7 @@ public class EventPublishingTests
|
||||
var envelope = EventEnvelope.Create(
|
||||
eventType: OrchestratorEventType.JobCreated,
|
||||
tenantId: "tenant-1",
|
||||
actor: actor);
|
||||
actor: actor, occurredAt: DateTimeOffset.UtcNow);
|
||||
|
||||
var digest = envelope.ComputeDigest(_cryptoHash);
|
||||
|
||||
@@ -538,7 +538,7 @@ public class EventPublishingTests
|
||||
var envelope = EventEnvelope.Create(
|
||||
eventType: OrchestratorEventType.JobCreated,
|
||||
tenantId: "tenant-1",
|
||||
actor: actor);
|
||||
actor: actor, occurredAt: DateTimeOffset.UtcNow);
|
||||
|
||||
var signed = await signer.SignAsync(envelope, CT);
|
||||
|
||||
@@ -557,7 +557,7 @@ public class EventPublishingTests
|
||||
eventType: OrchestratorEventType.ExportCompleted,
|
||||
tenantId: "tenant-1",
|
||||
actor: actor,
|
||||
projectId: "proj-1");
|
||||
occurredAt: DateTimeOffset.UtcNow, projectId: "proj-1");
|
||||
|
||||
var signed = await signer.SignAsync(original, CT);
|
||||
var verified = await signer.VerifyAsync(signed, CT);
|
||||
@@ -589,7 +589,7 @@ public class EventPublishingTests
|
||||
var envelope = EventEnvelope.Create(
|
||||
eventType: OrchestratorEventType.JobCreated,
|
||||
tenantId: "tenant-1",
|
||||
actor: actor);
|
||||
actor: actor, occurredAt: DateTimeOffset.UtcNow);
|
||||
|
||||
var result = await publisher.PublishAsync(envelope, CT);
|
||||
|
||||
@@ -605,7 +605,8 @@ public class EventPublishingTests
|
||||
EventEnvelope.Create(
|
||||
eventType: OrchestratorEventType.JobCreated,
|
||||
tenantId: "tenant-1",
|
||||
actor: actor));
|
||||
actor: actor,
|
||||
occurredAt: DateTimeOffset.UtcNow));
|
||||
|
||||
var result = await publisher.PublishBatchAsync(envelopes, CT);
|
||||
|
||||
@@ -643,7 +644,7 @@ public class EventPublishingTests
|
||||
var envelope = EventEnvelope.Create(
|
||||
eventType: OrchestratorEventType.JobCompleted,
|
||||
tenantId: "tenant-1",
|
||||
actor: actor);
|
||||
actor: actor, occurredAt: DateTimeOffset.UtcNow);
|
||||
|
||||
var result = await publisher.PublishAsync(envelope, CT);
|
||||
|
||||
@@ -668,7 +669,7 @@ public class EventPublishingTests
|
||||
eventType: OrchestratorEventType.JobCompleted,
|
||||
tenantId: "tenant-1",
|
||||
actor: actor,
|
||||
job: job);
|
||||
job: job, occurredAt: DateTimeOffset.UtcNow);
|
||||
|
||||
var result1 = await publisher.PublishAsync(envelope, CT);
|
||||
var result2 = await publisher.PublishAsync(envelope, CT);
|
||||
@@ -693,7 +694,7 @@ public class EventPublishingTests
|
||||
var envelope = EventEnvelope.Create(
|
||||
eventType: OrchestratorEventType.PolicyUpdated,
|
||||
tenantId: "tenant-1",
|
||||
actor: actor);
|
||||
actor: actor, occurredAt: DateTimeOffset.UtcNow);
|
||||
|
||||
await publisher.PublishAsync(envelope, CT);
|
||||
|
||||
@@ -713,36 +714,42 @@ public class EventPublishingTests
|
||||
store, bus, options, NullLogger<OrchestratorEventPublisher>.Instance);
|
||||
|
||||
var actor = EventActor.Service("test");
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
|
||||
// Export event
|
||||
await publisher.PublishAsync(EventEnvelope.Create(
|
||||
eventType: OrchestratorEventType.ExportCreated,
|
||||
tenantId: "t1",
|
||||
actor: actor), CT);
|
||||
actor: actor,
|
||||
occurredAt: now), CT);
|
||||
|
||||
// Policy event
|
||||
await publisher.PublishAsync(EventEnvelope.Create(
|
||||
eventType: OrchestratorEventType.PolicyUpdated,
|
||||
tenantId: "t1",
|
||||
actor: actor), CT);
|
||||
actor: actor,
|
||||
occurredAt: now), CT);
|
||||
|
||||
// Schedule event
|
||||
await publisher.PublishAsync(EventEnvelope.Create(
|
||||
eventType: OrchestratorEventType.ScheduleTriggered,
|
||||
tenantId: "t1",
|
||||
actor: actor), CT);
|
||||
actor: actor,
|
||||
occurredAt: now), CT);
|
||||
|
||||
// Alert event
|
||||
await publisher.PublishAsync(EventEnvelope.Create(
|
||||
eventType: OrchestratorEventType.AlertCreated,
|
||||
tenantId: "t1",
|
||||
actor: actor), CT);
|
||||
actor: actor,
|
||||
occurredAt: now), CT);
|
||||
|
||||
// Pack run event
|
||||
await publisher.PublishAsync(EventEnvelope.Create(
|
||||
eventType: OrchestratorEventType.PackRunStarted,
|
||||
tenantId: "t1",
|
||||
actor: actor), CT);
|
||||
actor: actor,
|
||||
occurredAt: now), CT);
|
||||
|
||||
Assert.Single(bus.GetMessages("orch.exports"));
|
||||
Assert.Single(bus.GetMessages("orch.policy"));
|
||||
@@ -766,7 +773,7 @@ public class EventPublishingTests
|
||||
eventType: OrchestratorEventType.JobCompleted,
|
||||
tenantId: "tenant-1",
|
||||
actor: actor,
|
||||
notifier: new EventNotifier("custom.channel", "raw", null));
|
||||
occurredAt: DateTimeOffset.UtcNow, notifier: new EventNotifier("custom.channel", "raw", null));
|
||||
|
||||
await publisher.PublishAsync(envelope, CT);
|
||||
|
||||
@@ -788,7 +795,7 @@ public class EventPublishingTests
|
||||
var envelope = EventEnvelope.Create(
|
||||
eventType: OrchestratorEventType.JobCompleted,
|
||||
tenantId: "tenant-1",
|
||||
actor: actor);
|
||||
actor: actor, occurredAt: DateTimeOffset.UtcNow);
|
||||
|
||||
Assert.False(await publisher.IsPublishedAsync(envelope.IdempotencyKey, CT));
|
||||
|
||||
@@ -813,7 +820,7 @@ public class EventPublishingTests
|
||||
eventType: OrchestratorEventType.JobCompleted,
|
||||
tenantId: "tenant-1",
|
||||
actor: actor,
|
||||
job: job);
|
||||
job: job, occurredAt: DateTimeOffset.UtcNow);
|
||||
|
||||
// First batch - all new
|
||||
var result1 = await publisher.PublishBatchAsync(new[] { envelope }, CT);
|
||||
@@ -844,7 +851,7 @@ public class EventPublishingTests
|
||||
var baseEnvelope = EventEnvelope.Create(
|
||||
eventType: OrchestratorEventType.JobCreated,
|
||||
tenantId: "tenant-1",
|
||||
actor: actor);
|
||||
actor: actor, occurredAt: DateTimeOffset.UtcNow);
|
||||
|
||||
var earliest = baseEnvelope with
|
||||
{
|
||||
@@ -972,3 +979,4 @@ public class EventPublishingTests
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
@@ -505,8 +505,7 @@ public sealed class JobAttestationServiceTests
|
||||
_service = new JobAttestationService(
|
||||
_signer,
|
||||
_store,
|
||||
_emitter,
|
||||
NullLogger<JobAttestationService>.Instance);
|
||||
_emitter, TimeProvider.System, NullLogger<JobAttestationService>.Instance);
|
||||
}
|
||||
|
||||
private JobAttestationRequest CreateRequest(
|
||||
@@ -589,7 +588,8 @@ public sealed class JobAttestationServiceTests
|
||||
Guid.NewGuid(),
|
||||
"test.job",
|
||||
JobCapsuleKind.JobCompletion,
|
||||
JobCapsuleInputs.FromPayload("{}"));
|
||||
JobCapsuleInputs.FromPayload("{}"),
|
||||
DateTimeOffset.UtcNow);
|
||||
var request = CreateRequest() with { Capsule = capsule };
|
||||
|
||||
var result = await _service.GenerateJobCompletionAttestationAsync(request);
|
||||
@@ -696,16 +696,16 @@ public sealed class JobAttestationServiceTests
|
||||
public void Constructor_ThrowsOnNullDependencies()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() => new JobAttestationService(
|
||||
null!, _store, _emitter, NullLogger<JobAttestationService>.Instance));
|
||||
null!, _store, _emitter, TimeProvider.System, NullLogger<JobAttestationService>.Instance));
|
||||
|
||||
Assert.Throws<ArgumentNullException>(() => new JobAttestationService(
|
||||
_signer, null!, _emitter, NullLogger<JobAttestationService>.Instance));
|
||||
_signer, null!, _emitter, TimeProvider.System, NullLogger<JobAttestationService>.Instance));
|
||||
|
||||
Assert.Throws<ArgumentNullException>(() => new JobAttestationService(
|
||||
_signer, _store, null!, NullLogger<JobAttestationService>.Instance));
|
||||
_signer, _store, null!, TimeProvider.System, NullLogger<JobAttestationService>.Instance));
|
||||
|
||||
Assert.Throws<ArgumentNullException>(() => new JobAttestationService(
|
||||
_signer, _store, _emitter, null!));
|
||||
_signer, _store, _emitter, TimeProvider.System, null!));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -759,3 +759,5 @@ internal sealed class TestTimelineEventEmitter : ITimelineEventEmitter
|
||||
|
||||
public void Clear() => _emittedEvents.Clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ public sealed class JobCapsuleTests
|
||||
public void JobCapsule_Create_GeneratesUniqueId()
|
||||
{
|
||||
var inputs = JobCapsuleInputs.FromPayload("{}");
|
||||
var capsule1 = JobCapsule.Create("tenant-1", Guid.NewGuid(), "export.ledger", JobCapsuleKind.JobScheduling, inputs);
|
||||
var capsule2 = JobCapsule.Create("tenant-1", Guid.NewGuid(), "export.ledger", JobCapsuleKind.JobScheduling, inputs);
|
||||
var capsule1 = JobCapsule.Create("tenant-1", Guid.NewGuid(), "export.ledger", JobCapsuleKind.JobScheduling, inputs, DateTimeOffset.UtcNow);
|
||||
var capsule2 = JobCapsule.Create("tenant-1", Guid.NewGuid(), "export.ledger", JobCapsuleKind.JobScheduling, inputs, DateTimeOffset.UtcNow);
|
||||
|
||||
Assert.NotEqual(capsule1.CapsuleId, capsule2.CapsuleId);
|
||||
}
|
||||
@@ -22,7 +22,7 @@ public sealed class JobCapsuleTests
|
||||
public void JobCapsule_Create_SetsSchemaVersion()
|
||||
{
|
||||
var inputs = JobCapsuleInputs.FromPayload("{}");
|
||||
var capsule = JobCapsule.Create("tenant-1", Guid.NewGuid(), "export.ledger", JobCapsuleKind.JobScheduling, inputs);
|
||||
var capsule = JobCapsule.Create("tenant-1", Guid.NewGuid(), "export.ledger", JobCapsuleKind.JobScheduling, inputs, DateTimeOffset.UtcNow);
|
||||
|
||||
Assert.Equal(JobCapsule.CurrentSchemaVersion, capsule.SchemaVersion);
|
||||
}
|
||||
@@ -31,7 +31,7 @@ public sealed class JobCapsuleTests
|
||||
public void JobCapsule_Create_ComputesRootHash()
|
||||
{
|
||||
var inputs = JobCapsuleInputs.FromPayload("{\"key\":\"value\"}");
|
||||
var capsule = JobCapsule.Create("tenant-1", Guid.NewGuid(), "export.ledger", JobCapsuleKind.JobScheduling, inputs);
|
||||
var capsule = JobCapsule.Create("tenant-1", Guid.NewGuid(), "export.ledger", JobCapsuleKind.JobScheduling, inputs, DateTimeOffset.UtcNow);
|
||||
|
||||
Assert.NotNull(capsule.RootHash);
|
||||
Assert.StartsWith("sha256:", capsule.RootHash);
|
||||
@@ -41,7 +41,7 @@ public sealed class JobCapsuleTests
|
||||
public void JobCapsule_ToJson_ProducesValidJson()
|
||||
{
|
||||
var inputs = JobCapsuleInputs.FromPayload("{\"format\":\"json\"}");
|
||||
var capsule = JobCapsule.Create("tenant-1", Guid.NewGuid(), "export.ledger", JobCapsuleKind.JobScheduling, inputs);
|
||||
var capsule = JobCapsule.Create("tenant-1", Guid.NewGuid(), "export.ledger", JobCapsuleKind.JobScheduling, inputs, DateTimeOffset.UtcNow);
|
||||
|
||||
var json = capsule.ToJson();
|
||||
|
||||
@@ -56,7 +56,7 @@ public sealed class JobCapsuleTests
|
||||
{
|
||||
var jobId = Guid.NewGuid();
|
||||
var inputs = JobCapsuleInputs.FromPayload("{\"format\":\"json\"}");
|
||||
var original = JobCapsule.Create("tenant-1", jobId, "export.ledger", JobCapsuleKind.JobScheduling, inputs, projectId: "proj-1");
|
||||
var original = JobCapsule.Create("tenant-1", jobId, "export.ledger", JobCapsuleKind.JobScheduling, inputs, DateTimeOffset.UtcNow, projectId: "proj-1");
|
||||
|
||||
var json = original.ToJson();
|
||||
var restored = JobCapsule.FromJson(json);
|
||||
@@ -73,7 +73,7 @@ public sealed class JobCapsuleTests
|
||||
public void JobCapsule_ToEvidencePointer_CreatesValidPointer()
|
||||
{
|
||||
var inputs = JobCapsuleInputs.FromPayload("{}");
|
||||
var capsule = JobCapsule.Create("tenant-1", Guid.NewGuid(), "export.ledger", JobCapsuleKind.JobScheduling, inputs);
|
||||
var capsule = JobCapsule.Create("tenant-1", Guid.NewGuid(), "export.ledger", JobCapsuleKind.JobScheduling, inputs, DateTimeOffset.UtcNow);
|
||||
|
||||
var pointer = capsule.ToEvidencePointer();
|
||||
|
||||
@@ -118,7 +118,7 @@ public sealed class JobCapsuleTests
|
||||
public void JobCapsule_SupportsAllKinds(JobCapsuleKind kind)
|
||||
{
|
||||
var inputs = JobCapsuleInputs.FromPayload("{}");
|
||||
var capsule = JobCapsule.Create("tenant-1", Guid.NewGuid(), "test.job", kind, inputs);
|
||||
var capsule = JobCapsule.Create("tenant-1", Guid.NewGuid(), "test.job", kind, inputs, DateTimeOffset.UtcNow);
|
||||
|
||||
Assert.Equal(kind, capsule.Kind);
|
||||
}
|
||||
@@ -132,8 +132,8 @@ public sealed class JobCapsuleTests
|
||||
new("output.json", "sha256:abc123", 1024, "application/json", null, null)
|
||||
};
|
||||
|
||||
var capsule1 = JobCapsule.Create("tenant-1", Guid.NewGuid(), "export.ledger", JobCapsuleKind.JobCompletion, inputs);
|
||||
var capsule2 = JobCapsule.Create("tenant-1", Guid.NewGuid(), "export.ledger", JobCapsuleKind.JobCompletion, inputs, artifacts: artifacts);
|
||||
var capsule1 = JobCapsule.Create("tenant-1", Guid.NewGuid(), "export.ledger", JobCapsuleKind.JobCompletion, inputs, DateTimeOffset.UtcNow);
|
||||
var capsule2 = JobCapsule.Create("tenant-1", Guid.NewGuid(), "export.ledger", JobCapsuleKind.JobCompletion, inputs, DateTimeOffset.UtcNow, artifacts: artifacts);
|
||||
|
||||
// Different artifacts should result in different root hashes
|
||||
Assert.NotEqual(capsule1.RootHash, capsule2.RootHash);
|
||||
@@ -152,8 +152,8 @@ public sealed class JobCapsuleTests
|
||||
RetryCount: 0,
|
||||
Error: null);
|
||||
|
||||
var capsule1 = JobCapsule.Create("tenant-1", Guid.NewGuid(), "export.ledger", JobCapsuleKind.JobCompletion, inputs);
|
||||
var capsule2 = JobCapsule.Create("tenant-1", Guid.NewGuid(), "export.ledger", JobCapsuleKind.JobCompletion, inputs, outputs: outputs);
|
||||
var capsule1 = JobCapsule.Create("tenant-1", Guid.NewGuid(), "export.ledger", JobCapsuleKind.JobCompletion, inputs, DateTimeOffset.UtcNow);
|
||||
var capsule2 = JobCapsule.Create("tenant-1", Guid.NewGuid(), "export.ledger", JobCapsuleKind.JobCompletion, inputs, DateTimeOffset.UtcNow, outputs: outputs);
|
||||
|
||||
Assert.NotEqual(capsule1.RootHash, capsule2.RootHash);
|
||||
}
|
||||
@@ -298,7 +298,7 @@ public sealed class InMemoryJobCapsuleStoreTests
|
||||
{
|
||||
var store = new InMemoryJobCapsuleStore();
|
||||
var inputs = JobCapsuleInputs.FromPayload("{}");
|
||||
var capsule = JobCapsule.Create("tenant-1", Guid.NewGuid(), "test.job", JobCapsuleKind.JobScheduling, inputs);
|
||||
var capsule = JobCapsule.Create("tenant-1", Guid.NewGuid(), "test.job", JobCapsuleKind.JobScheduling, inputs, DateTimeOffset.UtcNow);
|
||||
|
||||
await store.StoreAsync(capsule, CancellationToken.None);
|
||||
|
||||
@@ -310,7 +310,7 @@ public sealed class InMemoryJobCapsuleStoreTests
|
||||
{
|
||||
var store = new InMemoryJobCapsuleStore();
|
||||
var inputs = JobCapsuleInputs.FromPayload("{}");
|
||||
var capsule = JobCapsule.Create("tenant-1", Guid.NewGuid(), "test.job", JobCapsuleKind.JobScheduling, inputs);
|
||||
var capsule = JobCapsule.Create("tenant-1", Guid.NewGuid(), "test.job", JobCapsuleKind.JobScheduling, inputs, DateTimeOffset.UtcNow);
|
||||
|
||||
await store.StoreAsync(capsule, CancellationToken.None);
|
||||
var retrieved = await store.GetAsync(capsule.CapsuleId, CancellationToken.None);
|
||||
@@ -336,9 +336,9 @@ public sealed class InMemoryJobCapsuleStoreTests
|
||||
var jobId = Guid.NewGuid();
|
||||
var inputs = JobCapsuleInputs.FromPayload("{}");
|
||||
|
||||
var capsule1 = JobCapsule.Create("tenant-1", jobId, "test.job", JobCapsuleKind.JobScheduling, inputs);
|
||||
var capsule2 = JobCapsule.Create("tenant-1", jobId, "test.job", JobCapsuleKind.JobCompletion, inputs);
|
||||
var capsule3 = JobCapsule.Create("tenant-1", Guid.NewGuid(), "test.job", JobCapsuleKind.JobScheduling, inputs);
|
||||
var capsule1 = JobCapsule.Create("tenant-1", jobId, "test.job", JobCapsuleKind.JobScheduling, inputs, DateTimeOffset.UtcNow);
|
||||
var capsule2 = JobCapsule.Create("tenant-1", jobId, "test.job", JobCapsuleKind.JobCompletion, inputs, DateTimeOffset.UtcNow);
|
||||
var capsule3 = JobCapsule.Create("tenant-1", Guid.NewGuid(), "test.job", JobCapsuleKind.JobScheduling, inputs, DateTimeOffset.UtcNow);
|
||||
|
||||
await store.StoreAsync(capsule1, CancellationToken.None);
|
||||
await store.StoreAsync(capsule2, CancellationToken.None);
|
||||
@@ -355,7 +355,7 @@ public sealed class InMemoryJobCapsuleStoreTests
|
||||
{
|
||||
var store = new InMemoryJobCapsuleStore();
|
||||
var inputs = JobCapsuleInputs.FromPayload("{}");
|
||||
var capsule = JobCapsule.Create("tenant-1", Guid.NewGuid(), "test.job", JobCapsuleKind.JobScheduling, inputs);
|
||||
var capsule = JobCapsule.Create("tenant-1", Guid.NewGuid(), "test.job", JobCapsuleKind.JobScheduling, inputs, DateTimeOffset.UtcNow);
|
||||
|
||||
await store.StoreAsync(capsule, CancellationToken.None);
|
||||
Assert.Single(store.GetAll());
|
||||
@@ -365,3 +365,6 @@ public sealed class InMemoryJobCapsuleStoreTests
|
||||
Assert.Equal(0, store.Count);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ public sealed class ExportDistributionTests
|
||||
CreatedAt: DateTimeOffset.UtcNow);
|
||||
|
||||
var beforeUpdate = DateTimeOffset.UtcNow;
|
||||
var updated = distribution.WithDownloadUrl("https://download.example.com/test.json", TimeSpan.FromHours(1));
|
||||
var updated = distribution.WithDownloadUrl("https://download.example.com/test.json", TimeSpan.FromHours(1), DateTimeOffset.UtcNow);
|
||||
var afterUpdate = DateTimeOffset.UtcNow.AddHours(1);
|
||||
|
||||
Assert.Equal("https://download.example.com/test.json", updated.DownloadUrl);
|
||||
|
||||
@@ -78,7 +78,7 @@ public sealed class ExportRetentionTests
|
||||
ExtensionCount: 0,
|
||||
Metadata: null);
|
||||
|
||||
Assert.True(retention.IsExpired);
|
||||
Assert.True(retention.IsExpiredAt(DateTimeOffset.UtcNow));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -100,7 +100,7 @@ public sealed class ExportRetentionTests
|
||||
ExtensionCount: 0,
|
||||
Metadata: null);
|
||||
|
||||
Assert.False(retention.IsExpired);
|
||||
Assert.False(retention.IsExpiredAt(DateTimeOffset.UtcNow));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -122,7 +122,7 @@ public sealed class ExportRetentionTests
|
||||
ExtensionCount: 0,
|
||||
Metadata: null);
|
||||
|
||||
Assert.False(retention.IsExpired); // Legal hold prevents expiration
|
||||
Assert.False(retention.IsExpiredAt(DateTimeOffset.UtcNow)); // Legal hold prevents expiration
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -144,7 +144,7 @@ public sealed class ExportRetentionTests
|
||||
ExtensionCount: 0,
|
||||
Metadata: null);
|
||||
|
||||
Assert.True(retention.ShouldArchive);
|
||||
Assert.True(retention.ShouldArchiveAt(DateTimeOffset.UtcNow));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -166,7 +166,7 @@ public sealed class ExportRetentionTests
|
||||
ExtensionCount: 0,
|
||||
Metadata: null);
|
||||
|
||||
Assert.False(retention.ShouldArchive);
|
||||
Assert.False(retention.ShouldArchiveAt(DateTimeOffset.UtcNow));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -190,11 +190,11 @@ public sealed class ExportRetentionTests
|
||||
ExtensionCount: 0,
|
||||
Metadata: null);
|
||||
|
||||
Assert.False(retention.CanDelete); // Not released
|
||||
Assert.False(retention.CanDeleteAt(DateTimeOffset.UtcNow)); // Not released
|
||||
|
||||
// Now release
|
||||
var released = retention.Release("admin@example.com");
|
||||
Assert.True(released.CanDelete);
|
||||
var released = retention.Release("admin@example.com", DateTimeOffset.UtcNow);
|
||||
Assert.True(released.CanDeleteAt(DateTimeOffset.UtcNow));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -203,7 +203,7 @@ public sealed class ExportRetentionTests
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var retention = ExportRetention.Default(now);
|
||||
|
||||
var extended = retention.ExtendRetention(TimeSpan.FromDays(30), "Customer request");
|
||||
var extended = retention.ExtendRetention(TimeSpan.FromDays(30), DateTimeOffset.UtcNow, "Customer request");
|
||||
|
||||
Assert.Equal(1, extended.ExtensionCount);
|
||||
Assert.Equal(retention.ExpiresAt!.Value.AddDays(30), extended.ExpiresAt);
|
||||
@@ -218,8 +218,8 @@ public sealed class ExportRetentionTests
|
||||
var retention = ExportRetention.Default(now);
|
||||
|
||||
var extended = retention
|
||||
.ExtendRetention(TimeSpan.FromDays(10), "First extension")
|
||||
.ExtendRetention(TimeSpan.FromDays(20), "Second extension");
|
||||
.ExtendRetention(TimeSpan.FromDays(10), DateTimeOffset.UtcNow, "First extension")
|
||||
.ExtendRetention(TimeSpan.FromDays(20), DateTimeOffset.UtcNow, "Second extension");
|
||||
|
||||
Assert.Equal(2, extended.ExtensionCount);
|
||||
Assert.Equal(retention.ExpiresAt!.Value.AddDays(30), extended.ExpiresAt);
|
||||
@@ -254,7 +254,7 @@ public sealed class ExportRetentionTests
|
||||
var retention = ExportRetention.Compliance(DateTimeOffset.UtcNow, TimeSpan.FromDays(365));
|
||||
|
||||
var before = DateTimeOffset.UtcNow;
|
||||
var released = retention.Release("admin@example.com");
|
||||
var released = retention.Release("admin@example.com", DateTimeOffset.UtcNow);
|
||||
var after = DateTimeOffset.UtcNow;
|
||||
|
||||
Assert.Equal("admin@example.com", released.ReleasedBy);
|
||||
@@ -268,7 +268,7 @@ public sealed class ExportRetentionTests
|
||||
var retention = ExportRetention.Default(DateTimeOffset.UtcNow);
|
||||
|
||||
var before = DateTimeOffset.UtcNow;
|
||||
var archived = retention.MarkArchived();
|
||||
var archived = retention.MarkArchived(DateTimeOffset.UtcNow);
|
||||
var after = DateTimeOffset.UtcNow;
|
||||
|
||||
Assert.NotNull(archived.ArchivedAt);
|
||||
@@ -281,7 +281,7 @@ public sealed class ExportRetentionTests
|
||||
var retention = ExportRetention.Temporary(DateTimeOffset.UtcNow);
|
||||
|
||||
var before = DateTimeOffset.UtcNow;
|
||||
var deleted = retention.MarkDeleted();
|
||||
var deleted = retention.MarkDeleted(DateTimeOffset.UtcNow);
|
||||
var after = DateTimeOffset.UtcNow;
|
||||
|
||||
Assert.NotNull(deleted.DeletedAt);
|
||||
@@ -336,3 +336,8 @@ public sealed class ExportRetentionTests
|
||||
Assert.Equal(TimeSpan.FromDays(90), ExportRetention.DefaultPeriods.ArchiveDelay);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ public sealed class ExportScheduleTests
|
||||
exportType: "export.sbom",
|
||||
cronExpression: "0 0 * * *",
|
||||
payloadTemplate: payload,
|
||||
createdBy: "admin@example.com");
|
||||
createdBy: "admin@example.com", timestamp: DateTimeOffset.UtcNow);
|
||||
|
||||
var after = DateTimeOffset.UtcNow;
|
||||
|
||||
@@ -59,7 +59,7 @@ public sealed class ExportScheduleTests
|
||||
cronExpression: "0 0 * * SUN",
|
||||
payloadTemplate: payload,
|
||||
createdBy: "admin@example.com",
|
||||
description: "Weekly compliance report",
|
||||
timestamp: DateTimeOffset.UtcNow, description: "Weekly compliance report",
|
||||
timezone: "America/New_York",
|
||||
retentionPolicy: "compliance",
|
||||
projectId: "project-123",
|
||||
@@ -84,12 +84,12 @@ public sealed class ExportScheduleTests
|
||||
exportType: "export.sbom",
|
||||
cronExpression: "0 0 * * *",
|
||||
payloadTemplate: payload,
|
||||
createdBy: "admin@example.com");
|
||||
createdBy: "admin@example.com", timestamp: DateTimeOffset.UtcNow);
|
||||
|
||||
var disabled = schedule.Disable();
|
||||
var disabled = schedule.Disable(DateTimeOffset.UtcNow);
|
||||
Assert.False(disabled.Enabled);
|
||||
|
||||
var enabled = disabled.Enable();
|
||||
var enabled = disabled.Enable(DateTimeOffset.UtcNow);
|
||||
Assert.True(enabled.Enabled);
|
||||
Assert.True(enabled.UpdatedAt > disabled.UpdatedAt);
|
||||
}
|
||||
@@ -104,9 +104,9 @@ public sealed class ExportScheduleTests
|
||||
exportType: "export.sbom",
|
||||
cronExpression: "0 0 * * *",
|
||||
payloadTemplate: payload,
|
||||
createdBy: "admin@example.com");
|
||||
createdBy: "admin@example.com", timestamp: DateTimeOffset.UtcNow);
|
||||
|
||||
var disabled = schedule.Disable();
|
||||
var disabled = schedule.Disable(DateTimeOffset.UtcNow);
|
||||
|
||||
Assert.False(disabled.Enabled);
|
||||
Assert.True(disabled.UpdatedAt >= schedule.UpdatedAt);
|
||||
@@ -122,13 +122,13 @@ public sealed class ExportScheduleTests
|
||||
exportType: "export.sbom",
|
||||
cronExpression: "0 0 * * *",
|
||||
payloadTemplate: payload,
|
||||
createdBy: "admin@example.com");
|
||||
createdBy: "admin@example.com", timestamp: DateTimeOffset.UtcNow);
|
||||
|
||||
var jobId = Guid.NewGuid();
|
||||
var nextRun = DateTimeOffset.UtcNow.AddDays(1);
|
||||
var before = DateTimeOffset.UtcNow;
|
||||
|
||||
var updated = schedule.RecordSuccess(jobId, nextRun);
|
||||
var updated = schedule.RecordSuccess(jobId, nextRun, DateTimeOffset.UtcNow);
|
||||
|
||||
Assert.NotNull(updated.LastRunAt);
|
||||
Assert.True(updated.LastRunAt >= before);
|
||||
@@ -150,12 +150,12 @@ public sealed class ExportScheduleTests
|
||||
exportType: "export.sbom",
|
||||
cronExpression: "0 0 * * *",
|
||||
payloadTemplate: payload,
|
||||
createdBy: "admin@example.com");
|
||||
createdBy: "admin@example.com", timestamp: DateTimeOffset.UtcNow);
|
||||
|
||||
var jobId = Guid.NewGuid();
|
||||
var nextRun = DateTimeOffset.UtcNow.AddDays(1);
|
||||
|
||||
var updated = schedule.RecordFailure(jobId, "Database connection failed", nextRun);
|
||||
var updated = schedule.RecordFailure(jobId, DateTimeOffset.UtcNow, "Database connection failed", nextRun);
|
||||
|
||||
Assert.NotNull(updated.LastRunAt);
|
||||
Assert.Equal(jobId, updated.LastJobId);
|
||||
@@ -176,9 +176,9 @@ public sealed class ExportScheduleTests
|
||||
exportType: "export.sbom",
|
||||
cronExpression: "0 0 * * *",
|
||||
payloadTemplate: payload,
|
||||
createdBy: "admin@example.com");
|
||||
createdBy: "admin@example.com", timestamp: DateTimeOffset.UtcNow);
|
||||
|
||||
var updated = schedule.RecordFailure(Guid.NewGuid());
|
||||
var updated = schedule.RecordFailure(Guid.NewGuid(), DateTimeOffset.UtcNow);
|
||||
|
||||
Assert.Equal("failed: unknown", updated.LastRunStatus);
|
||||
}
|
||||
@@ -193,15 +193,15 @@ public sealed class ExportScheduleTests
|
||||
exportType: "export.sbom",
|
||||
cronExpression: "0 0 * * *",
|
||||
payloadTemplate: payload,
|
||||
createdBy: "admin@example.com");
|
||||
createdBy: "admin@example.com", timestamp: DateTimeOffset.UtcNow);
|
||||
|
||||
Assert.Equal(0, schedule.SuccessRate); // No runs
|
||||
|
||||
var updated = schedule
|
||||
.RecordSuccess(Guid.NewGuid())
|
||||
.RecordSuccess(Guid.NewGuid())
|
||||
.RecordSuccess(Guid.NewGuid())
|
||||
.RecordFailure(Guid.NewGuid());
|
||||
.RecordSuccess(Guid.NewGuid(), DateTimeOffset.UtcNow)
|
||||
.RecordSuccess(Guid.NewGuid(), DateTimeOffset.UtcNow)
|
||||
.RecordSuccess(Guid.NewGuid(), DateTimeOffset.UtcNow)
|
||||
.RecordFailure(Guid.NewGuid(), DateTimeOffset.UtcNow);
|
||||
|
||||
Assert.Equal(75.0, updated.SuccessRate);
|
||||
}
|
||||
@@ -216,10 +216,10 @@ public sealed class ExportScheduleTests
|
||||
exportType: "export.sbom",
|
||||
cronExpression: "0 0 * * *",
|
||||
payloadTemplate: payload,
|
||||
createdBy: "admin@example.com");
|
||||
createdBy: "admin@example.com", timestamp: DateTimeOffset.UtcNow);
|
||||
|
||||
var nextRun = DateTimeOffset.UtcNow.AddHours(6);
|
||||
var updated = schedule.WithNextRun(nextRun);
|
||||
var updated = schedule.WithNextRun(nextRun, DateTimeOffset.UtcNow);
|
||||
|
||||
Assert.Equal(nextRun, updated.NextRunAt);
|
||||
}
|
||||
@@ -234,9 +234,9 @@ public sealed class ExportScheduleTests
|
||||
exportType: "export.sbom",
|
||||
cronExpression: "0 0 * * *",
|
||||
payloadTemplate: payload,
|
||||
createdBy: "admin@example.com");
|
||||
createdBy: "admin@example.com", timestamp: DateTimeOffset.UtcNow);
|
||||
|
||||
var updated = schedule.WithCron("0 */6 * * *", "scheduler@example.com");
|
||||
var updated = schedule.WithCron("0 */6 * * *", "scheduler@example.com", DateTimeOffset.UtcNow);
|
||||
|
||||
Assert.Equal("0 */6 * * *", updated.CronExpression);
|
||||
Assert.Equal("scheduler@example.com", updated.UpdatedBy);
|
||||
@@ -252,11 +252,11 @@ public sealed class ExportScheduleTests
|
||||
exportType: "export.sbom",
|
||||
cronExpression: "0 0 * * *",
|
||||
payloadTemplate: payload,
|
||||
createdBy: "admin@example.com");
|
||||
createdBy: "admin@example.com", timestamp: DateTimeOffset.UtcNow);
|
||||
|
||||
var newPayload = ExportJobPayload.Default("ndjson") with { ProjectId = "project-2" };
|
||||
|
||||
var updated = schedule.WithPayload(newPayload, "editor@example.com");
|
||||
var updated = schedule.WithPayload(newPayload, "editor@example.com", DateTimeOffset.UtcNow);
|
||||
|
||||
Assert.Equal("project-2", updated.PayloadTemplate.ProjectId);
|
||||
Assert.Equal("ndjson", updated.PayloadTemplate.Format);
|
||||
@@ -273,7 +273,7 @@ public sealed class RetentionPruneConfigTests
|
||||
public void Create_CreatesConfigWithDefaults()
|
||||
{
|
||||
var before = DateTimeOffset.UtcNow;
|
||||
var config = RetentionPruneConfig.Create();
|
||||
var config = RetentionPruneConfig.Create(DateTimeOffset.UtcNow);
|
||||
var after = DateTimeOffset.UtcNow;
|
||||
|
||||
Assert.NotEqual(Guid.Empty, config.PruneId);
|
||||
@@ -295,8 +295,7 @@ public sealed class RetentionPruneConfigTests
|
||||
[Fact]
|
||||
public void Create_AcceptsOptionalParameters()
|
||||
{
|
||||
var config = RetentionPruneConfig.Create(
|
||||
tenantId: "tenant-1",
|
||||
var config = RetentionPruneConfig.Create(timestamp: DateTimeOffset.UtcNow, tenantId: "tenant-1",
|
||||
exportType: "export.sbom",
|
||||
cronExpression: "0 3 * * *",
|
||||
batchSize: 50);
|
||||
@@ -322,10 +321,10 @@ public sealed class RetentionPruneConfigTests
|
||||
[Fact]
|
||||
public void RecordPrune_UpdatesStatistics()
|
||||
{
|
||||
var config = RetentionPruneConfig.Create();
|
||||
var config = RetentionPruneConfig.Create(DateTimeOffset.UtcNow);
|
||||
var before = DateTimeOffset.UtcNow;
|
||||
|
||||
var updated = config.RecordPrune(25);
|
||||
var updated = config.RecordPrune(25, DateTimeOffset.UtcNow);
|
||||
|
||||
Assert.NotNull(updated.LastPruneAt);
|
||||
Assert.True(updated.LastPruneAt >= before);
|
||||
@@ -336,12 +335,12 @@ public sealed class RetentionPruneConfigTests
|
||||
[Fact]
|
||||
public void RecordPrune_AccumulatesTotal()
|
||||
{
|
||||
var config = RetentionPruneConfig.Create();
|
||||
var config = RetentionPruneConfig.Create(DateTimeOffset.UtcNow);
|
||||
|
||||
var updated = config
|
||||
.RecordPrune(10)
|
||||
.RecordPrune(15)
|
||||
.RecordPrune(20);
|
||||
.RecordPrune(10, DateTimeOffset.UtcNow)
|
||||
.RecordPrune(15, DateTimeOffset.UtcNow)
|
||||
.RecordPrune(20, DateTimeOffset.UtcNow);
|
||||
|
||||
Assert.Equal(20, updated.LastPruneCount);
|
||||
Assert.Equal(45, updated.TotalPruned);
|
||||
@@ -360,7 +359,7 @@ public sealed class ExportAlertConfigTests
|
||||
|
||||
var config = ExportAlertConfig.Create(
|
||||
tenantId: "tenant-1",
|
||||
name: "SBOM Export Failures");
|
||||
name: "SBOM Export Failures", timestamp: DateTimeOffset.UtcNow);
|
||||
|
||||
var after = DateTimeOffset.UtcNow;
|
||||
|
||||
@@ -386,7 +385,7 @@ public sealed class ExportAlertConfigTests
|
||||
var config = ExportAlertConfig.Create(
|
||||
tenantId: "tenant-1",
|
||||
name: "Critical Export Failures",
|
||||
exportType: "export.report",
|
||||
timestamp: DateTimeOffset.UtcNow, exportType: "export.report",
|
||||
consecutiveFailuresThreshold: 5,
|
||||
failureRateThreshold: 25.0,
|
||||
severity: ExportAlertSeverity.Critical);
|
||||
@@ -402,9 +401,9 @@ public sealed class ExportAlertConfigTests
|
||||
{
|
||||
var config = ExportAlertConfig.Create(
|
||||
tenantId: "tenant-1",
|
||||
name: "Test Alert");
|
||||
name: "Test Alert", timestamp: DateTimeOffset.UtcNow);
|
||||
|
||||
Assert.True(config.CanAlert);
|
||||
Assert.True(config.CanAlertAt(DateTimeOffset.UtcNow));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -412,11 +411,11 @@ public sealed class ExportAlertConfigTests
|
||||
{
|
||||
var config = ExportAlertConfig.Create(
|
||||
tenantId: "tenant-1",
|
||||
name: "Test Alert");
|
||||
name: "Test Alert", timestamp: DateTimeOffset.UtcNow);
|
||||
|
||||
var alerted = config.RecordAlert();
|
||||
var alerted = config.RecordAlert(DateTimeOffset.UtcNow);
|
||||
|
||||
Assert.False(alerted.CanAlert);
|
||||
Assert.False(alerted.CanAlertAt(DateTimeOffset.UtcNow));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -439,7 +438,7 @@ public sealed class ExportAlertConfigTests
|
||||
CreatedAt: DateTimeOffset.UtcNow.AddDays(-1),
|
||||
UpdatedAt: DateTimeOffset.UtcNow.AddMinutes(-20));
|
||||
|
||||
Assert.True(config.CanAlert);
|
||||
Assert.True(config.CanAlertAt(DateTimeOffset.UtcNow));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -447,10 +446,10 @@ public sealed class ExportAlertConfigTests
|
||||
{
|
||||
var config = ExportAlertConfig.Create(
|
||||
tenantId: "tenant-1",
|
||||
name: "Test Alert");
|
||||
name: "Test Alert", timestamp: DateTimeOffset.UtcNow);
|
||||
|
||||
var before = DateTimeOffset.UtcNow;
|
||||
var updated = config.RecordAlert();
|
||||
var updated = config.RecordAlert(DateTimeOffset.UtcNow);
|
||||
var after = DateTimeOffset.UtcNow;
|
||||
|
||||
Assert.NotNull(updated.LastAlertAt);
|
||||
@@ -463,7 +462,7 @@ public sealed class ExportAlertConfigTests
|
||||
{
|
||||
var config = ExportAlertConfig.Create(
|
||||
tenantId: "tenant-1",
|
||||
name: "Test Alert");
|
||||
name: "Test Alert", timestamp: DateTimeOffset.UtcNow);
|
||||
|
||||
// Simulate multiple alerts with cooldown passage
|
||||
var updated = config with
|
||||
@@ -472,7 +471,7 @@ public sealed class ExportAlertConfigTests
|
||||
TotalAlerts = 5
|
||||
};
|
||||
|
||||
var alerted = updated.RecordAlert();
|
||||
var alerted = updated.RecordAlert(DateTimeOffset.UtcNow);
|
||||
Assert.Equal(6, alerted.TotalAlerts);
|
||||
}
|
||||
}
|
||||
@@ -711,3 +710,9 @@ public sealed class RetentionPruneResultTests
|
||||
Assert.False(empty.HasErrors);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -32,8 +32,7 @@ public class IncidentModeHooksTests
|
||||
};
|
||||
|
||||
_sut = new IncidentModeHooks(
|
||||
_testEmitter,
|
||||
NullLogger<IncidentModeHooks>.Instance,
|
||||
_testEmitter, TimeProvider.System, NullLogger<IncidentModeHooks>.Instance,
|
||||
_options);
|
||||
}
|
||||
|
||||
@@ -41,14 +40,14 @@ public class IncidentModeHooksTests
|
||||
public void Constructor_WithNullEmitter_ThrowsArgumentNullException()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
new IncidentModeHooks(null!, NullLogger<IncidentModeHooks>.Instance));
|
||||
new IncidentModeHooks(null!, TimeProvider.System, NullLogger<IncidentModeHooks>.Instance));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithNullLogger_ThrowsArgumentNullException()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
new IncidentModeHooks(_testEmitter, null!));
|
||||
new IncidentModeHooks(_testEmitter, TimeProvider.System, null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -540,3 +539,6 @@ internal sealed class TestIncidentModeEmitter : ITimelineEventEmitter
|
||||
|
||||
public void Clear() => _emittedEvents.Clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ public sealed class LoadShedderTests
|
||||
WarningThreshold = 0.1, // Very low threshold for testing
|
||||
WarningPriorityThreshold = 5
|
||||
};
|
||||
var shedder = new LoadShedder(metrics, options);
|
||||
var shedder = new LoadShedder(metrics, TimeProvider.System, options);
|
||||
|
||||
// Simulate load to trigger warning state
|
||||
for (var i = 0; i < 100; i++)
|
||||
@@ -174,7 +174,7 @@ public sealed class LoadShedderTests
|
||||
EmergencyThreshold = 1.5,
|
||||
RecoveryCooldown = TimeSpan.Zero // Disable cooldown for testing
|
||||
};
|
||||
var shedder = new LoadShedder(metrics, options);
|
||||
var shedder = new LoadShedder(metrics, TimeProvider.System, options);
|
||||
|
||||
// Set up metrics to achieve target load factor
|
||||
// Load factor = 0.6 * latencyFactor + 0.4 * queueFactor
|
||||
@@ -206,7 +206,7 @@ public sealed class LoadShedderTests
|
||||
{
|
||||
RecoveryCooldown = TimeSpan.FromSeconds(30)
|
||||
};
|
||||
var shedder = new LoadShedder(metrics, options);
|
||||
var shedder = new LoadShedder(metrics, TimeProvider.System, options);
|
||||
|
||||
// Force emergency state
|
||||
shedder.SetState(LoadShedState.Emergency);
|
||||
@@ -241,3 +241,7 @@ public sealed class LoadShedderTests
|
||||
Assert.Equal(0, status.AcceptingPriority); // Normal state accepts all
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -310,7 +310,7 @@ public class AlertBudgetThresholdTests
|
||||
TenantId,
|
||||
budgetConsumedThreshold: 0.5,
|
||||
severity: AlertSeverity.Warning,
|
||||
createdBy: "admin");
|
||||
createdBy: "admin", createdAt: DateTimeOffset.UtcNow);
|
||||
|
||||
Assert.NotEqual(Guid.Empty, threshold.ThresholdId);
|
||||
Assert.Equal(sloId, threshold.SloId);
|
||||
@@ -330,7 +330,7 @@ public class AlertBudgetThresholdTests
|
||||
TenantId,
|
||||
0.8,
|
||||
AlertSeverity.Critical,
|
||||
"admin",
|
||||
"admin", DateTimeOffset.UtcNow,
|
||||
burnRateThreshold: 5.0);
|
||||
|
||||
Assert.Equal(5.0, threshold.BurnRateThreshold);
|
||||
@@ -344,7 +344,7 @@ public class AlertBudgetThresholdTests
|
||||
TenantId,
|
||||
0.5,
|
||||
AlertSeverity.Warning,
|
||||
"admin",
|
||||
"admin", DateTimeOffset.UtcNow,
|
||||
cooldown: TimeSpan.FromMinutes(30));
|
||||
|
||||
Assert.Equal(TimeSpan.FromMinutes(30), threshold.Cooldown);
|
||||
@@ -356,14 +356,13 @@ public class AlertBudgetThresholdTests
|
||||
public void Create_WithInvalidThreshold_Throws(double threshold)
|
||||
{
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
||||
AlertBudgetThreshold.Create(Guid.NewGuid(), TenantId, threshold, AlertSeverity.Warning, "admin"));
|
||||
AlertBudgetThreshold.Create(Guid.NewGuid(), TenantId, threshold, AlertSeverity.Warning, "admin", DateTimeOffset.UtcNow));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldTrigger_WhenDisabled_ReturnsFalse()
|
||||
{
|
||||
var threshold = AlertBudgetThreshold.Create(
|
||||
Guid.NewGuid(), TenantId, 0.5, AlertSeverity.Warning, "admin")
|
||||
var threshold = AlertBudgetThreshold.Create(Guid.NewGuid(), TenantId, 0.5, AlertSeverity.Warning, "admin", DateTimeOffset.UtcNow)
|
||||
with { Enabled = false };
|
||||
|
||||
var state = CreateTestState(budgetConsumed: 0.6);
|
||||
@@ -374,8 +373,7 @@ public class AlertBudgetThresholdTests
|
||||
[Fact]
|
||||
public void ShouldTrigger_WhenBudgetExceedsThreshold_ReturnsTrue()
|
||||
{
|
||||
var threshold = AlertBudgetThreshold.Create(
|
||||
Guid.NewGuid(), TenantId, 0.5, AlertSeverity.Warning, "admin");
|
||||
var threshold = AlertBudgetThreshold.Create(Guid.NewGuid(), TenantId, 0.5, AlertSeverity.Warning, "admin", DateTimeOffset.UtcNow);
|
||||
|
||||
var state = CreateTestState(budgetConsumed: 0.6);
|
||||
|
||||
@@ -385,8 +383,7 @@ public class AlertBudgetThresholdTests
|
||||
[Fact]
|
||||
public void ShouldTrigger_WhenBudgetBelowThreshold_ReturnsFalse()
|
||||
{
|
||||
var threshold = AlertBudgetThreshold.Create(
|
||||
Guid.NewGuid(), TenantId, 0.5, AlertSeverity.Warning, "admin");
|
||||
var threshold = AlertBudgetThreshold.Create(Guid.NewGuid(), TenantId, 0.5, AlertSeverity.Warning, "admin", DateTimeOffset.UtcNow);
|
||||
|
||||
var state = CreateTestState(budgetConsumed: 0.3);
|
||||
|
||||
@@ -397,7 +394,7 @@ public class AlertBudgetThresholdTests
|
||||
public void ShouldTrigger_WhenBurnRateExceedsThreshold_ReturnsTrue()
|
||||
{
|
||||
var threshold = AlertBudgetThreshold.Create(
|
||||
Guid.NewGuid(), TenantId, 0.9, AlertSeverity.Critical, "admin",
|
||||
Guid.NewGuid(), TenantId, 0.9, AlertSeverity.Critical, "admin", DateTimeOffset.UtcNow,
|
||||
burnRateThreshold: 3.0);
|
||||
|
||||
var state = CreateTestState(budgetConsumed: 0.3, burnRate: 4.0);
|
||||
@@ -408,8 +405,7 @@ public class AlertBudgetThresholdTests
|
||||
[Fact]
|
||||
public void ShouldTrigger_WhenWithinCooldown_ReturnsFalse()
|
||||
{
|
||||
var threshold = AlertBudgetThreshold.Create(
|
||||
Guid.NewGuid(), TenantId, 0.5, AlertSeverity.Warning, "admin")
|
||||
var threshold = AlertBudgetThreshold.Create(Guid.NewGuid(), TenantId, 0.5, AlertSeverity.Warning, "admin", DateTimeOffset.UtcNow)
|
||||
with { LastTriggeredAt = BaseTime, Cooldown = TimeSpan.FromHours(1) };
|
||||
|
||||
var state = CreateTestState(budgetConsumed: 0.6);
|
||||
@@ -420,8 +416,7 @@ public class AlertBudgetThresholdTests
|
||||
[Fact]
|
||||
public void ShouldTrigger_WhenCooldownExpired_ReturnsTrue()
|
||||
{
|
||||
var threshold = AlertBudgetThreshold.Create(
|
||||
Guid.NewGuid(), TenantId, 0.5, AlertSeverity.Warning, "admin")
|
||||
var threshold = AlertBudgetThreshold.Create(Guid.NewGuid(), TenantId, 0.5, AlertSeverity.Warning, "admin", DateTimeOffset.UtcNow)
|
||||
with { LastTriggeredAt = BaseTime, Cooldown = TimeSpan.FromHours(1) };
|
||||
|
||||
var state = CreateTestState(budgetConsumed: 0.6);
|
||||
@@ -432,8 +427,7 @@ public class AlertBudgetThresholdTests
|
||||
[Fact]
|
||||
public void RecordTrigger_UpdatesLastTriggeredAt()
|
||||
{
|
||||
var threshold = AlertBudgetThreshold.Create(
|
||||
Guid.NewGuid(), TenantId, 0.5, AlertSeverity.Warning, "admin");
|
||||
var threshold = AlertBudgetThreshold.Create(Guid.NewGuid(), TenantId, 0.5, AlertSeverity.Warning, "admin", DateTimeOffset.UtcNow);
|
||||
|
||||
var updated = threshold.RecordTrigger(BaseTime);
|
||||
|
||||
@@ -468,9 +462,9 @@ public class SloAlertTests
|
||||
[Fact]
|
||||
public void Create_FromSloAndState_CreatesAlert()
|
||||
{
|
||||
var slo = Slo.CreateAvailability(TenantId, "API Availability", 0.999, SloWindow.ThirtyDays, "admin");
|
||||
var slo = Slo.CreateAvailability(TenantId, "API Availability", 0.999, SloWindow.ThirtyDays, "admin", DateTimeOffset.UtcNow);
|
||||
var state = CreateTestState(slo.SloId, budgetConsumed: 0.8);
|
||||
var threshold = AlertBudgetThreshold.Create(slo.SloId, TenantId, 0.5, AlertSeverity.Warning, "admin");
|
||||
var threshold = AlertBudgetThreshold.Create(slo.SloId, TenantId, 0.5, AlertSeverity.Warning, "admin", DateTimeOffset.UtcNow);
|
||||
|
||||
var alert = SloAlert.Create(slo, state, threshold);
|
||||
|
||||
@@ -488,9 +482,9 @@ public class SloAlertTests
|
||||
[Fact]
|
||||
public void Create_WithBurnRateTrigger_IncludesBurnRateInMessage()
|
||||
{
|
||||
var slo = Slo.CreateAvailability(TenantId, "Test SLO", 0.99, SloWindow.OneDay, "admin");
|
||||
var slo = Slo.CreateAvailability(TenantId, "Test SLO", 0.99, SloWindow.OneDay, "admin", DateTimeOffset.UtcNow);
|
||||
var state = CreateTestState(slo.SloId, budgetConsumed: 0.3, burnRate: 6.0);
|
||||
var threshold = AlertBudgetThreshold.Create(slo.SloId, TenantId, 0.9, AlertSeverity.Critical, "admin",
|
||||
var threshold = AlertBudgetThreshold.Create(slo.SloId, TenantId, 0.9, AlertSeverity.Critical, "admin", DateTimeOffset.UtcNow,
|
||||
burnRateThreshold: 5.0);
|
||||
|
||||
var alert = SloAlert.Create(slo, state, threshold);
|
||||
@@ -526,9 +520,9 @@ public class SloAlertTests
|
||||
|
||||
private static SloAlert CreateTestAlert()
|
||||
{
|
||||
var slo = Slo.CreateAvailability(TenantId, "Test SLO", 0.99, SloWindow.OneDay, "admin");
|
||||
var slo = Slo.CreateAvailability(TenantId, "Test SLO", 0.99, SloWindow.OneDay, "admin", DateTimeOffset.UtcNow);
|
||||
var state = CreateTestState(slo.SloId, budgetConsumed: 0.6);
|
||||
var threshold = AlertBudgetThreshold.Create(slo.SloId, TenantId, 0.5, AlertSeverity.Warning, "admin");
|
||||
var threshold = AlertBudgetThreshold.Create(slo.SloId, TenantId, 0.5, AlertSeverity.Warning, "admin", DateTimeOffset.UtcNow);
|
||||
return SloAlert.Create(slo, state, threshold);
|
||||
}
|
||||
|
||||
@@ -550,3 +544,6 @@ public class SloAlertTests
|
||||
WindowStart: BaseTime.AddDays(-1),
|
||||
WindowEnd: BaseTime);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# StellaOps.Orchestrator.Tests Task Board
|
||||
|
||||
This board mirrors active sprint tasks for this module.
|
||||
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
|
||||
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
|
||||
|
||||
| Task ID | Status | Notes |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# StellaOps.Orchestrator.WebService Task Board
|
||||
|
||||
This board mirrors active sprint tasks for this module.
|
||||
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
|
||||
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
|
||||
|
||||
| Task ID | Status | Notes |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# StellaOps.Orchestrator.Worker Task Board
|
||||
|
||||
This board mirrors active sprint tasks for this module.
|
||||
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
|
||||
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
|
||||
|
||||
| Task ID | Status | Notes |
|
||||
| --- | --- | --- |
|
||||
|
||||
Reference in New Issue
Block a user