Add receipt input JSON and SHA256 hash for CVSS policy scoring tests

- Introduced a new JSON fixture `receipt-input.json` containing base, environmental, and threat metrics for CVSS scoring.
- Added corresponding SHA256 hash file `receipt-input.sha256` to ensure integrity of the JSON fixture.
This commit is contained in:
StellaOps Bot
2025-12-04 07:30:42 +02:00
parent 2d079d61ed
commit e1262eb916
91 changed files with 19493 additions and 187 deletions

View File

@@ -0,0 +1,38 @@
using System.Collections.Immutable;
using StellaOps.Concelier.Storage.Postgres.Repositories;
namespace StellaOps.Concelier.Storage.Postgres.Converters.Importers;
/// <summary>
/// Compares imported advisory snapshots between sources to ensure parity before cutover.
/// </summary>
public sealed class ParityRunner
{
private readonly IAdvisorySnapshotRepository _snapshots;
public ParityRunner(IAdvisorySnapshotRepository snapshots)
{
_snapshots = snapshots;
}
/// <summary>
/// Compares two feed snapshots by advisory keys; returns true when keys match exactly.
/// </summary>
public async Task<ParityResult> CompareAsync(Guid feedSnapshotA, Guid feedSnapshotB, CancellationToken cancellationToken = default)
{
var a = await _snapshots.GetByFeedSnapshotAsync(feedSnapshotA, cancellationToken).ConfigureAwait(false);
var b = await _snapshots.GetByFeedSnapshotAsync(feedSnapshotB, cancellationToken).ConfigureAwait(false);
var setA = a.Select(s => s.AdvisoryKey).ToImmutableSortedSet(StringComparer.OrdinalIgnoreCase);
var setB = b.Select(s => s.AdvisoryKey).ToImmutableSortedSet(StringComparer.OrdinalIgnoreCase);
var missingInB = setA.Except(setB).ToArray();
var missingInA = setB.Except(setA).ToArray();
var match = missingInA.Length == 0 && missingInB.Length == 0;
return new ParityResult(match, missingInA, missingInB);
}
public sealed record ParityResult(bool Match, IReadOnlyList<string> MissingInA, IReadOnlyList<string> MissingInB);
}

View File

@@ -0,0 +1,91 @@
using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;
using StellaOps.Concelier.Storage.Mongo.Advisories;
using StellaOps.Concelier.Storage.Postgres;
using StellaOps.Concelier.Storage.Postgres.Converters;
using StellaOps.Concelier.Storage.Postgres.Converters.Importers;
using StellaOps.Concelier.Storage.Postgres.Repositories;
using Xunit;
namespace StellaOps.Concelier.Storage.Postgres.Tests;
[Collection(ConcelierPostgresCollection.Name)]
public sealed class DualImportParityTests : IClassFixture<MongoFixture>, IAsyncLifetime
{
private readonly ConcelierPostgresFixture _pg;
private readonly MongoFixture _mongo;
private AdvisoryConversionService _service = default!;
private IAdvisoryRepository _advisories = default!;
private IFeedSnapshotRepository _feedSnapshots = default!;
private IAdvisorySnapshotRepository _advisorySnapshots = default!;
private IMongoCollection<AdvisoryDocument> _nvd = default!;
private IMongoCollection<AdvisoryDocument> _osv = default!;
public DualImportParityTests(ConcelierPostgresFixture pg, MongoFixture mongo)
{
_pg = pg;
_mongo = mongo;
}
public async Task InitializeAsync()
{
await _pg.TruncateAllTablesAsync();
var options = _pg.Fixture.CreateOptions();
options.SchemaName = _pg.SchemaName;
var dataSource = new ConcelierDataSource(Options.Create(options), NullLogger<ConcelierDataSource>.Instance);
_advisories = new AdvisoryRepository(dataSource, NullLogger<AdvisoryRepository>.Instance);
_feedSnapshots = new FeedSnapshotRepository(dataSource, NullLogger<FeedSnapshotRepository>.Instance);
_advisorySnapshots = new AdvisorySnapshotRepository(dataSource, NullLogger<AdvisorySnapshotRepository>.Instance);
_service = new AdvisoryConversionService(_advisories);
_nvd = _mongo.Database.GetCollection<AdvisoryDocument>("parity_nvd");
_osv = _mongo.Database.GetCollection<AdvisoryDocument>("parity_osv");
await _nvd.DeleteManyAsync(FilterDefinition<AdvisoryDocument>.Empty);
await _osv.DeleteManyAsync(FilterDefinition<AdvisoryDocument>.Empty);
var payload = new BsonDocument
{
{ "primaryVulnId", "CVE-2024-0600" },
{ "aliases", new BsonArray { "CVE-2024-0600" } },
{ "affected", new BsonArray { new BsonDocument { { "ecosystem", "npm" }, { "packageName", "dual" }, { "range", "{}" } } } }
};
await _nvd.InsertOneAsync(new AdvisoryDocument { AdvisoryKey = "ADV-PARITY", Payload = payload, Modified = DateTime.UtcNow, Published = DateTime.UtcNow.AddDays(-1) });
await _osv.InsertOneAsync(new AdvisoryDocument { AdvisoryKey = "ADV-PARITY", Payload = payload, Modified = DateTime.UtcNow, Published = DateTime.UtcNow.AddDays(-1) });
}
public Task DisposeAsync() => Task.CompletedTask;
[Fact]
public async Task DualImports_ResultInSingleAdvisoryAndTwoSnapshots()
{
var nvdImporter = new NvdImporter(_nvd, _service, _feedSnapshots, _advisorySnapshots);
var osvImporter = new OsvImporter(_osv, _service, _feedSnapshots, _advisorySnapshots);
var nvdSource = Guid.NewGuid();
var osvSource = Guid.NewGuid();
await nvdImporter.ImportSnapshotAsync(nvdSource, "nvd", "snap-nvd", default);
await osvImporter.ImportSnapshotAsync(osvSource, "snap-osv", default);
var advisory = await _advisories.GetByKeyAsync("ADV-PARITY");
advisory.Should().NotBeNull();
var total = await _advisories.CountAsync();
total.Should().Be(1);
var nvdFeed = await _feedSnapshots.GetBySourceAndIdAsync(nvdSource, "snap-nvd");
var osvFeed = await _feedSnapshots.GetBySourceAndIdAsync(osvSource, "snap-osv");
nvdFeed.Should().NotBeNull();
osvFeed.Should().NotBeNull();
var nvdSnaps = await _advisorySnapshots.GetByFeedSnapshotAsync(nvdFeed!.Id);
var osvSnaps = await _advisorySnapshots.GetByFeedSnapshotAsync(osvFeed!.Id);
nvdSnaps.Should().ContainSingle(s => s.AdvisoryKey == "ADV-PARITY");
osvSnaps.Should().ContainSingle(s => s.AdvisoryKey == "ADV-PARITY");
}
}

