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,206 @@
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Serilog;
using Serilog.Events;
using StellaOps.Auth.Abstractions;
using StellaOps.Auth.ServerIntegration;
using StellaOps.Configuration;
using StellaOps.DependencyInjection;
using StellaOps.Findings.Ledger.Domain;
using StellaOps.Findings.Ledger.Infrastructure;
using StellaOps.Findings.Ledger.Infrastructure.Merkle;
using StellaOps.Findings.Ledger.Infrastructure.Postgres;
using StellaOps.Findings.Ledger.Infrastructure.Projection;
using StellaOps.Findings.Ledger.Infrastructure.Policy;
using StellaOps.Findings.Ledger.Options;
using StellaOps.Findings.Ledger.Services;
using StellaOps.Findings.Ledger.WebService.Contracts;
using StellaOps.Findings.Ledger.WebService.Mappings;
using StellaOps.Telemetry.Core;
const string LedgerWritePolicy = "ledger.events.write";
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddStellaOpsDefaults(options =>
{
options.BasePath = builder.Environment.ContentRootPath;
options.EnvironmentPrefix = "FINDINGS_LEDGER_";
options.ConfigureBuilder = configurationBuilder =>
{
configurationBuilder.AddYamlFile("../etc/findings-ledger.yaml", optional: true, reloadOnChange: true);
};
});
var bootstrapOptions = builder.Configuration.BindOptions<LedgerServiceOptions>(
LedgerServiceOptions.SectionName,
(opts, _) => opts.Validate());
builder.Host.UseSerilog((context, services, loggerConfiguration) =>
{
loggerConfiguration
.MinimumLevel.Information()
.MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)
.Enrich.FromLogContext()
.WriteTo.Console();
});
builder.Services.AddOptions<LedgerServiceOptions>()
.Bind(builder.Configuration.GetSection(LedgerServiceOptions.SectionName))
.PostConfigure(options => options.Validate())
.ValidateOnStart();
builder.Services.AddSingleton(TimeProvider.System);
builder.Services.AddProblemDetails();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddHealthChecks();
builder.Services.AddStellaOpsTelemetry(
builder.Configuration,
configureMetering: meterBuilder =>
{
meterBuilder.AddAspNetCoreInstrumentation();
meterBuilder.AddHttpClientInstrumentation();
},
configureTracing: tracerBuilder =>
{
tracerBuilder.AddAspNetCoreInstrumentation();
tracerBuilder.AddHttpClientInstrumentation();
});
builder.Services.AddStellaOpsResourceServerAuthentication(
builder.Configuration,
configurationSection: null,
configure: resourceOptions =>
{
resourceOptions.Authority = bootstrapOptions.Authority.Issuer;
resourceOptions.RequireHttpsMetadata = bootstrapOptions.Authority.RequireHttpsMetadata;
resourceOptions.MetadataAddress = bootstrapOptions.Authority.MetadataAddress;
resourceOptions.BackchannelTimeout = bootstrapOptions.Authority.BackchannelTimeout;
resourceOptions.TokenClockSkew = bootstrapOptions.Authority.TokenClockSkew;
resourceOptions.Audiences.Clear();
foreach (var audience in bootstrapOptions.Authority.Audiences)
{
resourceOptions.Audiences.Add(audience);
}
resourceOptions.RequiredScopes.Clear();
foreach (var scope in bootstrapOptions.Authority.RequiredScopes)
{
resourceOptions.RequiredScopes.Add(scope);
}
foreach (var network in bootstrapOptions.Authority.BypassNetworks)
{
resourceOptions.BypassNetworks.Add(network);
}
});
builder.Services.AddAuthorization(options =>
{
var scopes = bootstrapOptions.Authority.RequiredScopes.Count > 0
? bootstrapOptions.Authority.RequiredScopes.ToArray()
: new[] { StellaOpsScopes.VulnOperate };
options.AddPolicy(LedgerWritePolicy, policy =>
{
policy.RequireAuthenticatedUser();
policy.Requirements.Add(new StellaOpsScopeRequirement(scopes));
policy.AddAuthenticationSchemes(StellaOpsAuthenticationDefaults.AuthenticationScheme);
});
});
builder.Services.AddSingleton<LedgerAnchorQueue>();
builder.Services.AddSingleton<LedgerDataSource>();
builder.Services.AddSingleton<IMerkleAnchorRepository, PostgresMerkleAnchorRepository>();
builder.Services.AddSingleton<ILedgerEventRepository, PostgresLedgerEventRepository>();
builder.Services.AddSingleton<IMerkleAnchorScheduler, PostgresMerkleAnchorScheduler>();
builder.Services.AddSingleton<ILedgerEventStream, PostgresLedgerEventStream>();
builder.Services.AddSingleton<IFindingProjectionRepository, PostgresFindingProjectionRepository>();
builder.Services.AddSingleton<IPolicyEvaluationService, InlinePolicyEvaluationService>();
builder.Services.AddSingleton<ILedgerEventWriteService, LedgerEventWriteService>();
builder.Services.AddHostedService<LedgerMerkleAnchorWorker>();
builder.Services.AddHostedService<LedgerProjectionWorker>();
var app = builder.Build();
app.UseSerilogRequestLogging();
app.UseExceptionHandler(exceptionApp =>
{
exceptionApp.Run(async context =>
{
var feature = context.Features.Get<IExceptionHandlerFeature>();
if (feature?.Error is null)
{
return;
}
var problem = Results.Problem(
statusCode: StatusCodes.Status500InternalServerError,
title: "ledger_internal_error",
detail: feature.Error.Message);
await problem.ExecuteAsync(context);
});
});
app.UseAuthentication();
app.UseAuthorization();
app.MapHealthChecks("/healthz");
app.MapPost("/vuln/ledger/events", async Task<Results<Created<LedgerEventResponse>, Ok<LedgerEventResponse>, ProblemHttpResult>> (
LedgerEventRequest request,
ILedgerEventWriteService writeService,
CancellationToken cancellationToken) =>
{
var draft = request.ToDraft();
var result = await writeService.AppendAsync(draft, cancellationToken).ConfigureAwait(false);
return result.Status switch
{
LedgerWriteStatus.Success => CreateCreatedResponse(result.Record!),
LedgerWriteStatus.Idempotent => TypedResults.Ok(CreateResponse(result.Record!, "idempotent")),
LedgerWriteStatus.ValidationFailed => TypedResults.Problem(
statusCode: StatusCodes.Status400BadRequest,
title: "validation_failed",
detail: string.Join(";", result.Errors)),
LedgerWriteStatus.Conflict => TypedResults.Problem(
statusCode: StatusCodes.Status409Conflict,
title: result.ConflictCode ?? "conflict",
detail: string.Join(";", result.Errors)),
_ => TypedResults.Problem(
statusCode: StatusCodes.Status500InternalServerError,
title: "ledger_internal_error",
detail: "Unexpected ledger status.")
};
})
.WithName("LedgerEventAppend")
.RequireAuthorization(LedgerWritePolicy)
.Produces(StatusCodes.Status201Created)
.Produces(StatusCodes.Status200OK)
.ProducesProblem(StatusCodes.Status400BadRequest)
.ProducesProblem(StatusCodes.Status409Conflict)
.ProducesProblem(StatusCodes.Status500InternalServerError);
app.Run();
static Created<LedgerEventResponse> CreateCreatedResponse(LedgerEventRecord record)
{
var response = CreateResponse(record, "created");
return TypedResults.Created($"/vuln/ledger/events/{record.EventId}", response);
}
static LedgerEventResponse CreateResponse(LedgerEventRecord record, string status)
=> new()
{
EventId = record.EventId,
ChainId = record.ChainId,
Sequence = record.SequenceNumber,
Status = status,
EventHash = record.EventHash,
PreviousHash = record.PreviousHash,
MerkleLeafHash = record.MerkleLeafHash,
RecordedAt = record.RecordedAt
};