up
This commit is contained in:
@@ -11,6 +11,6 @@
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Excititor.Storage.Mongo/StellaOps.Excititor.Storage.Mongo.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Excititor.Core/StellaOps.Excititor.Core.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Excititor.Policy/StellaOps.Excititor.Policy.csproj" />
|
||||
<ProjectReference Include="../../../Concelier/__Libraries/StellaOps.Concelier.Storage.Mongo/StellaOps.Concelier.Storage.Mongo.csproj" />
|
||||
<ProjectReference Include="../../../Concelier/__Libraries/StellaOps.Concelier.Models/StellaOps.Concelier.Models.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -15,6 +15,8 @@ public sealed class ExcititorPostgresFixture : PostgresIntegrationFixture, IColl
|
||||
=> typeof(ExcititorDataSource).Assembly;
|
||||
|
||||
protected override string GetModuleName() => "Excititor";
|
||||
|
||||
protected override string? GetResourcePrefix() => "Migrations";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Excititor.Core.Observations;
|
||||
using StellaOps.Excititor.Storage.Postgres;
|
||||
using StellaOps.Excititor.Storage.Postgres.Repositories;
|
||||
using StellaOps.Infrastructure.Postgres.Options;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Excititor.Storage.Postgres.Tests;
|
||||
|
||||
[Collection(ExcititorPostgresCollection.Name)]
|
||||
public sealed class PostgresAppendOnlyLinksetStoreTests : IAsyncLifetime
|
||||
{
|
||||
private readonly ExcititorPostgresFixture _fixture;
|
||||
private readonly PostgresAppendOnlyLinksetStore _store;
|
||||
private readonly ExcititorDataSource _dataSource;
|
||||
|
||||
public PostgresAppendOnlyLinksetStoreTests(ExcititorPostgresFixture fixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
var options = Options.Create(new PostgresOptions
|
||||
{
|
||||
ConnectionString = fixture.ConnectionString,
|
||||
SchemaName = fixture.SchemaName,
|
||||
AutoMigrate = false
|
||||
});
|
||||
|
||||
_dataSource = new ExcititorDataSource(options, NullLogger<ExcititorDataSource>.Instance);
|
||||
_store = new PostgresAppendOnlyLinksetStore(_dataSource, NullLogger<PostgresAppendOnlyLinksetStore>.Instance);
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
await _fixture.Fixture.RunMigrationsFromAssemblyAsync(
|
||||
typeof(ExcititorDataSource).Assembly,
|
||||
moduleName: "Excititor",
|
||||
resourcePrefix: "Migrations",
|
||||
cancellationToken: CancellationToken.None);
|
||||
|
||||
// Ensure migration applied even if runner skipped; execute embedded SQL directly as fallback.
|
||||
var resourceName = typeof(ExcititorDataSource).Assembly
|
||||
.GetManifestResourceNames()
|
||||
.FirstOrDefault(n => n.EndsWith("001_initial_schema.sql", StringComparison.OrdinalIgnoreCase));
|
||||
await using var stream = resourceName is null
|
||||
? null
|
||||
: typeof(ExcititorDataSource).Assembly.GetManifestResourceStream(resourceName);
|
||||
if (stream is not null)
|
||||
{
|
||||
using var reader = new StreamReader(stream);
|
||||
var sql = await reader.ReadToEndAsync();
|
||||
await _fixture.Fixture.ExecuteSqlAsync(sql);
|
||||
}
|
||||
|
||||
await _fixture.TruncateAllTablesAsync();
|
||||
}
|
||||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
await _dataSource.DisposeAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AppendObservation_CreatesLinksetAndDedupes()
|
||||
{
|
||||
var tenant = "tenant-a";
|
||||
var vuln = "CVE-2025-1234";
|
||||
var product = "pkg:nuget/demo@1.0.0";
|
||||
var scope = VexProductScope.Unknown(product);
|
||||
var observation = new VexLinksetObservationRefModel("obs-1", "provider-a", "not_affected", 0.9);
|
||||
|
||||
var first = await _store.AppendObservationAsync(tenant, vuln, product, observation, scope, CancellationToken.None);
|
||||
|
||||
first.WasCreated.Should().BeTrue();
|
||||
first.ObservationsAdded.Should().Be(1);
|
||||
first.SequenceNumber.Should().BeGreaterThan(0);
|
||||
first.Linkset.Observations.Should().HaveCount(1);
|
||||
|
||||
var second = await _store.AppendObservationAsync(tenant, vuln, product, observation, scope, CancellationToken.None);
|
||||
|
||||
second.HadChanges.Should().BeFalse();
|
||||
second.Linkset.Observations.Should().HaveCount(1);
|
||||
second.SequenceNumber.Should().Be(first.SequenceNumber);
|
||||
|
||||
var mutations = await _store.GetMutationLogAsync(tenant, first.Linkset.LinksetId, CancellationToken.None);
|
||||
mutations.Select(m => m.SequenceNumber).Should().BeInAscendingOrder();
|
||||
mutations.Should().HaveCount(2); // created + observation
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AppendBatch_AppendsMultipleAndMaintainsOrder()
|
||||
{
|
||||
var tenant = "tenant-b";
|
||||
var vuln = "CVE-2025-2000";
|
||||
var product = "pkg:maven/demo/demo@2.0.0";
|
||||
var scope = VexProductScope.Unknown(product);
|
||||
var observations = new[]
|
||||
{
|
||||
new VexLinksetObservationRefModel("obs-2", "provider-b", "affected", 0.7),
|
||||
new VexLinksetObservationRefModel("obs-1", "provider-a", "affected", 0.8),
|
||||
new VexLinksetObservationRefModel("obs-3", "provider-a", "fixed", 0.9)
|
||||
};
|
||||
|
||||
var result = await _store.AppendObservationsBatchAsync(tenant, vuln, product, observations, scope, CancellationToken.None);
|
||||
|
||||
result.Linkset.Observations.Should().HaveCount(3);
|
||||
result.Linkset.Observations
|
||||
.Select(o => $"{o.ProviderId}:{o.Status}:{o.ObservationId}")
|
||||
.Should()
|
||||
.ContainInOrder(
|
||||
"provider-a:affected:obs-1",
|
||||
"provider-a:fixed:obs-3",
|
||||
"provider-b:affected:obs-2");
|
||||
result.SequenceNumber.Should().BeGreaterThan(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AppendDisagreement_RegistersConflictAndCounts()
|
||||
{
|
||||
var tenant = "tenant-c";
|
||||
var vuln = "CVE-2025-3000";
|
||||
var product = "pkg:deb/debian/demo@1.2.3";
|
||||
var disagreement = new VexObservationDisagreement("provider-c", "not_affected", "component_not_present", 0.6);
|
||||
|
||||
var result = await _store.AppendDisagreementAsync(tenant, vuln, product, disagreement, CancellationToken.None);
|
||||
|
||||
result.Linkset.HasConflicts.Should().BeTrue();
|
||||
result.SequenceNumber.Should().BeGreaterThan(0);
|
||||
|
||||
var conflicts = await _store.FindWithConflictsAsync(tenant, limit: 10, CancellationToken.None);
|
||||
conflicts.Should().ContainSingle(ls => ls.LinksetId == result.Linkset.LinksetId);
|
||||
|
||||
var conflictCount = await _store.CountWithConflictsAsync(tenant, CancellationToken.None);
|
||||
conflictCount.Should().Be(1);
|
||||
}
|
||||
}
|
||||
@@ -10,9 +10,16 @@
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Remove="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Remove="xunit" />
|
||||
<PackageReference Remove="xunit.runner.visualstudio" />
|
||||
<PackageReference Remove="coverlet.collector" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
|
||||
<PackageReference Include="Moq" Version="4.20.70" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||
@@ -27,6 +34,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\__Libraries\StellaOps.Excititor.Storage.Postgres\StellaOps.Excititor.Storage.Postgres.csproj" />
|
||||
<ProjectReference Include="..\..\__Libraries\StellaOps.Excititor.Core\StellaOps.Excititor.Core.csproj" />
|
||||
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Infrastructure.Postgres.Testing\StellaOps.Infrastructure.Postgres.Testing.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user