View File

@@ -0,0 +1,84 @@
using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;
using StellaOps.Concelier.Storage.Mongo.Advisories;
using StellaOps.Concelier.Storage.Postgres;
using StellaOps.Concelier.Storage.Postgres.Converters;
using StellaOps.Concelier.Storage.Postgres.Converters.Importers;
using StellaOps.Concelier.Storage.Postgres.Repositories;
using Xunit;
namespace StellaOps.Concelier.Storage.Postgres.Tests;
[Collection(ConcelierPostgresCollection.Name)]
public sealed class GhsaImporterMongoTests : IClassFixture<MongoFixture>, IAsyncLifetime
{
private readonly ConcelierPostgresFixture _pg;
private readonly MongoFixture _mongoFixture;
private AdvisoryConversionService _service = default!;
private IAdvisoryRepository _advisories = default!;
private IFeedSnapshotRepository _feedSnapshots = default!;
private IAdvisorySnapshotRepository _advisorySnapshots = default!;
private IMongoCollection<AdvisoryDocument> _collection = default!;
public GhsaImporterMongoTests(ConcelierPostgresFixture pg, MongoFixture mongo)
{
_pg = pg;
_mongoFixture = mongo;
}
public async Task InitializeAsync()
{
await _pg.TruncateAllTablesAsync();
var options = _pg.Fixture.CreateOptions();
options.SchemaName = _pg.SchemaName;
var dataSource = new ConcelierDataSource(Options.Create(options), NullLogger<ConcelierDataSource>.Instance);
_advisories = new AdvisoryRepository(dataSource, NullLogger<AdvisoryRepository>.Instance);
_feedSnapshots = new FeedSnapshotRepository(dataSource, NullLogger<FeedSnapshotRepository>.Instance);
_advisorySnapshots = new AdvisorySnapshotRepository(dataSource, NullLogger<AdvisorySnapshotRepository>.Instance);
_service = new AdvisoryConversionService(_advisories);
_collection = _mongoFixture.Database.GetCollection<AdvisoryDocument>("ghsa_advisories");
await _collection.DeleteManyAsync(FilterDefinition<AdvisoryDocument>.Empty);
await _collection.InsertOneAsync(CreateDoc());
}
public Task DisposeAsync() => Task.CompletedTask;
[Fact]
public async Task ImportSnapshot_PersistsGhsaSnapshot()
{
var importer = new GhsaImporter(_collection, _service, _feedSnapshots, _advisorySnapshots);
var sourceId = Guid.NewGuid();
await importer.ImportSnapshotAsync(sourceId, "ghsa", "ghsa-snap-1", default);
var advisory = await _advisories.GetByKeyAsync("ADV-GHSA-1");
advisory.Should().NotBeNull();
var feed = await _feedSnapshots.GetBySourceAndIdAsync(sourceId, "ghsa-snap-1");
feed.Should().NotBeNull();
var snapshots = await _advisorySnapshots.GetByFeedSnapshotAsync(feed!.Id);
snapshots.Should().ContainSingle(s => s.AdvisoryKey == "ADV-GHSA-1");
}
private static AdvisoryDocument CreateDoc()
{
var payload = new BsonDocument
{
{ "primaryVulnId", "GHSA-0100" },
{ "aliases", new BsonArray { "GHSA-0100" } },
{ "affected", new BsonArray { new BsonDocument { { "ecosystem", "npm" }, { "packageName", "ghsa-pkg" }, { "range", "{}" } } } }
};
return new AdvisoryDocument
{
AdvisoryKey = "ADV-GHSA-1",
Payload = payload,
Modified = DateTime.UtcNow,
Published = DateTime.UtcNow.AddDays(-2)
};
}
}

