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:
@@ -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);
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ Local status mirror for orchestration sprints to keep doc and code views aligned
|
||||
| 0151 | ORCH-AIRGAP-57-001 | BLOCKED | Await upstream 56-002. |
|
||||
| 0151 | ORCH-AIRGAP-58-001 | BLOCKED | Await upstream 57-001. |
|
||||
| 0151 | ORCH-SVC-32-001 | DONE | Service bootstrap + initial schema/migrations. |
|
||||
| 0151 | ORCH-GAPS-151-016 | DONE | OR1–OR10 gaps: canonical hashes, replay inputs.lock, heartbeat ordering, log/artifact integrity. |
|
||||
| 0152 | ORCH-SVC-32-002…37-101 | DONE | See `src/Orchestrator/StellaOps.Orchestrator/TASKS.md` for per-task detail. |
|
||||
|
||||
Last synced: 2025-11-30 (UTC).
|
||||
Last synced: 2025-12-03 (UTC).
|
||||
|
||||
@@ -24,11 +24,15 @@ public sealed class EvaluationRunRepository : RepositoryBase<PolicyDataSource>,
|
||||
const string sql = """
|
||||
INSERT INTO policy.evaluation_runs (
|
||||
id, tenant_id, project_id, artifact_id, pack_id, pack_version,
|
||||
risk_profile_id, status, input_hash, metadata, created_by
|
||||
risk_profile_id, status, result, score,
|
||||
findings_count, critical_count, high_count, medium_count, low_count,
|
||||
input_hash, metadata, duration_ms, error_message, created_by
|
||||
)
|
||||
VALUES (
|
||||
@id, @tenant_id, @project_id, @artifact_id, @pack_id, @pack_version,
|
||||
@risk_profile_id, @status, @input_hash, @metadata::jsonb, @created_by
|
||||
@risk_profile_id, @status, @result, @score,
|
||||
@findings_count, @critical_count, @high_count, @medium_count, @low_count,
|
||||
@input_hash, @metadata::jsonb, @duration_ms, @error_message, @created_by
|
||||
)
|
||||
RETURNING *
|
||||
""";
|
||||
@@ -45,8 +49,17 @@ public sealed class EvaluationRunRepository : RepositoryBase<PolicyDataSource>,
|
||||
AddParameter(command, "pack_version", run.PackVersion);
|
||||
AddParameter(command, "risk_profile_id", run.RiskProfileId);
|
||||
AddParameter(command, "status", StatusToString(run.Status));
|
||||
AddParameter(command, "result", run.Result.HasValue ? ResultToString(run.Result.Value) : null);
|
||||
AddParameter(command, "score", run.Score);
|
||||
AddParameter(command, "findings_count", run.FindingsCount);
|
||||
AddParameter(command, "critical_count", run.CriticalCount);
|
||||
AddParameter(command, "high_count", run.HighCount);
|
||||
AddParameter(command, "medium_count", run.MediumCount);
|
||||
AddParameter(command, "low_count", run.LowCount);
|
||||
AddParameter(command, "input_hash", run.InputHash);
|
||||
AddJsonbParameter(command, "metadata", run.Metadata);
|
||||
AddParameter(command, "duration_ms", run.DurationMs);
|
||||
AddParameter(command, "error_message", run.ErrorMessage);
|
||||
AddParameter(command, "created_by", run.CreatedBy);
|
||||
|
||||
await using var reader = await command.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -12,7 +12,11 @@ public sealed class EvaluationRunRepositoryTests : IAsyncLifetime
|
||||
{
|
||||
private readonly PolicyPostgresFixture _fixture;
|
||||
private readonly EvaluationRunRepository _repository;
|
||||
private readonly PackRepository _packRepository;
|
||||
private readonly PackVersionRepository _packVersionRepository;
|
||||
private readonly string _tenantId = Guid.NewGuid().ToString();
|
||||
private readonly Guid _packId = Guid.NewGuid();
|
||||
private const int SeedPackVersion = 1;
|
||||
|
||||
public EvaluationRunRepositoryTests(PolicyPostgresFixture fixture)
|
||||
{
|
||||
@@ -21,10 +25,41 @@ public sealed class EvaluationRunRepositoryTests : IAsyncLifetime
|
||||
var options = fixture.Fixture.CreateOptions();
|
||||
options.SchemaName = fixture.SchemaName;
|
||||
var dataSource = new PolicyDataSource(Options.Create(options), NullLogger<PolicyDataSource>.Instance);
|
||||
_packRepository = new PackRepository(dataSource, NullLogger<PackRepository>.Instance);
|
||||
_packVersionRepository = new PackVersionRepository(dataSource, NullLogger<PackVersionRepository>.Instance);
|
||||
_repository = new EvaluationRunRepository(dataSource, NullLogger<EvaluationRunRepository>.Instance);
|
||||
}
|
||||
|
||||
public Task InitializeAsync() => _fixture.TruncateAllTablesAsync();
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
await _fixture.TruncateAllTablesAsync();
|
||||
|
||||
var pack = new PackEntity
|
||||
{
|
||||
Id = _packId,
|
||||
TenantId = _tenantId,
|
||||
Name = "eval-pack",
|
||||
DisplayName = "Evaluation Pack",
|
||||
ActiveVersion = SeedPackVersion,
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
UpdatedAt = DateTimeOffset.UtcNow,
|
||||
CreatedBy = "tests"
|
||||
};
|
||||
await _packRepository.CreateAsync(pack);
|
||||
|
||||
var packVersion = new PackVersionEntity
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
PackId = _packId,
|
||||
Version = SeedPackVersion,
|
||||
RulesHash = "seed-hash",
|
||||
IsPublished = true,
|
||||
PublishedAt = DateTimeOffset.UtcNow,
|
||||
PublishedBy = "tests",
|
||||
CreatedBy = "tests"
|
||||
};
|
||||
await _packVersionRepository.CreateAsync(packVersion);
|
||||
}
|
||||
public Task DisposeAsync() => Task.CompletedTask;
|
||||
|
||||
[Fact]
|
||||
@@ -37,8 +72,8 @@ public sealed class EvaluationRunRepositoryTests : IAsyncLifetime
|
||||
TenantId = _tenantId,
|
||||
ProjectId = "project-123",
|
||||
ArtifactId = "registry.example.com/app:v1.0",
|
||||
PackId = Guid.NewGuid(),
|
||||
PackVersion = 1,
|
||||
PackId = _packId,
|
||||
PackVersion = SeedPackVersion,
|
||||
Status = EvaluationStatus.Pending
|
||||
};
|
||||
|
||||
@@ -204,6 +239,8 @@ public sealed class EvaluationRunRepositoryTests : IAsyncLifetime
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
TenantId = _tenantId,
|
||||
PackId = _packId,
|
||||
PackVersion = SeedPackVersion,
|
||||
Status = EvaluationStatus.Completed,
|
||||
Result = EvaluationResult.Pass,
|
||||
Score = 100,
|
||||
@@ -215,6 +252,8 @@ public sealed class EvaluationRunRepositoryTests : IAsyncLifetime
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
TenantId = _tenantId,
|
||||
PackId = _packId,
|
||||
PackVersion = SeedPackVersion,
|
||||
Status = EvaluationStatus.Completed,
|
||||
Result = EvaluationResult.Fail,
|
||||
Score = 50,
|
||||
@@ -245,6 +284,8 @@ public sealed class EvaluationRunRepositoryTests : IAsyncLifetime
|
||||
Id = Guid.NewGuid(),
|
||||
TenantId = _tenantId,
|
||||
ProjectId = projectId,
|
||||
PackId = _packId,
|
||||
PackVersion = SeedPackVersion,
|
||||
Status = EvaluationStatus.Pending
|
||||
};
|
||||
}
|
||||
|
||||
@@ -170,6 +170,8 @@ public sealed class PolicyAuditRepositoryTests : IAsyncLifetime
|
||||
public async Task DeleteOld_RemovesOldAudits()
|
||||
{
|
||||
// Arrange
|
||||
// Clear any cross-test residue defensively.
|
||||
await _repository.DeleteOldAsync(DateTimeOffset.MaxValue);
|
||||
await _repository.CreateAsync(CreateAudit("old-action"));
|
||||
|
||||
// Act - Delete audits older than future date
|
||||
|
||||
@@ -12,7 +12,11 @@ public sealed class RuleRepositoryTests : IAsyncLifetime
|
||||
{
|
||||
private readonly PolicyPostgresFixture _fixture;
|
||||
private readonly RuleRepository _repository;
|
||||
private readonly PackRepository _packRepository;
|
||||
private readonly PackVersionRepository _packVersionRepository;
|
||||
private readonly Guid _packId = Guid.NewGuid();
|
||||
private readonly Guid _packVersionId = Guid.NewGuid();
|
||||
private readonly string _tenantId = Guid.NewGuid().ToString();
|
||||
|
||||
public RuleRepositoryTests(PolicyPostgresFixture fixture)
|
||||
{
|
||||
@@ -21,10 +25,46 @@ public sealed class RuleRepositoryTests : IAsyncLifetime
|
||||
var options = fixture.Fixture.CreateOptions();
|
||||
options.SchemaName = fixture.SchemaName;
|
||||
var dataSource = new PolicyDataSource(Options.Create(options), NullLogger<PolicyDataSource>.Instance);
|
||||
|
||||
_packRepository = new PackRepository(dataSource, NullLogger<PackRepository>.Instance);
|
||||
_packVersionRepository = new PackVersionRepository(dataSource, NullLogger<PackVersionRepository>.Instance);
|
||||
_repository = new RuleRepository(dataSource, NullLogger<RuleRepository>.Instance);
|
||||
}
|
||||
|
||||
public Task InitializeAsync() => _fixture.TruncateAllTablesAsync();
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
await _fixture.TruncateAllTablesAsync();
|
||||
|
||||
var pack = new PackEntity
|
||||
{
|
||||
Id = _packId,
|
||||
TenantId = _tenantId,
|
||||
Name = "test-pack",
|
||||
DisplayName = "Test Pack",
|
||||
Description = "Seed pack for rule tests",
|
||||
ActiveVersion = 1,
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
UpdatedAt = DateTimeOffset.UtcNow,
|
||||
CreatedBy = "tests"
|
||||
};
|
||||
|
||||
await _packRepository.CreateAsync(pack);
|
||||
|
||||
var packVersion = new PackVersionEntity
|
||||
{
|
||||
Id = _packVersionId,
|
||||
PackId = _packId,
|
||||
Version = 1,
|
||||
Description = "seed version",
|
||||
RulesHash = "hash",
|
||||
IsPublished = true,
|
||||
PublishedAt = DateTimeOffset.UtcNow,
|
||||
PublishedBy = "tests",
|
||||
CreatedBy = "tests"
|
||||
};
|
||||
|
||||
await _packVersionRepository.CreateAsync(packVersion);
|
||||
}
|
||||
public Task DisposeAsync() => Task.CompletedTask;
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user