126 lines
3.9 KiB
C#
126 lines
3.9 KiB
C#
using System.Diagnostics;
|
|
using System.Text.Json.Nodes;
|
|
using FluentAssertions;
|
|
using StellaOps.Findings.Ledger.Domain;
|
|
using StellaOps.Findings.Ledger.Observability;
|
|
using StellaOps.Findings.Ledger.Services.Incident;
|
|
using Xunit;
|
|
|
|
namespace StellaOps.Findings.Ledger.Tests.Observability;
|
|
|
|
public class LedgerTimelineTests
|
|
{
|
|
[Fact]
|
|
public void EmitLedgerAppended_writes_structured_log_with_event_id()
|
|
{
|
|
var logger = new TestLogger<LedgerTimelineTests>();
|
|
using var activity = new Activity("test").Start();
|
|
var record = CreateRecord();
|
|
|
|
LedgerTimeline.EmitLedgerAppended(logger, record, "evidence-123");
|
|
|
|
logger.Entries.Should().HaveCount(1);
|
|
var entry = logger.Entries.First();
|
|
entry.EventId.Name.Should().Be("ledger.event.appended");
|
|
entry.EventId.Id.Should().Be(6101);
|
|
|
|
var state = AsDictionary(entry.State);
|
|
state["Tenant"].Should().Be(record.TenantId);
|
|
state["EvidenceRef"].Should().Be("evidence-123");
|
|
}
|
|
|
|
[Fact]
|
|
public void EmitProjectionUpdated_writes_structured_log_with_status()
|
|
{
|
|
var logger = new TestLogger<LedgerTimelineTests>();
|
|
using var activity = new Activity("test").Start();
|
|
var record = CreateRecord();
|
|
|
|
LedgerTimeline.EmitProjectionUpdated(logger, record, "affected");
|
|
|
|
var entry = logger.Entries.Single();
|
|
entry.EventId.Name.Should().Be("ledger.projection.updated");
|
|
entry.EventId.Id.Should().Be(6201);
|
|
|
|
var state = AsDictionary(entry.State);
|
|
state["Status"].Should().Be("affected");
|
|
}
|
|
|
|
[Fact]
|
|
public void EmitIncidentModeChanged_writes_structured_log()
|
|
{
|
|
var logger = new TestLogger<LedgerTimelineTests>();
|
|
var snapshot = new LedgerIncidentSnapshot(
|
|
IsActive: true,
|
|
ActivationId: "act-123",
|
|
Actor: "actor-1",
|
|
Reason: "reason",
|
|
TenantId: "tenant-a",
|
|
ChangedAt: DateTimeOffset.UtcNow,
|
|
ExpiresAt: DateTimeOffset.UtcNow.AddMinutes(10),
|
|
RetentionExtensionDays: 30);
|
|
|
|
LedgerTimeline.EmitIncidentModeChanged(logger, snapshot, wasReactivation: false);
|
|
|
|
var entry = logger.Entries.Single(e => e.EventId.Id == 6901);
|
|
var state = AsDictionary(entry.State);
|
|
state["RetentionExtensionDays"].Should().Be(30);
|
|
state["ActivationId"].Should().Be("act-123");
|
|
}
|
|
|
|
[Fact]
|
|
public void EmitIncidentLagTrace_writes_structured_log()
|
|
{
|
|
var logger = new TestLogger<LedgerTimelineTests>();
|
|
var sample = new ProjectionLagSample(
|
|
"tenant-a",
|
|
Guid.NewGuid(),
|
|
10,
|
|
"finding.created",
|
|
"v1",
|
|
12.5,
|
|
DateTimeOffset.UtcNow.AddSeconds(-12),
|
|
DateTimeOffset.UtcNow);
|
|
|
|
LedgerTimeline.EmitIncidentLagTrace(logger, sample);
|
|
|
|
var entry = logger.Entries.Single(e => e.EventId.Id == 6902);
|
|
var state = AsDictionary(entry.State);
|
|
state["LagSeconds"].Should().Be(12.5);
|
|
}
|
|
|
|
private static LedgerEventRecord CreateRecord()
|
|
{
|
|
var payload = new JsonObject { ["status"] = "affected" };
|
|
return new LedgerEventRecord(
|
|
"tenant-a",
|
|
Guid.NewGuid(),
|
|
1,
|
|
Guid.NewGuid(),
|
|
"finding.status.changed",
|
|
"v1",
|
|
"finding-1",
|
|
"artifact-1",
|
|
null,
|
|
"actor-1",
|
|
"operator",
|
|
DateTimeOffset.UtcNow,
|
|
DateTimeOffset.UtcNow,
|
|
payload,
|
|
"hash-event",
|
|
"hash-prev",
|
|
"hash-leaf",
|
|
"canonical-json");
|
|
}
|
|
|
|
private static IDictionary<string, object?> AsDictionary(object? state)
|
|
{
|
|
if (state is not IEnumerable<KeyValuePair<string, object?>> pairs)
|
|
{
|
|
return new Dictionary<string, object?>();
|
|
}
|
|
|
|
return pairs.ToDictionary(k => k.Key, v => v.Value);
|
|
}
|
|
}
|