Files
git.stella-ops.org/src/Policy/__Tests/StellaOps.Policy.Engine.Tests/PolicyDecisionServiceTests.cs

219 lines
7.7 KiB
C#

using Xunit;
using Microsoft.Extensions.Time.Testing;
using StellaOps.Policy.Engine.Domain;
using StellaOps.Policy.Engine.Ledger;
using StellaOps.Policy.Engine.Orchestration;
using StellaOps.Policy.Engine.Services;
using StellaOps.Policy.Engine.Snapshots;
using StellaOps.Policy.Engine.TrustWeighting;
using StellaOps.Policy.Engine.Violations;
using StellaOps.TestKit;
namespace StellaOps.Policy.Engine.Tests;
public sealed class PolicyDecisionServiceTests
{
private static (PolicyDecisionService service, string snapshotId) BuildService()
{
var clock = new FakeTimeProvider(DateTimeOffset.Parse("2025-11-27T10: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 evidenceService = new EvidenceSummaryService(clock);
var decisionService = new PolicyDecisionService(
eventService,
fusionService,
conflictService,
evidenceService);
// Setup test data
var job = new OrchestratorJob(
JobId: "job-decision-test",
TenantId: "acme",
ContextId: "ctx",
PolicyProfileHash: "hash",
RequestedAt: clock.GetUtcNow(),
Priority: "normal",
BatchItems: new[]
{
new OrchestratorJobItem("pkg:npm/lodash@4.17.21", "CVE-2021-23337"),
new OrchestratorJobItem("pkg:npm/axios@0.21.1", "CVE-2021-3749"),
new OrchestratorJobItem("pkg:maven/log4j@2.14.1", "CVE-2021-44228")
},
Callbacks: null,
TraceRef: "trace-decision",
Status: "completed",
DeterminismHash: "hash",
CompletedAt: clock.GetUtcNow(),
ResultHash: "res");
jobStore.SaveAsync(job).GetAwaiter().GetResult();
resultStore.SaveAsync(new WorkerRunResult(
job.JobId,
"worker-decision",
clock.GetUtcNow(),
clock.GetUtcNow(),
new[]
{
new WorkerResultItem("pkg:npm/lodash@4.17.21", "CVE-2021-23337", "violation", "trace-lodash"),
new WorkerResultItem("pkg:npm/axios@0.21.1", "CVE-2021-3749", "warn", "trace-axios"),
new WorkerResultItem("pkg:maven/log4j@2.14.1", "CVE-2021-44228", "violation", "trace-log4j")
},
"hash")).GetAwaiter().GetResult();
ledger.BuildAsync(new LedgerExportRequest("acme")).GetAwaiter().GetResult();
var snapshot = snapshotService.CreateAsync(new SnapshotRequest("acme", "overlay-decision")).GetAwaiter().GetResult();
return (decisionService, snapshot.SnapshotId);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetDecisionsAsync_ReturnsDecisionsWithEvidence()
{
var (service, snapshotId) = BuildService();
var request = new PolicyDecisionRequest(snapshotId);
var response = await service.GetDecisionsAsync(request);
Assert.Equal(snapshotId, response.SnapshotId);
Assert.Equal(3, response.Decisions.Count);
Assert.All(response.Decisions, d =>
{
Assert.False(string.IsNullOrWhiteSpace(d.SeverityFused));
Assert.NotNull(d.Evidence);
Assert.NotNull(d.TopSources);
Assert.True(d.TopSources.Count > 0);
});
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetDecisionsAsync_BuildsSummaryStatistics()
{
var (service, snapshotId) = BuildService();
var request = new PolicyDecisionRequest(snapshotId);
var response = await service.GetDecisionsAsync(request);
Assert.Equal(3, response.Summary.TotalDecisions);
Assert.NotEmpty(response.Summary.SeverityCounts);
Assert.NotEmpty(response.Summary.TopSeveritySources);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetDecisionsAsync_FiltersById()
{
var (service, snapshotId) = BuildService();
var request = new PolicyDecisionRequest(
SnapshotId: snapshotId,
AdvisoryId: "CVE-2021-44228");
var response = await service.GetDecisionsAsync(request);
Assert.Single(response.Decisions);
Assert.Equal("CVE-2021-44228", response.Decisions[0].AdvisoryId);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetDecisionsAsync_FiltersByTenant()
{
var (service, snapshotId) = BuildService();
var request = new PolicyDecisionRequest(
SnapshotId: snapshotId,
TenantId: "acme");
var response = await service.GetDecisionsAsync(request);
Assert.All(response.Decisions, d => Assert.Equal("acme", d.TenantId));
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetDecisionsAsync_LimitsTopSources()
{
var (service, snapshotId) = BuildService();
var request = new PolicyDecisionRequest(
SnapshotId: snapshotId,
MaxSources: 1);
var response = await service.GetDecisionsAsync(request);
Assert.All(response.Decisions, d =>
{
Assert.True(d.TopSources.Count <= 1);
});
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetDecisionsAsync_ExcludesEvidenceWhenNotRequested()
{
var (service, snapshotId) = BuildService();
var request = new PolicyDecisionRequest(
SnapshotId: snapshotId,
IncludeEvidence: false);
var response = await service.GetDecisionsAsync(request);
Assert.All(response.Decisions, d => Assert.Null(d.Evidence));
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetDecisionsAsync_ReturnsDeterministicOrder()
{
var (service, snapshotId) = BuildService();
var request = new PolicyDecisionRequest(snapshotId);
var response1 = await service.GetDecisionsAsync(request);
var response2 = await service.GetDecisionsAsync(request);
Assert.Equal(
response1.Decisions.Select(d => d.ComponentPurl),
response2.Decisions.Select(d => d.ComponentPurl));
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetDecisionsAsync_ThrowsOnEmptySnapshotId()
{
var (service, _) = BuildService();
var request = new PolicyDecisionRequest(string.Empty);
await Assert.ThrowsAsync<ArgumentException>(() => service.GetDecisionsAsync(request));
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetDecisionsAsync_TopSourcesHaveRanks()
{
var (service, snapshotId) = BuildService();
var request = new PolicyDecisionRequest(snapshotId);
var response = await service.GetDecisionsAsync(request);
foreach (var decision in response.Decisions)
{
for (var i = 0; i < decision.TopSources.Count; i++)
{
Assert.Equal(i + 1, decision.TopSources[i].Rank);
}
}
}
}