up
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,301 @@
|
||||
using System.Collections.Immutable;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Time.Testing;
|
||||
using StellaOps.Policy.Engine.Telemetry;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Policy.Engine.Tests.Telemetry;
|
||||
|
||||
public sealed class TelemetryTests
|
||||
{
|
||||
#region RuleHitTrace Tests
|
||||
|
||||
[Fact]
|
||||
public void RuleHitTrace_GetOrCreateTraceId_ReturnsValidId()
|
||||
{
|
||||
var traceId = RuleHitTrace.GetOrCreateTraceId();
|
||||
|
||||
traceId.Should().NotBeNullOrEmpty();
|
||||
traceId.Should().HaveLength(32); // 16 bytes = 32 hex chars
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RuleHitTrace_GetOrCreateSpanId_ReturnsValidId()
|
||||
{
|
||||
var spanId = RuleHitTrace.GetOrCreateSpanId();
|
||||
|
||||
spanId.Should().NotBeNullOrEmpty();
|
||||
spanId.Should().HaveLength(16); // 8 bytes = 16 hex chars
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RuleHitTrace_GetOrCreateTraceId_GeneratesUniqueIds()
|
||||
{
|
||||
var ids = Enumerable.Range(0, 100)
|
||||
.Select(_ => RuleHitTrace.GetOrCreateTraceId())
|
||||
.ToList();
|
||||
|
||||
ids.Distinct().Should().HaveCount(100);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region RuleHitTraceFactory Tests
|
||||
|
||||
[Fact]
|
||||
public void Create_ProducesValidTrace()
|
||||
{
|
||||
var timestamp = DateTimeOffset.UtcNow;
|
||||
var timeProvider = new FakeTimeProvider(timestamp);
|
||||
|
||||
var trace = RuleHitTraceFactory.Create(
|
||||
tenantId: "TENANT-1",
|
||||
policyId: "policy-1",
|
||||
policyVersion: 2,
|
||||
runId: "run-123",
|
||||
ruleName: "block-critical",
|
||||
rulePriority: 10,
|
||||
outcome: "deny",
|
||||
evaluationTimestamp: timestamp,
|
||||
timeProvider: timeProvider,
|
||||
ruleCategory: "severity",
|
||||
assignedSeverity: "Critical",
|
||||
componentPurl: "pkg:npm/lodash@4.17.21",
|
||||
advisoryId: "GHSA-test-001",
|
||||
vulnerabilityId: "CVE-2021-12345");
|
||||
|
||||
trace.TenantId.Should().Be("tenant-1"); // Normalized to lowercase
|
||||
trace.PolicyId.Should().Be("policy-1");
|
||||
trace.PolicyVersion.Should().Be(2);
|
||||
trace.RunId.Should().Be("run-123");
|
||||
trace.RuleName.Should().Be("block-critical");
|
||||
trace.RulePriority.Should().Be(10);
|
||||
trace.Outcome.Should().Be("deny");
|
||||
trace.RuleCategory.Should().Be("severity");
|
||||
trace.AssignedSeverity.Should().Be("Critical");
|
||||
trace.ComponentPurl.Should().Be("pkg:npm/lodash@4.17.21");
|
||||
trace.EvaluationTimestamp.Should().Be(timestamp);
|
||||
trace.RecordedAt.Should().Be(timestamp);
|
||||
trace.TraceId.Should().NotBeNullOrEmpty();
|
||||
trace.SpanId.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_TracksVexOverride()
|
||||
{
|
||||
var timestamp = DateTimeOffset.UtcNow;
|
||||
|
||||
var trace = RuleHitTraceFactory.Create(
|
||||
tenantId: "tenant-1",
|
||||
policyId: "policy-1",
|
||||
policyVersion: 1,
|
||||
runId: "run-123",
|
||||
ruleName: "vex-override",
|
||||
rulePriority: 1,
|
||||
outcome: "suppress",
|
||||
evaluationTimestamp: timestamp,
|
||||
vexStatus: "not_affected",
|
||||
vexJustification: "vulnerable_code_not_in_execute_path",
|
||||
vexVendor: "vendor-1",
|
||||
isVexOverride: true);
|
||||
|
||||
trace.VexStatus.Should().Be("not_affected");
|
||||
trace.VexJustification.Should().Be("vulnerable_code_not_in_execute_path");
|
||||
trace.VexVendor.Should().Be("vendor-1");
|
||||
trace.IsVexOverride.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_TracksReachability()
|
||||
{
|
||||
var timestamp = DateTimeOffset.UtcNow;
|
||||
|
||||
var trace = RuleHitTraceFactory.Create(
|
||||
tenantId: "tenant-1",
|
||||
policyId: "policy-1",
|
||||
policyVersion: 1,
|
||||
runId: "run-123",
|
||||
ruleName: "reachability-rule",
|
||||
rulePriority: 5,
|
||||
outcome: "allow",
|
||||
evaluationTimestamp: timestamp,
|
||||
reachabilityState: "reachable",
|
||||
reachabilityConfidence: 0.95);
|
||||
|
||||
trace.ReachabilityState.Should().Be("reachable");
|
||||
trace.ReachabilityConfidence.Should().Be(0.95);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_IncludesCustomAttributes()
|
||||
{
|
||||
var timestamp = DateTimeOffset.UtcNow;
|
||||
var attributes = ImmutableDictionary<string, string>.Empty
|
||||
.Add("custom_key", "custom_value")
|
||||
.Add("another_key", "another_value");
|
||||
|
||||
var trace = RuleHitTraceFactory.Create(
|
||||
tenantId: "tenant-1",
|
||||
policyId: "policy-1",
|
||||
policyVersion: 1,
|
||||
runId: "run-123",
|
||||
ruleName: "test-rule",
|
||||
rulePriority: 1,
|
||||
outcome: "allow",
|
||||
evaluationTimestamp: timestamp,
|
||||
attributes: attributes);
|
||||
|
||||
trace.Attributes.Should().ContainKey("custom_key");
|
||||
trace.Attributes["custom_key"].Should().Be("custom_value");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToJson_ProducesValidJson()
|
||||
{
|
||||
var trace = RuleHitTraceFactory.Create(
|
||||
tenantId: "tenant-1",
|
||||
policyId: "policy-1",
|
||||
policyVersion: 1,
|
||||
runId: "run-123",
|
||||
ruleName: "test-rule",
|
||||
rulePriority: 1,
|
||||
outcome: "allow",
|
||||
evaluationTimestamp: DateTimeOffset.UtcNow);
|
||||
|
||||
var json = RuleHitTraceFactory.ToJson(trace);
|
||||
|
||||
json.Should().Contain("\"tenant_id\":\"tenant-1\"");
|
||||
json.Should().Contain("\"policy_id\":\"policy-1\"");
|
||||
json.Should().Contain("\"rule_name\":\"test-rule\"");
|
||||
json.Should().NotContain("\n"); // Single line
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToNdjson_ProducesMultipleLines()
|
||||
{
|
||||
var timestamp = DateTimeOffset.UtcNow;
|
||||
var traces = new[]
|
||||
{
|
||||
RuleHitTraceFactory.Create("tenant-1", "policy-1", 1, "run-1", "rule-1", 1, "allow", timestamp),
|
||||
RuleHitTraceFactory.Create("tenant-1", "policy-1", 1, "run-1", "rule-2", 2, "deny", timestamp),
|
||||
RuleHitTraceFactory.Create("tenant-1", "policy-1", 1, "run-1", "rule-3", 3, "suppress", timestamp)
|
||||
};
|
||||
|
||||
var ndjson = RuleHitTraceFactory.ToNdjson(traces);
|
||||
var lines = ndjson.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
lines.Should().HaveCount(3);
|
||||
lines[0].Should().Contain("rule-1");
|
||||
lines[1].Should().Contain("rule-2");
|
||||
lines[2].Should().Contain("rule-3");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region RuleHitStatistics Tests
|
||||
|
||||
[Fact]
|
||||
public void CreateStatistics_AggregatesCorrectly()
|
||||
{
|
||||
var timestamp = DateTimeOffset.UtcNow;
|
||||
var traces = new[]
|
||||
{
|
||||
RuleHitTraceFactory.Create("tenant-1", "policy-1", 1, "run-1", "rule-1", 1, "allow", timestamp,
|
||||
ruleCategory: "severity"),
|
||||
RuleHitTraceFactory.Create("tenant-1", "policy-1", 1, "run-1", "rule-2", 2, "deny", timestamp,
|
||||
ruleCategory: "severity"),
|
||||
RuleHitTraceFactory.Create("tenant-1", "policy-1", 1, "run-1", "rule-3", 3, "suppress", timestamp,
|
||||
ruleCategory: "vex", isVexOverride: true, vexVendor: "vendor-1", vexStatus: "not_affected"),
|
||||
RuleHitTraceFactory.Create("tenant-1", "policy-1", 1, "run-1", "rule-4", 4, "suppress", timestamp,
|
||||
ruleCategory: "vex", isVexOverride: true, vexVendor: "vendor-2", vexStatus: "fixed")
|
||||
};
|
||||
|
||||
var stats = RuleHitTraceFactory.CreateStatistics(
|
||||
runId: "run-1",
|
||||
policyId: "policy-1",
|
||||
traces: traces,
|
||||
totalRulesEvaluated: 10,
|
||||
totalEvaluationMs: 50);
|
||||
|
||||
stats.RunId.Should().Be("run-1");
|
||||
stats.PolicyId.Should().Be("policy-1");
|
||||
stats.TotalRulesEvaluated.Should().Be(10);
|
||||
stats.TotalRulesFired.Should().Be(4);
|
||||
stats.TotalVexOverrides.Should().Be(2);
|
||||
|
||||
stats.RulesFiredByCategory.Should().ContainKey("severity");
|
||||
stats.RulesFiredByCategory["severity"].Should().Be(2);
|
||||
stats.RulesFiredByCategory["vex"].Should().Be(2);
|
||||
|
||||
stats.RulesFiredByOutcome.Should().ContainKey("allow");
|
||||
stats.RulesFiredByOutcome["allow"].Should().Be(1);
|
||||
stats.RulesFiredByOutcome["deny"].Should().Be(1);
|
||||
stats.RulesFiredByOutcome["suppress"].Should().Be(2);
|
||||
|
||||
stats.VexOverridesByVendor.Should().HaveCount(2);
|
||||
stats.VexOverridesByStatus.Should().ContainKey("not_affected");
|
||||
stats.VexOverridesByStatus.Should().ContainKey("fixed");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateStatistics_ComputesAverageEvaluationTime()
|
||||
{
|
||||
var traces = Array.Empty<RuleHitTrace>();
|
||||
var stats = RuleHitTraceFactory.CreateStatistics(
|
||||
runId: "run-1",
|
||||
policyId: "policy-1",
|
||||
traces: traces,
|
||||
totalRulesEvaluated: 100,
|
||||
totalEvaluationMs: 50);
|
||||
|
||||
stats.TotalEvaluationMs.Should().Be(50);
|
||||
stats.AverageRuleEvaluationMicroseconds.Should().Be(500); // 50ms * 1000 / 100 rules
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateStatistics_HandlesZeroRules()
|
||||
{
|
||||
var traces = Array.Empty<RuleHitTrace>();
|
||||
var stats = RuleHitTraceFactory.CreateStatistics(
|
||||
runId: "run-1",
|
||||
policyId: "policy-1",
|
||||
traces: traces,
|
||||
totalRulesEvaluated: 0,
|
||||
totalEvaluationMs: 0);
|
||||
|
||||
stats.TotalRulesEvaluated.Should().Be(0);
|
||||
stats.AverageRuleEvaluationMicroseconds.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateStatistics_GeneratesTopRules()
|
||||
{
|
||||
var timestamp = DateTimeOffset.UtcNow;
|
||||
var traces = Enumerable.Range(0, 20)
|
||||
.SelectMany(i => Enumerable.Range(0, i + 1).Select(_ =>
|
||||
RuleHitTraceFactory.Create("tenant-1", "policy-1", 1, "run-1", $"rule-{i}", i, "allow", timestamp)))
|
||||
.ToArray();
|
||||
|
||||
var stats = RuleHitTraceFactory.CreateStatistics("run-1", "policy-1", traces, 100, 50);
|
||||
|
||||
stats.TopRulesByHitCount.Should().HaveCount(10);
|
||||
stats.TopRulesByHitCount[0].RuleName.Should().Be("rule-19"); // Highest count
|
||||
stats.TopRulesByHitCount[0].HitCount.Should().Be(20);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region RuleHitCount Tests
|
||||
|
||||
[Fact]
|
||||
public void RuleHitCount_RecordWorks()
|
||||
{
|
||||
var hitCount = new RuleHitCount("severity-rule", 42, "deny");
|
||||
|
||||
hitCount.RuleName.Should().Be("severity-rule");
|
||||
hitCount.HitCount.Should().Be(42);
|
||||
hitCount.Outcome.Should().Be("deny");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user