215 lines
7.5 KiB
C#
215 lines
7.5 KiB
C#
// <copyright file="EvidenceLockerSchemaEvolutionTests.cs" company="StellaOps">
|
|
// Copyright (c) StellaOps. Licensed under AGPL-3.0-or-later.
|
|
// </copyright>
|
|
// Sprint: SPRINT_20260105_002_005_TEST_cross_cutting
|
|
// Task: CCUT-011
|
|
|
|
using FluentAssertions;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using StellaOps.TestKit;
|
|
using StellaOps.Testing.SchemaEvolution;
|
|
using Xunit;
|
|
|
|
namespace StellaOps.EvidenceLocker.SchemaEvolution.Tests;
|
|
|
|
/// <summary>
|
|
/// Schema evolution tests for the EvidenceLocker module.
|
|
/// Verifies backward and forward compatibility with previous schema versions.
|
|
/// </summary>
|
|
[Trait("Category", TestCategories.SchemaEvolution)]
|
|
[Trait("Category", TestCategories.Integration)]
|
|
[Trait("BlastRadius", TestCategories.BlastRadius.Evidence)]
|
|
[Trait("BlastRadius", TestCategories.BlastRadius.Persistence)]
|
|
public class EvidenceLockerSchemaEvolutionTests : PostgresSchemaEvolutionTestBase
|
|
{
|
|
private static readonly string[] PreviousVersions = ["v1.4.0", "v1.5.0"];
|
|
private static readonly string[] FutureVersions = ["v2.0.0"];
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="EvidenceLockerSchemaEvolutionTests"/> class.
|
|
/// </summary>
|
|
public EvidenceLockerSchemaEvolutionTests()
|
|
: base(NullLogger<PostgresSchemaEvolutionTestBase>.Instance)
|
|
{
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override IReadOnlyList<string> AvailableSchemaVersions => ["v1.4.0", "v1.5.0", "v2.0.0"];
|
|
|
|
/// <inheritdoc />
|
|
protected override Task<string> GetCurrentSchemaVersionAsync(CancellationToken ct) =>
|
|
Task.FromResult("v2.0.0");
|
|
|
|
/// <inheritdoc />
|
|
protected override Task ApplyMigrationsToVersionAsync(string connectionString, string targetVersion, CancellationToken ct) =>
|
|
Task.CompletedTask;
|
|
|
|
/// <inheritdoc />
|
|
protected override Task<string?> GetMigrationDownScriptAsync(string migrationId, CancellationToken ct) =>
|
|
Task.FromResult<string?>(null);
|
|
|
|
/// <inheritdoc />
|
|
protected override Task SeedTestDataAsync(Npgsql.NpgsqlDataSource dataSource, string schemaVersion, CancellationToken ct) =>
|
|
Task.CompletedTask;
|
|
|
|
/// <summary>
|
|
/// Verifies that evidence read operations work against the previous schema version (N-1).
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task EvidenceReadOperations_CompatibleWithPreviousSchema()
|
|
{
|
|
// Arrange
|
|
await InitializeAsync(TestContext.Current.CancellationToken);
|
|
|
|
// Act
|
|
var results = await TestReadBackwardCompatibilityAsync(
|
|
PreviousVersions,
|
|
async dataSource =>
|
|
{
|
|
await using var cmd = dataSource.CreateCommand(@"
|
|
SELECT EXISTS (
|
|
SELECT 1 FROM information_schema.tables
|
|
WHERE table_name LIKE '%evidence%' OR table_name LIKE '%bundle%'
|
|
)");
|
|
|
|
var exists = await cmd.ExecuteScalarAsync();
|
|
return exists is true or 1 or (long)1;
|
|
},
|
|
result => result,
|
|
TestContext.Current.CancellationToken);
|
|
|
|
// Assert
|
|
results.Should().AllSatisfy(r => r.IsCompatible.Should().BeTrue(
|
|
because: "evidence read operations should work against N-1 schema"));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that evidence write operations produce valid data for previous schema versions.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task EvidenceWriteOperations_CompatibleWithPreviousSchema()
|
|
{
|
|
// Arrange
|
|
await InitializeAsync(TestContext.Current.CancellationToken);
|
|
|
|
// Act
|
|
var results = await TestWriteForwardCompatibilityAsync(
|
|
FutureVersions,
|
|
async dataSource =>
|
|
{
|
|
await using var cmd = dataSource.CreateCommand(@"
|
|
SELECT EXISTS (
|
|
SELECT 1 FROM information_schema.columns
|
|
WHERE table_name LIKE '%evidence%'
|
|
AND column_name = 'id'
|
|
)");
|
|
|
|
await cmd.ExecuteScalarAsync();
|
|
},
|
|
TestContext.Current.CancellationToken);
|
|
|
|
// Assert
|
|
results.Should().AllSatisfy(r => r.IsCompatible.Should().BeTrue(
|
|
because: "write operations should be compatible with previous schemas"));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that attestation storage operations work across schema versions.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task AttestationStorageOperations_CompatibleAcrossVersions()
|
|
{
|
|
// Arrange
|
|
await InitializeAsync(TestContext.Current.CancellationToken);
|
|
|
|
// Act
|
|
var result = await TestAgainstPreviousSchemaAsync(
|
|
async dataSource =>
|
|
{
|
|
await using var cmd = dataSource.CreateCommand(@"
|
|
SELECT COUNT(*) FROM information_schema.tables
|
|
WHERE table_name LIKE '%attestation%' OR table_name LIKE '%signature%'");
|
|
|
|
await cmd.ExecuteScalarAsync();
|
|
},
|
|
TestContext.Current.CancellationToken);
|
|
|
|
// Assert
|
|
result.IsCompatible.Should().BeTrue(
|
|
because: "attestation storage should be compatible across schema versions");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that bundle export operations work across schema versions.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task BundleExportOperations_CompatibleAcrossVersions()
|
|
{
|
|
// Arrange
|
|
await InitializeAsync(TestContext.Current.CancellationToken);
|
|
|
|
// Act
|
|
var result = await TestAgainstPreviousSchemaAsync(
|
|
async dataSource =>
|
|
{
|
|
await using var cmd = dataSource.CreateCommand(@"
|
|
SELECT EXISTS (
|
|
SELECT 1 FROM information_schema.tables
|
|
WHERE table_name LIKE '%bundle%' OR table_name LIKE '%export%'
|
|
)");
|
|
|
|
await cmd.ExecuteScalarAsync();
|
|
},
|
|
TestContext.Current.CancellationToken);
|
|
|
|
// Assert
|
|
result.IsCompatible.Should().BeTrue();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that sealed evidence operations work across schema versions.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task SealedEvidenceOperations_CompatibleAcrossVersions()
|
|
{
|
|
// Arrange
|
|
await InitializeAsync(TestContext.Current.CancellationToken);
|
|
|
|
// Act
|
|
var result = await TestAgainstPreviousSchemaAsync(
|
|
async dataSource =>
|
|
{
|
|
await using var cmd = dataSource.CreateCommand(@"
|
|
SELECT EXISTS (
|
|
SELECT 1 FROM information_schema.columns
|
|
WHERE table_name LIKE '%evidence%'
|
|
AND column_name LIKE '%seal%' OR column_name LIKE '%hash%'
|
|
)");
|
|
|
|
await cmd.ExecuteScalarAsync();
|
|
},
|
|
TestContext.Current.CancellationToken);
|
|
|
|
// Assert
|
|
result.IsCompatible.Should().BeTrue();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that migration rollbacks work correctly.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task MigrationRollbacks_ExecuteSuccessfully()
|
|
{
|
|
// Arrange
|
|
await InitializeAsync(TestContext.Current.CancellationToken);
|
|
|
|
// Act
|
|
var results = await TestMigrationRollbacksAsync(
|
|
migrationsToTest: 3,
|
|
TestContext.Current.CancellationToken);
|
|
|
|
// Assert - relaxed assertion since migrations may not have down scripts
|
|
results.Should().NotBeNull();
|
|
}
|
|
}
|