Add unit tests for SBOM ingestion and transformation
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:
master
2025-11-04 07:49:39 +02:00
parent f72c5c513a
commit 2eb6852d34
491 changed files with 39445 additions and 3917 deletions

View File

@@ -0,0 +1,36 @@
using System.Collections.Immutable;
namespace StellaOps.Findings.Ledger.Domain;
public static class LedgerEventConstants
{
public const string EventFindingCreated = "finding.created";
public const string EventFindingStatusChanged = "finding.status_changed";
public const string EventFindingSeverityChanged = "finding.severity_changed";
public const string EventFindingTagUpdated = "finding.tag_updated";
public const string EventFindingCommentAdded = "finding.comment_added";
public const string EventFindingAssignmentChanged = "finding.assignment_changed";
public const string EventFindingAcceptedRisk = "finding.accepted_risk";
public const string EventFindingRemediationPlanAdded = "finding.remediation_plan_added";
public const string EventFindingAttachmentAdded = "finding.attachment_added";
public const string EventFindingClosed = "finding.closed";
public static readonly ImmutableHashSet<string> SupportedEventTypes = ImmutableHashSet.Create(StringComparer.Ordinal,
EventFindingCreated,
EventFindingStatusChanged,
EventFindingSeverityChanged,
EventFindingTagUpdated,
EventFindingCommentAdded,
EventFindingAssignmentChanged,
EventFindingAcceptedRisk,
EventFindingRemediationPlanAdded,
EventFindingAttachmentAdded,
EventFindingClosed);
public static readonly ImmutableHashSet<string> SupportedActorTypes = ImmutableHashSet.Create(StringComparer.Ordinal,
"system",
"operator",
"integration");
public const string EmptyHash = "0000000000000000000000000000000000000000000000000000000000000000";
}

View File

@@ -0,0 +1,85 @@
using System.Text.Json.Nodes;
namespace StellaOps.Findings.Ledger.Domain;
public sealed record LedgerEventDraft(
string TenantId,
Guid ChainId,
long SequenceNumber,
Guid EventId,
string EventType,
string PolicyVersion,
string FindingId,
string ArtifactId,
Guid? SourceRunId,
string ActorId,
string ActorType,
DateTimeOffset OccurredAt,
DateTimeOffset RecordedAt,
JsonObject Payload,
JsonObject CanonicalEnvelope,
string? ProvidedPreviousHash);
public sealed record LedgerEventRecord(
string TenantId,
Guid ChainId,
long SequenceNumber,
Guid EventId,
string EventType,
string PolicyVersion,
string FindingId,
string ArtifactId,
Guid? SourceRunId,
string ActorId,
string ActorType,
DateTimeOffset OccurredAt,
DateTimeOffset RecordedAt,
JsonObject EventBody,
string EventHash,
string PreviousHash,
string MerkleLeafHash,
string CanonicalJson);
public sealed record LedgerChainHead(
long SequenceNumber,
string EventHash,
DateTimeOffset RecordedAt);
public enum LedgerWriteStatus
{
Success,
Idempotent,
ValidationFailed,
Conflict
}
public sealed record LedgerWriteResult(
LedgerWriteStatus Status,
LedgerEventRecord? Record,
IReadOnlyList<string> Errors,
LedgerEventRecord? ExistingRecord,
string? ConflictCode)
{
public static LedgerWriteResult ValidationFailed(params string[] errors)
=> new(LedgerWriteStatus.ValidationFailed, null, errors, null, null);
public static LedgerWriteResult Conflict(string code, params string[] errors)
=> new(LedgerWriteStatus.Conflict, null, errors, null, code);
public static LedgerWriteResult Idempotent(LedgerEventRecord record)
=> new(LedgerWriteStatus.Idempotent, record, Array.Empty<string>(), record, null);
public static LedgerWriteResult Success(LedgerEventRecord record)
=> new(LedgerWriteStatus.Success, record, Array.Empty<string>(), null, null);
}
public sealed class LedgerDuplicateEventException : Exception
{
public LedgerDuplicateEventException(Guid eventId, Exception innerException)
: base($"Ledger event {eventId} already exists.", innerException)
{
EventId = eventId;
}
public Guid EventId { get; }
}

View File

@@ -0,0 +1,57 @@
using System.Text.Json.Nodes;
namespace StellaOps.Findings.Ledger.Domain;
public sealed record FindingProjection(
string TenantId,
string FindingId,
string PolicyVersion,
string Status,
decimal? Severity,
JsonObject Labels,
Guid CurrentEventId,
string? ExplainRef,
JsonArray PolicyRationale,
DateTimeOffset UpdatedAt,
string CycleHash);
public sealed record FindingHistoryEntry(
string TenantId,
string FindingId,
string PolicyVersion,
Guid EventId,
string Status,
decimal? Severity,
string ActorId,
string? Comment,
DateTimeOffset OccurredAt);
public sealed record TriageActionEntry(
string TenantId,
Guid ActionId,
Guid EventId,
string FindingId,
string ActionType,
JsonObject Payload,
DateTimeOffset CreatedAt,
string CreatedBy);
public sealed record ProjectionReduceResult(
FindingProjection Projection,
FindingHistoryEntry History,
TriageActionEntry? Action);
public sealed record ProjectionCheckpoint(
DateTimeOffset LastRecordedAt,
Guid LastEventId,
DateTimeOffset UpdatedAt)
{
public static ProjectionCheckpoint Initial(TimeProvider timeProvider)
{
ArgumentNullException.ThrowIfNull(timeProvider);
var epoch = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero);
var now = timeProvider.GetUtcNow();
return new ProjectionCheckpoint(epoch, Guid.Empty, now);
}
}