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

@@ -1,7 +1,9 @@
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using StellaOps.Scheduler.Models;
using StellaOps.Scheduler.Storage.Mongo.Services;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Text;
using StellaOps.Scheduler.Models;
using StellaOps.Scheduler.Storage.Mongo.Repositories;
using StellaOps.Scheduler.Storage.Mongo.Services;
namespace StellaOps.Scheduler.WebService;
@@ -91,11 +93,11 @@ internal static class SchedulerEndpointHelpers
return null;
}
public static DateTimeOffset? TryParseDateTimeOffset(string? value)
{
if (string.IsNullOrWhiteSpace(value))
{
return null;
public static DateTimeOffset? TryParseDateTimeOffset(string? value)
{
if (string.IsNullOrWhiteSpace(value))
{
return null;
}
if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var parsed))
@@ -114,14 +116,62 @@ internal static class SchedulerEndpointHelpers
throw new ArgumentException("Tenant identifier must be provided.", nameof(tenantId));
}
return new Selector(
selection.Scope,
tenantId,
selection.Namespaces,
selection.Repositories,
selection.Digests,
selection.IncludeTags,
selection.Labels,
selection.ResolvesTags);
}
}
return new Selector(
selection.Scope,
tenantId,
selection.Namespaces,
selection.Repositories,
selection.Digests,
selection.IncludeTags,
selection.Labels,
selection.ResolvesTags);
}
public static string CreateRunCursor(Run run)
{
ArgumentNullException.ThrowIfNull(run);
var payload = $"{run.CreatedAt.ToUniversalTime():O}|{run.Id}";
return Convert.ToBase64String(Encoding.UTF8.GetBytes(payload));
}
public static RunListCursor? TryParseRunCursor(string? value)
{
if (string.IsNullOrWhiteSpace(value))
{
return null;
}
var trimmed = value.Trim();
if (trimmed.Length == 0)
{
return null;
}
try
{
var bytes = Convert.FromBase64String(trimmed);
var decoded = Encoding.UTF8.GetString(bytes);
var parts = decoded.Split('|', 2, StringSplitOptions.TrimEntries);
if (parts.Length != 2)
{
throw new ValidationException($"Cursor '{value}' is not valid.");
}
if (!DateTimeOffset.TryParse(parts[0], CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var timestamp))
{
throw new ValidationException($"Cursor '{value}' is not valid.");
}
if (string.IsNullOrWhiteSpace(parts[1]))
{
throw new ValidationException($"Cursor '{value}' is not valid.");
}
return new RunListCursor(timestamp.ToUniversalTime(), parts[1]);
}
catch (FormatException ex)
{
throw new ValidationException($"Cursor '{value}' is not valid.", ex);
}
}
}