This commit is contained in:
2025-10-12 23:42:19 +03:00
parent 607e72e2a1
commit 4829b26c53
33 changed files with 3132 additions and 2630 deletions

View File

@@ -62,7 +62,12 @@ public sealed class AdvisoryStorePerformanceTests : IClassFixture<MongoIntegrati
await bootstrapper.InitializeAsync(CancellationToken.None);
var aliasStore = new AliasStore(database, NullLogger<AliasStore>.Instance);
var store = new AdvisoryStore(database, aliasStore, NullLogger<AdvisoryStore>.Instance, TimeProvider.System);
var store = new AdvisoryStore(
database,
aliasStore,
NullLogger<AdvisoryStore>.Instance,
Options.Create(new MongoStorageOptions()),
TimeProvider.System);
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(45));
// Warm up collections (indexes, serialization caches) so perf timings exclude one-time setup work.

View File

@@ -1,9 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging.Abstractions;
using MongoDB.Driver;
using StellaOps.Feedser.Models;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using MongoDB.Driver;
using StellaOps.Feedser.Models;
using StellaOps.Feedser.Storage.Mongo.Advisories;
using StellaOps.Feedser.Storage.Mongo.Aliases;
@@ -25,8 +26,13 @@ public sealed class AdvisoryStoreTests : IClassFixture<MongoIntegrationFixture>
await DropCollectionAsync(MongoStorageDefaults.Collections.Advisory);
await DropCollectionAsync(MongoStorageDefaults.Collections.Alias);
var aliasStore = new AliasStore(_fixture.Database, NullLogger<AliasStore>.Instance);
var store = new AdvisoryStore(_fixture.Database, aliasStore, NullLogger<AdvisoryStore>.Instance, TimeProvider.System);
var aliasStore = new AliasStore(_fixture.Database, NullLogger<AliasStore>.Instance);
var store = new AdvisoryStore(
_fixture.Database,
aliasStore,
NullLogger<AdvisoryStore>.Instance,
Options.Create(new MongoStorageOptions()),
TimeProvider.System);
var advisory = new Advisory(
advisoryKey: "ADV-1",
title: "Sample Advisory",
@@ -62,8 +68,13 @@ public sealed class AdvisoryStoreTests : IClassFixture<MongoIntegrationFixture>
await DropCollectionAsync(MongoStorageDefaults.Collections.Advisory);
await DropCollectionAsync(MongoStorageDefaults.Collections.Alias);
var aliasStore = new AliasStore(_fixture.Database, NullLogger<AliasStore>.Instance);
var store = new AdvisoryStore(_fixture.Database, aliasStore, NullLogger<AdvisoryStore>.Instance, TimeProvider.System);
var aliasStore = new AliasStore(_fixture.Database, NullLogger<AliasStore>.Instance);
var store = new AdvisoryStore(
_fixture.Database,
aliasStore,
NullLogger<AdvisoryStore>.Instance,
Options.Create(new MongoStorageOptions()),
TimeProvider.System);
var recordedAt = new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero);
var provenance = new AdvisoryProvenance("source-x", "mapper", "payload-123", recordedAt);
@@ -144,15 +155,147 @@ public sealed class AdvisoryStoreTests : IClassFixture<MongoIntegrationFixture>
Assert.NotNull(fetchedRange.Primitives);
Assert.Equal(rangePrimitives.SemVer, fetchedRange.Primitives!.SemVer);
Assert.Equal(rangePrimitives.Nevra, fetchedRange.Primitives.Nevra);
Assert.Equal(rangePrimitives.Evr, fetchedRange.Primitives.Evr);
Assert.Equal(rangePrimitives.VendorExtensions, fetchedRange.Primitives.VendorExtensions);
}
private async Task DropCollectionAsync(string collectionName)
{
try
{
await _fixture.Database.DropCollectionAsync(collectionName);
Assert.Equal(rangePrimitives.Evr, fetchedRange.Primitives.Evr);
Assert.Equal(rangePrimitives.VendorExtensions, fetchedRange.Primitives.VendorExtensions);
}
[Fact]
public async Task UpsertAsync_SkipsNormalizedVersionsWhenFeatureDisabled()
{
await DropCollectionAsync(MongoStorageDefaults.Collections.Advisory);
await DropCollectionAsync(MongoStorageDefaults.Collections.Alias);
var aliasStore = new AliasStore(_fixture.Database, NullLogger<AliasStore>.Instance);
var store = new AdvisoryStore(
_fixture.Database,
aliasStore,
NullLogger<AdvisoryStore>.Instance,
Options.Create(new MongoStorageOptions { EnableSemVerStyle = false }),
TimeProvider.System);
var advisory = CreateNormalizedAdvisory("ADV-NORM-DISABLED");
await store.UpsertAsync(advisory, CancellationToken.None);
var document = await _fixture.Database
.GetCollection<AdvisoryDocument>(MongoStorageDefaults.Collections.Advisory)
.Find(x => x.AdvisoryKey == advisory.AdvisoryKey)
.FirstOrDefaultAsync();
Assert.NotNull(document);
Assert.True(document!.NormalizedVersions is null || document.NormalizedVersions.Count == 0);
}
[Fact]
public async Task UpsertAsync_PopulatesNormalizedVersionsWhenFeatureEnabled()
{
await DropCollectionAsync(MongoStorageDefaults.Collections.Advisory);
await DropCollectionAsync(MongoStorageDefaults.Collections.Alias);
var aliasStore = new AliasStore(_fixture.Database, NullLogger<AliasStore>.Instance);
var store = new AdvisoryStore(
_fixture.Database,
aliasStore,
NullLogger<AdvisoryStore>.Instance,
Options.Create(new MongoStorageOptions { EnableSemVerStyle = true }),
TimeProvider.System);
var advisory = CreateNormalizedAdvisory("ADV-NORM-ENABLED");
await store.UpsertAsync(advisory, CancellationToken.None);
var document = await _fixture.Database
.GetCollection<AdvisoryDocument>(MongoStorageDefaults.Collections.Advisory)
.Find(x => x.AdvisoryKey == advisory.AdvisoryKey)
.FirstOrDefaultAsync();
Assert.NotNull(document);
var normalizedCollection = document!.NormalizedVersions;
Assert.NotNull(normalizedCollection);
var normalized = Assert.Single(normalizedCollection!);
Assert.Equal("pkg:npm/example", normalized.PackageId);
Assert.Equal(AffectedPackageTypes.SemVer, normalized.PackageType);
Assert.Equal(NormalizedVersionSchemes.SemVer, normalized.Scheme);
Assert.Equal(NormalizedVersionRuleTypes.Range, normalized.Type);
Assert.Equal("range", normalized.Style);
Assert.Equal("1.0.0", normalized.Min);
Assert.True(normalized.MinInclusive);
Assert.Equal("2.0.0", normalized.Max);
Assert.False(normalized.MaxInclusive);
Assert.Null(normalized.Value);
Assert.Equal("ghsa:pkg:npm/example", normalized.Notes);
Assert.Equal("range-decision", normalized.DecisionReason);
Assert.Equal(">= 1.0.0 < 2.0.0", normalized.Constraint);
Assert.Equal("ghsa", normalized.Source);
Assert.Equal(new DateTime(2025, 10, 9, 0, 0, 0, DateTimeKind.Utc), normalized.RecordedAtUtc);
}
private static Advisory CreateNormalizedAdvisory(string advisoryKey)
{
var recordedAt = new DateTimeOffset(2025, 10, 9, 0, 0, 0, TimeSpan.Zero);
var rangeProvenance = new AdvisoryProvenance(
source: "ghsa",
kind: "affected-range",
value: "pkg:npm/example",
recordedAt: recordedAt,
fieldMask: new[] { "affectedpackages[].versionranges[]" },
decisionReason: "range-decision");
var semverPrimitive = new SemVerPrimitive(
Introduced: "1.0.0",
IntroducedInclusive: true,
Fixed: "2.0.0",
FixedInclusive: false,
LastAffected: null,
LastAffectedInclusive: false,
ConstraintExpression: ">= 1.0.0 < 2.0.0");
var normalizedRule = semverPrimitive.ToNormalizedVersionRule("ghsa:pkg:npm/example")!;
var versionRange = new AffectedVersionRange(
rangeKind: "semver",
introducedVersion: "1.0.0",
fixedVersion: "2.0.0",
lastAffectedVersion: null,
rangeExpression: ">= 1.0.0 < 2.0.0",
provenance: rangeProvenance,
primitives: new RangePrimitives(semverPrimitive, null, null, null));
var package = new AffectedPackage(
type: AffectedPackageTypes.SemVer,
identifier: "pkg:npm/example",
platform: "npm",
versionRanges: new[] { versionRange },
statuses: Array.Empty<AffectedPackageStatus>(),
provenance: new[] { rangeProvenance },
normalizedVersions: new[] { normalizedRule });
var advisoryProvenance = new AdvisoryProvenance(
source: "ghsa",
kind: "document",
value: advisoryKey,
recordedAt: recordedAt,
fieldMask: new[] { "advisory" },
decisionReason: "document-decision");
return new Advisory(
advisoryKey: advisoryKey,
title: "Normalized advisory",
summary: "Contains normalized versions for storage testing.",
language: "en",
published: recordedAt,
modified: recordedAt,
severity: "medium",
exploitKnown: false,
aliases: new[] { $"{advisoryKey}-ALIAS" },
references: Array.Empty<AdvisoryReference>(),
affectedPackages: new[] { package },
cvssMetrics: Array.Empty<CvssMetric>(),
provenance: new[] { advisoryProvenance });
}
private async Task DropCollectionAsync(string collectionName)
{
try
{
await _fixture.Database.DropCollectionAsync(collectionName);
}
catch (MongoDB.Driver.MongoCommandException ex) when (ex.CodeName == "NamespaceNotFound" || ex.Message.Contains("ns not found", StringComparison.OrdinalIgnoreCase))
{

View File

@@ -0,0 +1,97 @@
using System;
using System.Linq;
using System.Threading;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;
using StellaOps.Feedser.Storage.Mongo;
using StellaOps.Feedser.Storage.Mongo.Migrations;
using Xunit;
namespace StellaOps.Feedser.Storage.Mongo.Tests;
[Collection("mongo-fixture")]
public sealed class MongoBootstrapperTests : IClassFixture<MongoIntegrationFixture>
{
private readonly MongoIntegrationFixture _fixture;
public MongoBootstrapperTests(MongoIntegrationFixture fixture)
{
_fixture = fixture;
}
[Fact]
public async Task InitializeAsync_CreatesNormalizedIndexesWhenSemVerStyleEnabled()
{
var databaseName = $"feedser-bootstrap-semver-{Guid.NewGuid():N}";
var database = _fixture.Client.GetDatabase(databaseName);
try
{
var runner = new MongoMigrationRunner(
database,
Array.Empty<IMongoMigration>(),
NullLogger<MongoMigrationRunner>.Instance,
TimeProvider.System);
var bootstrapper = new MongoBootstrapper(
database,
Options.Create(new MongoStorageOptions { EnableSemVerStyle = true }),
NullLogger<MongoBootstrapper>.Instance,
runner);
await bootstrapper.InitializeAsync(CancellationToken.None);
var indexCursor = await database
.GetCollection<BsonDocument>(MongoStorageDefaults.Collections.Advisory)
.Indexes
.ListAsync();
var indexNames = (await indexCursor.ToListAsync()).Select(x => x["name"].AsString).ToArray();
Assert.Contains("advisory_normalizedVersions_pkg_scheme_type", indexNames);
Assert.Contains("advisory_normalizedVersions_value", indexNames);
}
finally
{
await _fixture.Client.DropDatabaseAsync(databaseName);
}
}
[Fact]
public async Task InitializeAsync_DoesNotCreateNormalizedIndexesWhenFeatureDisabled()
{
var databaseName = $"feedser-bootstrap-no-semver-{Guid.NewGuid():N}";
var database = _fixture.Client.GetDatabase(databaseName);
try
{
var runner = new MongoMigrationRunner(
database,
Array.Empty<IMongoMigration>(),
NullLogger<MongoMigrationRunner>.Instance,
TimeProvider.System);
var bootstrapper = new MongoBootstrapper(
database,
Options.Create(new MongoStorageOptions { EnableSemVerStyle = false }),
NullLogger<MongoBootstrapper>.Instance,
runner);
await bootstrapper.InitializeAsync(CancellationToken.None);
var indexCursor = await database
.GetCollection<BsonDocument>(MongoStorageDefaults.Collections.Advisory)
.Indexes
.ListAsync();
var indexNames = (await indexCursor.ToListAsync()).Select(x => x["name"].AsString).ToArray();
Assert.DoesNotContain("advisory_normalizedVersions_pkg_scheme_type", indexNames);
Assert.DoesNotContain("advisory_normalizedVersions_value", indexNames);
}
finally
{
await _fixture.Client.DropDatabaseAsync(databaseName);
}
}
}