View File

@@ -0,0 +1,29 @@
using Mongo2Go;
using MongoDB.Driver;
namespace StellaOps.Concelier.Storage.Postgres.Tests;
/// <summary>
/// Provides an ephemeral MongoDB instance for importer tests.
/// </summary>
public sealed class MongoFixture : IAsyncLifetime
{
private MongoDbRunner? _runner;
public IMongoDatabase Database { get; private set; } = default!;
public Task InitializeAsync()
{
_runner = MongoDbRunner.Start(singleNodeReplSet: true, additionalMongodArguments: "--quiet");
var client = new MongoClient(_runner.ConnectionString);
Database = client.GetDatabase("concelier_import_test");
return Task.CompletedTask;
}
public async Task DisposeAsync()
{
if (_runner is not null)
{
await Task.Run(_runner.Dispose);
}
}
}

View File

@@ -0,0 +1,87 @@
using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;
using StellaOps.Concelier.Storage.Mongo.Advisories;
using StellaOps.Concelier.Storage.Postgres;
using StellaOps.Concelier.Storage.Postgres.Converters;
using StellaOps.Concelier.Storage.Postgres.Converters.Importers;
using StellaOps.Concelier.Storage.Postgres.Repositories;
using Xunit;
namespace StellaOps.Concelier.Storage.Postgres.Tests;
[Collection(ConcelierPostgresCollection.Name)]
public sealed class NvdImporterMongoTests : IClassFixture<MongoFixture>, IAsyncLifetime
{
private readonly ConcelierPostgresFixture _pg;
private readonly MongoFixture _mongoFixture;
private AdvisoryConversionService _service = default!;
private IAdvisoryRepository _advisories = default!;
private IFeedSnapshotRepository _feedSnapshots = default!;
private IAdvisorySnapshotRepository _advisorySnapshots = default!;
private IMongoCollection<AdvisoryDocument> _collection = default!;
public NvdImporterMongoTests(ConcelierPostgresFixture pg, MongoFixture mongo)
{
_pg = pg;
_mongoFixture = mongo;
}
public async Task InitializeAsync()
{
await _pg.TruncateAllTablesAsync();
var options = _pg.Fixture.CreateOptions();
options.SchemaName = _pg.SchemaName;
var dataSource = new ConcelierDataSource(Options.Create(options), NullLogger<ConcelierDataSource>.Instance);
_advisories = new AdvisoryRepository(dataSource, NullLogger<AdvisoryRepository>.Instance);
_feedSnapshots = new FeedSnapshotRepository(dataSource, NullLogger<FeedSnapshotRepository>.Instance);
_advisorySnapshots = new AdvisorySnapshotRepository(dataSource, NullLogger<AdvisorySnapshotRepository>.Instance);
_service = new AdvisoryConversionService(_advisories);
_collection = _mongoFixture.Database.GetCollection<AdvisoryDocument>("advisories");
await _collection.DeleteManyAsync(FilterDefinition<AdvisoryDocument>.Empty);
await _collection.InsertOneAsync(CreateDoc());
}
public Task DisposeAsync()
{
return Task.CompletedTask;
}
[Fact]
public async Task ImportSnapshot_PersistsSnapshotsAndAdvisories()
{
var importer = new NvdImporter(_collection, _service, _feedSnapshots, _advisorySnapshots);
var sourceId = Guid.NewGuid();
await importer.ImportSnapshotAsync(sourceId, "nvd", "snap-mongo-1", default);
var advisory = await _advisories.GetByKeyAsync("ADV-4");
advisory.Should().NotBeNull();
var feed = await _feedSnapshots.GetBySourceAndIdAsync(sourceId, "snap-mongo-1");
feed.Should().NotBeNull();
var snapshots = await _advisorySnapshots.GetByFeedSnapshotAsync(feed!.Id);
snapshots.Should().ContainSingle(s => s.AdvisoryKey == "ADV-4");
}
private static AdvisoryDocument CreateDoc()
{
var payload = new BsonDocument
{
{ "primaryVulnId", "CVE-2024-0004" },
{ "aliases", new BsonArray { "CVE-2024-0004" } },
{ "affected", new BsonArray { new BsonDocument { { "ecosystem", "npm" }, { "packageName", "pkg" }, { "range", "{}" } } } }
};
return new AdvisoryDocument
{
AdvisoryKey = "ADV-4",
Payload = payload,
Modified = DateTime.UtcNow,
Published = DateTime.UtcNow.AddDays(-4)
};
}
}

