// // Copyright (c) StellaOps. Licensed under AGPL-3.0-or-later. // // 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; /// /// Schema evolution tests for the EvidenceLocker module. /// Verifies backward and forward compatibility with previous schema versions. /// [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"]; /// /// Initializes a new instance of the class. /// public EvidenceLockerSchemaEvolutionTests() : base(NullLogger.Instance) { } /// protected override IReadOnlyList AvailableSchemaVersions => ["v1.4.0", "v1.5.0", "v2.0.0"]; /// protected override Task GetCurrentSchemaVersionAsync(CancellationToken ct) => Task.FromResult("v2.0.0"); /// protected override Task ApplyMigrationsToVersionAsync(string connectionString, string targetVersion, CancellationToken ct) => Task.CompletedTask; /// protected override Task GetMigrationDownScriptAsync(string migrationId, CancellationToken ct) => Task.FromResult(null); /// protected override Task SeedTestDataAsync(Npgsql.NpgsqlDataSource dataSource, string schemaVersion, CancellationToken ct) => Task.CompletedTask; /// /// Verifies that evidence read operations work against the previous schema version (N-1). /// [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")); } /// /// Verifies that evidence write operations produce valid data for previous schema versions. /// [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")); } /// /// Verifies that attestation storage operations work across schema versions. /// [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"); } /// /// Verifies that bundle export operations work across schema versions. /// [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(); } /// /// Verifies that sealed evidence operations work across schema versions. /// [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(); } /// /// Verifies that migration rollbacks work correctly. /// [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(); } }