101 lines
3.9 KiB
C#
101 lines
3.9 KiB
C#
using Xunit;
|
|
using Microsoft.Extensions.Time.Testing;
|
|
using StellaOps.Policy.Engine.Ledger;
|
|
using StellaOps.Policy.Engine.Orchestration;
|
|
using StellaOps.Policy.Engine.Snapshots;
|
|
using StellaOps.Policy.Engine.TrustWeighting;
|
|
using StellaOps.Policy.Engine.Violations;
|
|
|
|
namespace StellaOps.Policy.Engine.Tests;
|
|
|
|
public sealed class ViolationServicesTests
|
|
{
|
|
private static (ViolationEventService events, SeverityFusionService fusion, ConflictHandlingService conflicts, string snapshotId) BuildPipeline()
|
|
{
|
|
var clock = new FakeTimeProvider(DateTimeOffset.Parse("2025-11-24T17:00:00Z"));
|
|
|
|
var jobStore = new InMemoryOrchestratorJobStore();
|
|
var resultStore = new InMemoryWorkerResultStore(jobStore);
|
|
var exportStore = new InMemoryLedgerExportStore();
|
|
var ledger = new LedgerExportService(clock, jobStore, resultStore, exportStore);
|
|
var snapshotStore = new InMemorySnapshotStore();
|
|
var violationStore = new InMemoryViolationEventStore();
|
|
var trust = new TrustWeightingService(clock);
|
|
|
|
var snapshotService = new SnapshotService(clock, ledger, snapshotStore);
|
|
var eventService = new ViolationEventService(snapshotStore, jobStore, violationStore);
|
|
var fusionService = new SeverityFusionService(violationStore, trust);
|
|
var conflictService = new ConflictHandlingService(violationStore);
|
|
|
|
var job = new OrchestratorJob(
|
|
JobId: "job-viol",
|
|
TenantId: "acme",
|
|
ContextId: "ctx",
|
|
PolicyProfileHash: "hash",
|
|
RequestedAt: clock.GetUtcNow(),
|
|
Priority: "normal",
|
|
BatchItems: new[] { new OrchestratorJobItem("pkg:a", "ADV-1"), new OrchestratorJobItem("pkg:b", "ADV-2") },
|
|
Callbacks: null,
|
|
TraceRef: "trace",
|
|
Status: "completed",
|
|
DeterminismHash: "hash",
|
|
CompletedAt: clock.GetUtcNow(),
|
|
ResultHash: "res");
|
|
|
|
jobStore.SaveAsync(job).GetAwaiter().GetResult();
|
|
|
|
resultStore.SaveAsync(new WorkerRunResult(
|
|
job.JobId,
|
|
"worker",
|
|
clock.GetUtcNow(),
|
|
clock.GetUtcNow(),
|
|
new[]
|
|
{
|
|
new WorkerResultItem("pkg:a", "ADV-1", "violation", "trace-a"),
|
|
new WorkerResultItem("pkg:b", "ADV-2", "warn", "trace-b")
|
|
},
|
|
"hash")).GetAwaiter().GetResult();
|
|
|
|
ledger.BuildAsync(new LedgerExportRequest("acme")).GetAwaiter().GetResult();
|
|
var snapshot = snapshotService.CreateAsync(new SnapshotRequest("acme", "overlay-1")).GetAwaiter().GetResult();
|
|
|
|
return (eventService, fusionService, conflictService, snapshot.SnapshotId);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task EmitAsync_BuildsEvents()
|
|
{
|
|
var (eventService, _, _, snapshotId) = BuildPipeline();
|
|
|
|
var events = await eventService.EmitAsync(new ViolationEventRequest(snapshotId));
|
|
|
|
Assert.Equal(2, events.Count);
|
|
Assert.All(events, e => Assert.Equal("policy.violation.detected", e.ViolationCode));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task FuseAsync_ProducesWeightedSeverity()
|
|
{
|
|
var (eventService, fusionService, _, snapshotId) = BuildPipeline();
|
|
|
|
await eventService.EmitAsync(new ViolationEventRequest(snapshotId));
|
|
var fused = await fusionService.FuseAsync(snapshotId);
|
|
|
|
Assert.Equal(2, fused.Count);
|
|
Assert.All(fused, f => Assert.False(string.IsNullOrWhiteSpace(f.SeverityFused)));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ConflictsAsync_DetectsDivergentSeverities()
|
|
{
|
|
var (eventService, fusionService, conflictService, snapshotId) = BuildPipeline();
|
|
await eventService.EmitAsync(new ViolationEventRequest(snapshotId));
|
|
var fused = await fusionService.FuseAsync(snapshotId);
|
|
|
|
var conflicts = await conflictService.ComputeAsync(snapshotId, fused);
|
|
|
|
// Only triggers when severities differ; in this stub they do, so expect at least one.
|
|
Assert.NotNull(conflicts);
|
|
}
|
|
}
|