View File

@@ -0,0 +1,84 @@
using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;
using StellaOps.Concelier.Storage.Mongo.Advisories;
using StellaOps.Concelier.Storage.Postgres;
using StellaOps.Concelier.Storage.Postgres.Converters;
using StellaOps.Concelier.Storage.Postgres.Converters.Importers;
using StellaOps.Concelier.Storage.Postgres.Repositories;
using Xunit;
namespace StellaOps.Concelier.Storage.Postgres.Tests;
[Collection(ConcelierPostgresCollection.Name)]
public sealed class OsvImporterMongoTests : IClassFixture<MongoFixture>, IAsyncLifetime
{
private readonly ConcelierPostgresFixture _pg;
private readonly MongoFixture _mongoFixture;
private AdvisoryConversionService _service = default!;
private IAdvisoryRepository _advisories = default!;
private IFeedSnapshotRepository _feedSnapshots = default!;
private IAdvisorySnapshotRepository _advisorySnapshots = default!;
private IMongoCollection<AdvisoryDocument> _collection = default!;
public OsvImporterMongoTests(ConcelierPostgresFixture pg, MongoFixture mongo)
{
_pg = pg;
_mongoFixture = mongo;
}
public async Task InitializeAsync()
{
await _pg.TruncateAllTablesAsync();
var options = _pg.Fixture.CreateOptions();
options.SchemaName = _pg.SchemaName;
var dataSource = new ConcelierDataSource(Options.Create(options), NullLogger<ConcelierDataSource>.Instance);
_advisories = new AdvisoryRepository(dataSource, NullLogger<AdvisoryRepository>.Instance);
_feedSnapshots = new FeedSnapshotRepository(dataSource, NullLogger<FeedSnapshotRepository>.Instance);
_advisorySnapshots = new AdvisorySnapshotRepository(dataSource, NullLogger<AdvisorySnapshotRepository>.Instance);
_service = new AdvisoryConversionService(_advisories);
_collection = _mongoFixture.Database.GetCollection<AdvisoryDocument>("osv_advisories");
await _collection.DeleteManyAsync(FilterDefinition<AdvisoryDocument>.Empty);
await _collection.InsertOneAsync(CreateDoc());
}
public Task DisposeAsync() => Task.CompletedTask;
[Fact]
public async Task ImportSnapshot_PersistsOsvSnapshot()
{
var importer = new OsvImporter(_collection, _service, _feedSnapshots, _advisorySnapshots);
var sourceId = Guid.NewGuid();
await importer.ImportSnapshotAsync(sourceId, "osv-snap-1", default);
var advisory = await _advisories.GetByKeyAsync("ADV-OSV-1");
advisory.Should().NotBeNull();
var feed = await _feedSnapshots.GetBySourceAndIdAsync(sourceId, "osv-snap-1");
feed.Should().NotBeNull();
var snapshots = await _advisorySnapshots.GetByFeedSnapshotAsync(feed!.Id);
snapshots.Should().ContainSingle(s => s.AdvisoryKey == "ADV-OSV-1");
}
private static AdvisoryDocument CreateDoc()
{
var payload = new BsonDocument
{
{ "primaryVulnId", "CVE-2024-0100" },
{ "aliases", new BsonArray { "CVE-2024-0100" } },
{ "affected", new BsonArray { new BsonDocument { { "ecosystem", "npm" }, { "packageName", "osv-pkg" }, { "range", "{}" } } } }
};
return new AdvisoryDocument
{
AdvisoryKey = "ADV-OSV-1",
Payload = payload,
Modified = DateTime.UtcNow,
Published = DateTime.UtcNow.AddDays(-2)
};
}
}

