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:
@@ -15,6 +15,8 @@ public sealed class SchedulerMongoOptions
|
||||
|
||||
public string RunsCollection { get; set; } = "runs";
|
||||
|
||||
public string PolicyJobsCollection { get; set; } = "policy_jobs";
|
||||
|
||||
public string ImpactSnapshotsCollection { get; set; } = "impact_snapshots";
|
||||
|
||||
public string AuditCollection { get; set; } = "audit";
|
||||
|
||||
@@ -36,13 +36,19 @@ public interface IPolicyRunJobRepository
|
||||
PolicyRunMode? mode = null,
|
||||
IReadOnlyCollection<PolicyRunJobStatus>? statuses = null,
|
||||
DateTimeOffset? queuedAfter = null,
|
||||
int limit = 50,
|
||||
IClientSessionHandle? session = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<bool> ReplaceAsync(
|
||||
PolicyRunJob job,
|
||||
string? expectedLeaseOwner = null,
|
||||
IClientSessionHandle? session = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
int limit = 50,
|
||||
IClientSessionHandle? session = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<bool> ReplaceAsync(
|
||||
PolicyRunJob job,
|
||||
string? expectedLeaseOwner = null,
|
||||
IClientSessionHandle? session = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<long> CountAsync(
|
||||
string tenantId,
|
||||
PolicyRunMode mode,
|
||||
IReadOnlyCollection<PolicyRunJobStatus> statuses,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
using StellaOps.Scheduler.Models;
|
||||
using StellaOps.Scheduler.Storage.Mongo.Internal;
|
||||
using StellaOps.Scheduler.Storage.Mongo.Serialization;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
using StellaOps.Scheduler.Models;
|
||||
using StellaOps.Scheduler.Storage.Mongo.Internal;
|
||||
using StellaOps.Scheduler.Storage.Mongo.Serialization;
|
||||
|
||||
namespace StellaOps.Scheduler.Storage.Mongo.Repositories;
|
||||
|
||||
@@ -206,16 +207,43 @@ internal sealed class PolicyRunJobRepository : IPolicyRunJobRepository
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return documents
|
||||
.Select(PolicyRunJobDocumentMapper.FromBsonDocument)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public async Task<bool> ReplaceAsync(
|
||||
PolicyRunJob job,
|
||||
string? expectedLeaseOwner = null,
|
||||
IClientSessionHandle? session = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
return documents
|
||||
.Select(PolicyRunJobDocumentMapper.FromBsonDocument)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public async Task<long> CountAsync(
|
||||
string tenantId,
|
||||
PolicyRunMode mode,
|
||||
IReadOnlyCollection<PolicyRunJobStatus> statuses,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(tenantId))
|
||||
{
|
||||
throw new ArgumentException("Tenant id must be provided.", nameof(tenantId));
|
||||
}
|
||||
|
||||
var filters = new List<FilterDefinition<BsonDocument>>
|
||||
{
|
||||
Filter.Eq("tenantId", tenantId),
|
||||
Filter.Eq("mode", mode.ToString().ToLowerInvariant())
|
||||
};
|
||||
|
||||
if (statuses is { Count: > 0 })
|
||||
{
|
||||
var array = new BsonArray(statuses.Select(static status => status.ToString().ToLowerInvariant()));
|
||||
filters.Add(Filter.In("status", array));
|
||||
}
|
||||
|
||||
var filter = Filter.And(filters);
|
||||
return await _collection.CountDocumentsAsync(filter, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<bool> ReplaceAsync(
|
||||
PolicyRunJob job,
|
||||
string? expectedLeaseOwner = null,
|
||||
IClientSessionHandle? session = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(job);
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
|
||||
namespace StellaOps.Scheduler.Storage.Mongo.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Cursor describing the position of a run in deterministic ordering.
|
||||
/// </summary>
|
||||
public sealed record RunListCursor
|
||||
{
|
||||
public RunListCursor(DateTimeOffset createdAt, string runId)
|
||||
{
|
||||
CreatedAt = NormalizeTimestamp(createdAt);
|
||||
RunId = NormalizeRunId(runId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Timestamp of the last run observed (UTC).
|
||||
/// </summary>
|
||||
public DateTimeOffset CreatedAt { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Identifier of the last run observed.
|
||||
/// </summary>
|
||||
public string RunId { get; }
|
||||
|
||||
private static DateTimeOffset NormalizeTimestamp(DateTimeOffset value)
|
||||
{
|
||||
var utc = value.ToUniversalTime();
|
||||
return new DateTimeOffset(DateTime.SpecifyKind(utc.DateTime, DateTimeKind.Utc));
|
||||
}
|
||||
|
||||
private static string NormalizeRunId(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
throw new ArgumentException("Run id must be provided.", nameof(value));
|
||||
}
|
||||
|
||||
var trimmed = value.Trim();
|
||||
if (trimmed.Length > 256)
|
||||
{
|
||||
throw new ArgumentException("Run id exceeds 256 characters.", nameof(value));
|
||||
}
|
||||
|
||||
return trimmed;
|
||||
}
|
||||
}
|
||||
@@ -19,16 +19,21 @@ public sealed class RunQueryOptions
|
||||
public ImmutableArray<RunState> States { get; init; } = ImmutableArray<RunState>.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Optional lower bound for creation timestamp (UTC).
|
||||
/// </summary>
|
||||
public DateTimeOffset? CreatedAfter { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of runs to return (default 50 when unspecified).
|
||||
/// </summary>
|
||||
public int? Limit { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional lower bound for creation timestamp (UTC).
|
||||
/// </summary>
|
||||
public DateTimeOffset? CreatedAfter { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional cursor to resume iteration using deterministic ordering.
|
||||
/// </summary>
|
||||
public RunListCursor? Cursor { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of runs to return (default 50 when unspecified).
|
||||
/// </summary>
|
||||
public int? Limit { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Sort order flag. Defaults to descending by createdAt.
|
||||
/// </summary>
|
||||
public bool SortAscending { get; init; }
|
||||
|
||||
@@ -127,28 +127,53 @@ internal sealed class RunRepository : IRunRepository
|
||||
filters.Add(Filter.In("state", options.States.Select(state => state.ToString().ToLowerInvariant())));
|
||||
}
|
||||
|
||||
if (options.CreatedAfter is { } createdAfter)
|
||||
{
|
||||
filters.Add(Filter.Gt("createdAt", createdAfter.ToUniversalTime().UtcDateTime));
|
||||
}
|
||||
if (options.CreatedAfter is { } createdAfter)
|
||||
{
|
||||
filters.Add(Filter.Gt("createdAt", createdAfter.ToUniversalTime().UtcDateTime));
|
||||
}
|
||||
|
||||
if (options.Cursor is { } cursor)
|
||||
{
|
||||
var createdAtUtc = cursor.CreatedAt.ToUniversalTime().UtcDateTime;
|
||||
FilterDefinition<BsonDocument> cursorFilter;
|
||||
|
||||
if (options.SortAscending)
|
||||
{
|
||||
cursorFilter = Filter.Or(
|
||||
Filter.Gt("createdAt", createdAtUtc),
|
||||
Filter.And(
|
||||
Filter.Eq("createdAt", createdAtUtc),
|
||||
Filter.Gt("_id", cursor.RunId)));
|
||||
}
|
||||
else
|
||||
{
|
||||
cursorFilter = Filter.Or(
|
||||
Filter.Lt("createdAt", createdAtUtc),
|
||||
Filter.And(
|
||||
Filter.Eq("createdAt", createdAtUtc),
|
||||
Filter.Lt("_id", cursor.RunId)));
|
||||
}
|
||||
|
||||
filters.Add(cursorFilter);
|
||||
}
|
||||
|
||||
var combined = Filter.And(filters);
|
||||
|
||||
var find = session is null
|
||||
? _collection.Find(combined)
|
||||
: _collection.Find(session, combined);
|
||||
|
||||
var combined = Filter.And(filters);
|
||||
|
||||
var find = session is null
|
||||
? _collection.Find(combined)
|
||||
: _collection.Find(session, combined);
|
||||
|
||||
var limit = options.Limit is { } specified && specified > 0 ? specified : DefaultListLimit;
|
||||
find = find.Limit(limit);
|
||||
|
||||
var sortDefinition = options.SortAscending
|
||||
? Sort.Ascending("createdAt")
|
||||
: Sort.Descending("createdAt");
|
||||
|
||||
find = find.Sort(sortDefinition);
|
||||
|
||||
var documents = await find.ToListAsync(cancellationToken).ConfigureAwait(false);
|
||||
return documents.Select(RunDocumentMapper.FromBsonDocument).ToArray();
|
||||
var limit = options.Limit is { } specified && specified > 0 ? specified : DefaultListLimit;
|
||||
find = find.Limit(limit);
|
||||
|
||||
var sortDefinition = options.SortAscending
|
||||
? Sort.Combine(Sort.Ascending("createdAt"), Sort.Ascending("_id"))
|
||||
: Sort.Combine(Sort.Descending("createdAt"), Sort.Descending("_id"));
|
||||
|
||||
find = find.Sort(sortDefinition);
|
||||
|
||||
var documents = await find.ToListAsync(cancellationToken).ConfigureAwait(false);
|
||||
return documents.Select(RunDocumentMapper.FromBsonDocument).ToArray();
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<Run>> ListByStateAsync(
|
||||
|
||||
Reference in New Issue
Block a user