Add unit tests for SBOM ingestion and transformation
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
- Implement `SbomIngestServiceCollectionExtensionsTests` to verify the SBOM ingestion pipeline exports snapshots correctly. - Create `SbomIngestTransformerTests` to ensure the transformation produces expected nodes and edges, including deduplication of license nodes and normalization of timestamps. - Add `SbomSnapshotExporterTests` to test the export functionality for manifest, adjacency, nodes, and edges. - Introduce `VexOverlayTransformerTests` to validate the transformation of VEX nodes and edges. - Set up project file for the test project with necessary dependencies and configurations. - Include JSON fixture files for testing purposes.
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
using System.Text.Json.Nodes;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Findings.Ledger.Domain;
|
||||
|
||||
namespace StellaOps.Findings.Ledger.Infrastructure.Policy;
|
||||
|
||||
public sealed class InlinePolicyEvaluationService : IPolicyEvaluationService
|
||||
{
|
||||
private readonly ILogger<InlinePolicyEvaluationService> _logger;
|
||||
|
||||
public InlinePolicyEvaluationService(ILogger<InlinePolicyEvaluationService> logger)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
public Task<PolicyEvaluationResult> EvaluateAsync(
|
||||
LedgerEventRecord record,
|
||||
FindingProjection? existingProjection,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (record is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(record));
|
||||
}
|
||||
|
||||
var eventObject = record.EventBody["event"]?.AsObject();
|
||||
if (eventObject is null)
|
||||
{
|
||||
_logger.LogWarning("Ledger event {EventId} missing canonical event payload; falling back to existing projection.", record.EventId);
|
||||
return Task.FromResult(CreateFallback(existingProjection));
|
||||
}
|
||||
|
||||
var payload = eventObject["payload"] as JsonObject;
|
||||
var status = ExtractString(payload, "status");
|
||||
var severity = ExtractDecimal(payload, "severity");
|
||||
var explainRef = ExtractString(payload, "explainRef") ?? ExtractString(payload, "explain_ref");
|
||||
|
||||
var labels = ExtractLabels(payload, existingProjection);
|
||||
var rationale = ExtractRationale(payload, explainRef);
|
||||
|
||||
var result = new PolicyEvaluationResult(
|
||||
status,
|
||||
severity,
|
||||
labels,
|
||||
explainRef,
|
||||
rationale);
|
||||
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
|
||||
private static PolicyEvaluationResult CreateFallback(FindingProjection? existingProjection)
|
||||
{
|
||||
var labels = existingProjection?.Labels is not null
|
||||
? (JsonObject)existingProjection.Labels.DeepClone()
|
||||
: new JsonObject();
|
||||
|
||||
var rationale = existingProjection?.PolicyRationale is not null
|
||||
? CloneArray(existingProjection.PolicyRationale)
|
||||
: new JsonArray();
|
||||
|
||||
return new PolicyEvaluationResult(
|
||||
existingProjection?.Status,
|
||||
existingProjection?.Severity,
|
||||
labels,
|
||||
existingProjection?.ExplainRef,
|
||||
rationale);
|
||||
}
|
||||
|
||||
private static JsonObject ExtractLabels(JsonObject? payload, FindingProjection? existingProjection)
|
||||
{
|
||||
var labels = existingProjection?.Labels is not null
|
||||
? (JsonObject)existingProjection.Labels.DeepClone()
|
||||
: new JsonObject();
|
||||
|
||||
if (payload is null)
|
||||
{
|
||||
return labels;
|
||||
}
|
||||
|
||||
if (payload.TryGetPropertyValue("labels", out var labelsNode) && labelsNode is JsonObject labelUpdates)
|
||||
{
|
||||
foreach (var property in labelUpdates)
|
||||
{
|
||||
if (property.Value is null || property.Value.GetValueKind() == JsonValueKind.Null)
|
||||
{
|
||||
labels.Remove(property.Key);
|
||||
}
|
||||
else
|
||||
{
|
||||
labels[property.Key] = property.Value.DeepClone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (payload.TryGetPropertyValue("labelsRemove", out var removeNode) && removeNode is JsonArray removeArray)
|
||||
{
|
||||
foreach (var item in removeArray)
|
||||
{
|
||||
if (item is JsonValue value && value.TryGetValue(out string? key) && !string.IsNullOrWhiteSpace(key))
|
||||
{
|
||||
labels.Remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return labels;
|
||||
}
|
||||
|
||||
private static JsonArray ExtractRationale(JsonObject? payload, string? explainRef)
|
||||
{
|
||||
if (payload?.TryGetPropertyValue("rationaleRefs", out var rationaleNode) == true &&
|
||||
rationaleNode is JsonArray rationaleRefs)
|
||||
{
|
||||
return CloneArray(rationaleRefs);
|
||||
}
|
||||
|
||||
var rationale = new JsonArray();
|
||||
if (!string.IsNullOrWhiteSpace(explainRef))
|
||||
{
|
||||
rationale.Add(explainRef);
|
||||
}
|
||||
|
||||
return rationale;
|
||||
}
|
||||
|
||||
private static string? ExtractString(JsonObject? obj, string propertyName)
|
||||
{
|
||||
if (obj is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!obj.TryGetPropertyValue(propertyName, out var value) || value is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (value is JsonValue jsonValue && jsonValue.TryGetValue(out string? text))
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(text) ? null : text;
|
||||
}
|
||||
|
||||
return value.ToString();
|
||||
}
|
||||
|
||||
private static decimal? ExtractDecimal(JsonObject? obj, string propertyName)
|
||||
{
|
||||
if (obj is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!obj.TryGetPropertyValue(propertyName, out var value) || value is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (value is JsonValue jsonValue)
|
||||
{
|
||||
if (jsonValue.TryGetValue(out decimal decimalValue))
|
||||
{
|
||||
return decimalValue;
|
||||
}
|
||||
|
||||
if (jsonValue.TryGetValue(out double doubleValue))
|
||||
{
|
||||
return Convert.ToDecimal(doubleValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (decimal.TryParse(value.ToString(), out var parsed))
|
||||
{
|
||||
return parsed;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static JsonArray CloneArray(JsonArray array)
|
||||
{
|
||||
var clone = new JsonArray();
|
||||
foreach (var item in array)
|
||||
{
|
||||
clone.Add(item?.DeepClone());
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user