View File

@@ -0,0 +1,82 @@
using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;
using StellaOps.Concelier.Storage.Mongo.Advisories;
using StellaOps.Concelier.Storage.Postgres;
using StellaOps.Concelier.Storage.Postgres.Converters;
using StellaOps.Concelier.Storage.Postgres.Converters.Importers;
using StellaOps.Concelier.Storage.Postgres.Repositories;
using Xunit;
namespace StellaOps.Concelier.Storage.Postgres.Tests;
[Collection(ConcelierPostgresCollection.Name)]
public sealed class ParityRunnerTests : IClassFixture<MongoFixture>, IAsyncLifetime
{
private readonly ConcelierPostgresFixture _pg;
private readonly MongoFixture _mongo;
private AdvisoryConversionService _service = default!;
private IAdvisoryRepository _advisories = default!;
private IFeedSnapshotRepository _feedSnapshots = default!;
private IAdvisorySnapshotRepository _advisorySnapshots = default!;
private IMongoCollection<AdvisoryDocument> _nvd = default!;
private IMongoCollection<AdvisoryDocument> _osv = default!;
public ParityRunnerTests(ConcelierPostgresFixture pg, MongoFixture mongo)
{
_pg = pg;
_mongo = mongo;
}
public async Task InitializeAsync()
{
await _pg.TruncateAllTablesAsync();
var options = _pg.Fixture.CreateOptions();
options.SchemaName = _pg.SchemaName;
var dataSource = new ConcelierDataSource(Options.Create(options), NullLogger<ConcelierDataSource>.Instance);
_advisories = new AdvisoryRepository(dataSource, NullLogger<AdvisoryRepository>.Instance);
_feedSnapshots = new FeedSnapshotRepository(dataSource, NullLogger<FeedSnapshotRepository>.Instance);
_advisorySnapshots = new AdvisorySnapshotRepository(dataSource, NullLogger<AdvisorySnapshotRepository>.Instance);
_service = new AdvisoryConversionService(_advisories);
_nvd = _mongo.Database.GetCollection<AdvisoryDocument>("parity_nvd");
_osv = _mongo.Database.GetCollection<AdvisoryDocument>("parity_osv");
await _nvd.DeleteManyAsync(FilterDefinition<AdvisoryDocument>.Empty);
await _osv.DeleteManyAsync(FilterDefinition<AdvisoryDocument>.Empty);
var payload = new BsonDocument
{
{ "primaryVulnId", "CVE-2024-0700" },
{ "aliases", new BsonArray { "CVE-2024-0700" } },
{ "affected", new BsonArray { new BsonDocument { { "ecosystem", "npm" }, { "packageName", "parity-pkg" }, { "range", "{}" } } } }
};
await _nvd.InsertOneAsync(new AdvisoryDocument { AdvisoryKey = "ADV-PARITY-KEY", Payload = payload, Modified = DateTime.UtcNow, Published = DateTime.UtcNow.AddDays(-1) });
await _osv.InsertOneAsync(new AdvisoryDocument { AdvisoryKey = "ADV-PARITY-KEY", Payload = payload, Modified = DateTime.UtcNow, Published = DateTime.UtcNow.AddDays(-1) });
}
public Task DisposeAsync() => Task.CompletedTask;
[Fact]
public async Task ParityRunner_FindsMatchingKeys()
{
var nvdImporter = new NvdImporter(_nvd, _service, _feedSnapshots, _advisorySnapshots);
var osvImporter = new OsvImporter(_osv, _service, _feedSnapshots, _advisorySnapshots);
var nvdSource = Guid.NewGuid();
var osvSource = Guid.NewGuid();
var nvdFeed = (await nvdImporter.ImportSnapshotAsync(nvdSource, "nvd", "snap-parity-nvd", default));
var osvFeed = (await osvImporter.ImportSnapshotAsync(osvSource, "snap-parity-osv", default));
var runner = new ParityRunner(_advisorySnapshots);
var result = await runner.CompareAsync(nvdFeed.Id, osvFeed.Id, default);
result.Match.Should().BeTrue();
result.MissingInA.Should().BeEmpty();
result.MissingInB.Should().BeEmpty();
}
}