Add tests for SBOM generation determinism across multiple formats

- Created `StellaOps.TestKit.Tests` project for unit tests related to determinism.
- Implemented `DeterminismManifestTests` to validate deterministic output for canonical bytes and strings, file read/write operations, and error handling for invalid schema versions.
- Added `SbomDeterminismTests` to ensure identical inputs produce consistent SBOMs across SPDX 3.0.1 and CycloneDX 1.6/1.7 formats, including parallel execution tests.
- Updated project references in `StellaOps.Integration.Determinism` to include the new determinism testing library.
This commit is contained in:
master
2025-12-23 18:56:12 +02:00
committed by StellaOps Bot
parent 7ac70ece71
commit 491e883653
409 changed files with 23797 additions and 17779 deletions

View File

@@ -231,6 +231,7 @@ public sealed class RoaringImpactIndexTests
await index.RemoveAsync(digest1);
var snapshot = await index.CreateSnapshotAsync();
snapshot.SnapshotId.Should().MatchRegex("^snap-[0-9a-f]{64}$");
var restored = new RoaringImpactIndex(NullLogger<RoaringImpactIndex>.Instance);
await restored.RestoreSnapshotAsync(snapshot);

View File

@@ -1,9 +1,20 @@
// -----------------------------------------------------------------------------
// SchedulerPostgresFixture.cs
// Sprint: SPRINT_5100_0007_0004_storage_harness
// Task: STOR-HARNESS-011
// Description: Scheduler PostgreSQL test fixture using TestKit
// -----------------------------------------------------------------------------
using System.Reflection;
using Npgsql;
using StellaOps.Infrastructure.Postgres.Testing;
using StellaOps.Scheduler.Storage.Postgres;
using Xunit;
// Type aliases to disambiguate TestKit and Infrastructure.Postgres.Testing fixtures
using TestKitPostgresFixture = StellaOps.TestKit.Fixtures.PostgresFixture;
using TestKitPostgresIsolationMode = StellaOps.TestKit.Fixtures.PostgresIsolationMode;
namespace StellaOps.Scheduler.Storage.Postgres.Tests;
/// <summary>
@@ -65,3 +76,68 @@ public sealed class SchedulerPostgresCollection : ICollectionFixture<SchedulerPo
{
public const string Name = "SchedulerPostgres";
}
/// <summary>
/// TestKit-based PostgreSQL fixture for Scheduler storage tests.
/// Uses TestKit's PostgresFixture for enhanced isolation modes.
/// </summary>
public sealed class SchedulerTestKitPostgresFixture : IAsyncLifetime
{
private TestKitPostgresFixture _fixture = null!;
private Assembly MigrationAssembly => typeof(SchedulerDataSource).Assembly;
public TestKitPostgresFixture Fixture => _fixture;
public string ConnectionString => _fixture.ConnectionString;
public async Task InitializeAsync()
{
_fixture = new TestKitPostgresFixture(TestKitPostgresIsolationMode.Truncation);
await _fixture.InitializeAsync();
await _fixture.ApplyMigrationsFromAssemblyAsync(MigrationAssembly);
}
public Task DisposeAsync() => _fixture.DisposeAsync();
public async Task TruncateAllTablesAsync(CancellationToken cancellationToken = default)
{
await _fixture.TruncateAllTablesAsync(cancellationToken);
// Scheduler migrations create the canonical `scheduler.*` schema explicitly
await using var connection = new NpgsqlConnection(ConnectionString);
await connection.OpenAsync(cancellationToken).ConfigureAwait(false);
const string listTablesSql = """
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'scheduler'
AND table_type = 'BASE TABLE';
""";
var tables = new List<string>();
await using (var command = new NpgsqlCommand(listTablesSql, connection))
await using (var reader = await command.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false))
{
while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false))
{
tables.Add(reader.GetString(0));
}
}
if (tables.Count > 0)
{
var qualified = tables.Select(static t => $"scheduler.\"{t}\"");
var truncateSql = $"TRUNCATE TABLE {string.Join(", ", qualified)} RESTART IDENTITY CASCADE;";
await using var truncateCommand = new NpgsqlCommand(truncateSql, connection);
await truncateCommand.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
}
}
}
/// <summary>
/// Collection definition for Scheduler TestKit PostgreSQL tests.
/// </summary>
[CollectionDefinition(SchedulerTestKitPostgresCollection.Name)]
public sealed class SchedulerTestKitPostgresCollection : ICollectionFixture<SchedulerTestKitPostgresFixture>
{
public const string Name = "SchedulerTestKitPostgres";
}

View File

@@ -18,6 +18,6 @@
<ItemGroup>
<ProjectReference Include="..\..\__Libraries\StellaOps.Scheduler.Storage.Postgres\StellaOps.Scheduler.Storage.Postgres.csproj" />
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Infrastructure.Postgres.Testing\StellaOps.Infrastructure.Postgres.Testing.csproj" />
<ProjectReference Include="..\..\..\__Libraries\StellaOps.TestKit\StellaOps.TestKit.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Scheduler.Models;
@@ -130,6 +131,47 @@ public sealed class GraphJobServiceTests
Assert.Equal("oras://cartographer/bundle-v2", resultUri);
}
[Fact]
public async Task CreateBuildJob_NormalizesSbomDigest()
{
var store = new TrackingGraphJobStore();
var clock = new FixedClock(FixedTime);
var publisher = new RecordingPublisher();
var webhook = new RecordingWebhookClient();
var service = new GraphJobService(store, clock, publisher, webhook);
var request = new GraphBuildJobRequest
{
SbomId = "sbom-alpha",
SbomVersionId = "sbom-alpha-v1",
SbomDigest = " SHA256:" + new string('A', 64) + " ",
};
var created = await service.CreateBuildJobAsync("tenant-alpha", request, CancellationToken.None);
Assert.Equal("sha256:" + new string('a', 64), created.SbomDigest);
}
[Fact]
public async Task CreateBuildJob_RejectsDigestWithoutPrefix()
{
var store = new TrackingGraphJobStore();
var clock = new FixedClock(FixedTime);
var publisher = new RecordingPublisher();
var webhook = new RecordingWebhookClient();
var service = new GraphJobService(store, clock, publisher, webhook);
var request = new GraphBuildJobRequest
{
SbomId = "sbom-alpha",
SbomVersionId = "sbom-alpha-v1",
SbomDigest = new string('a', 64),
};
var ex = await Assert.ThrowsAsync<ValidationException>(
async () => await service.CreateBuildJobAsync("tenant-alpha", request, CancellationToken.None));
Assert.Contains("sha256:", ex.Message, StringComparison.Ordinal);
}
private static GraphBuildJob CreateBuildJob()
{
var digest = "sha256:" + new string('a', 64);