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

@@ -13,13 +13,15 @@ using StellaOps.Concelier.Storage.Mongo.Aliases;
namespace StellaOps.Concelier.Storage.Mongo.Advisories;
public sealed class AdvisoryStore : IAdvisoryStore
{
public sealed class AdvisoryStore : IAdvisoryStore
{
private readonly IMongoDatabase _database;
private readonly IMongoCollection<AdvisoryDocument> _collection;
private readonly ILogger<AdvisoryStore> _logger;
private readonly IAliasStore _aliasStore;
private readonly TimeProvider _timeProvider;
private readonly MongoStorageOptions _options;
private IMongoCollection<AdvisoryDocument>? _legacyCollection;
public AdvisoryStore(
IMongoDatabase database,
@@ -28,8 +30,8 @@ public sealed class AdvisoryStore : IAdvisoryStore
IOptions<MongoStorageOptions> options,
TimeProvider? timeProvider = null)
{
_collection = (database ?? throw new ArgumentNullException(nameof(database)))
.GetCollection<AdvisoryDocument>(MongoStorageDefaults.Collections.Advisory);
_database = database ?? throw new ArgumentNullException(nameof(database));
_collection = _database.GetCollection<AdvisoryDocument>(MongoStorageDefaults.Collections.Advisory);
_aliasStore = aliasStore ?? throw new ArgumentNullException(nameof(aliasStore));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
@@ -69,14 +71,7 @@ public sealed class AdvisoryStore : IAdvisoryStore
var options = new ReplaceOptions { IsUpsert = true };
var filter = Builders<AdvisoryDocument>.Filter.Eq(x => x.AdvisoryKey, advisory.AdvisoryKey);
if (session is null)
{
await _collection.ReplaceOneAsync(filter, document, options, cancellationToken).ConfigureAwait(false);
}
else
{
await _collection.ReplaceOneAsync(session, filter, document, options, cancellationToken).ConfigureAwait(false);
}
await ReplaceAsync(filter, document, options, session, cancellationToken).ConfigureAwait(false);
_logger.LogDebug("Upserted advisory {AdvisoryKey}", advisory.AdvisoryKey);
var aliasEntries = BuildAliasEntries(advisory);
@@ -129,6 +124,71 @@ public sealed class AdvisoryStore : IAdvisoryStore
return cursor.Select(static doc => Deserialize(doc.Payload)).ToArray();
}
private async Task ReplaceAsync(
FilterDefinition<AdvisoryDocument> filter,
AdvisoryDocument document,
ReplaceOptions options,
IClientSessionHandle? session,
CancellationToken cancellationToken)
{
try
{
if (session is null)
{
await _collection.ReplaceOneAsync(filter, document, options, cancellationToken).ConfigureAwait(false);
}
else
{
await _collection.ReplaceOneAsync(session, filter, document, options, cancellationToken).ConfigureAwait(false);
}
}
catch (MongoWriteException ex) when (IsNamespaceViewError(ex))
{
var legacyCollection = await GetLegacyAdvisoryCollectionAsync(cancellationToken).ConfigureAwait(false);
if (session is null)
{
await legacyCollection.ReplaceOneAsync(filter, document, options, cancellationToken).ConfigureAwait(false);
}
else
{
await legacyCollection.ReplaceOneAsync(session, filter, document, options, cancellationToken).ConfigureAwait(false);
}
}
}
private static bool IsNamespaceViewError(MongoWriteException ex)
=> ex?.WriteError?.Code == 166 ||
(ex?.WriteError?.Message?.Contains("is a view", StringComparison.OrdinalIgnoreCase) ?? false);
private async ValueTask<IMongoCollection<AdvisoryDocument>> GetLegacyAdvisoryCollectionAsync(CancellationToken cancellationToken)
{
if (_legacyCollection is not null)
{
return _legacyCollection;
}
var filter = new BsonDocument("name", MongoStorageDefaults.Collections.Advisory);
using var cursor = await _database
.ListCollectionsAsync(new ListCollectionsOptions { Filter = filter }, cancellationToken)
.ConfigureAwait(false);
var info = await cursor.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false)
?? throw new InvalidOperationException("Advisory collection metadata not found.");
if (!info.TryGetValue("options", out var optionsValue) || optionsValue is not BsonDocument optionsDocument)
{
throw new InvalidOperationException("Advisory view options missing.");
}
if (!optionsDocument.TryGetValue("viewOn", out var viewOnValue) || viewOnValue.BsonType != BsonType.String)
{
throw new InvalidOperationException("Advisory view target not specified.");
}
var targetName = viewOnValue.AsString;
_legacyCollection = _database.GetCollection<AdvisoryDocument>(targetName);
return _legacyCollection;
}
public async IAsyncEnumerable<Advisory> StreamAsync([EnumeratorCancellation] CancellationToken cancellationToken, IClientSessionHandle? session = null)
{
var options = new FindOptions<AdvisoryDocument>

View File

@@ -46,14 +46,42 @@ public sealed class AliasStore : IAliasStore
});
}
if (documents.Count > 0)
{
await _collection.InsertManyAsync(
documents,
new InsertManyOptions { IsOrdered = false },
cancellationToken).ConfigureAwait(false);
}
}
if (documents.Count > 0)
{
try
{
await _collection.InsertManyAsync(
documents,
new InsertManyOptions { IsOrdered = false },
cancellationToken).ConfigureAwait(false);
}
catch (MongoBulkWriteException<AliasDocument> ex) when (ex.WriteErrors.Any(error => error.Category == ServerErrorCategory.DuplicateKey))
{
foreach (var writeError in ex.WriteErrors.Where(error => error.Category == ServerErrorCategory.DuplicateKey))
{
var duplicateDocument = documents.ElementAtOrDefault(writeError.Index);
_logger.LogError(
ex,
"Alias duplicate detected while inserting {Scheme}:{Value} for advisory {AdvisoryKey}. Existing aliases: {Existing}",
duplicateDocument?.Scheme,
duplicateDocument?.Value,
duplicateDocument?.AdvisoryKey,
string.Join(", ", aliasList.Select(a => $"{a.Scheme}:{a.Value}")));
}
throw;
}
catch (MongoWriteException ex) when (ex.WriteError?.Category == ServerErrorCategory.DuplicateKey)
{
_logger.LogError(
ex,
"Alias duplicate detected while inserting aliases for advisory {AdvisoryKey}. Aliases: {Aliases}",
advisoryKey,
string.Join(", ", aliasList.Select(a => $"{a.Scheme}:{a.Value}")));
throw;
}
}
}
if (aliasList.Length == 0)
{