// // Copyright (c) StellaOps. Licensed under AGPL-3.0-or-later. // // Sprint: SPRINT_20260105_002_005_TEST_cross_cutting // Task: CCUT-009 using FluentAssertions; using Microsoft.Extensions.Logging.Abstractions; using StellaOps.TestKit; using StellaOps.Testing.SchemaEvolution; using Xunit; namespace StellaOps.Scanner.SchemaEvolution.Tests; /// /// Schema evolution tests for the Scanner module. /// Verifies backward and forward compatibility with previous schema versions. /// [Trait("Category", TestCategories.SchemaEvolution)] [Trait("Category", TestCategories.Integration)] [Trait("BlastRadius", TestCategories.BlastRadius.Scanning)] [Trait("BlastRadius", TestCategories.BlastRadius.Persistence)] public class ScannerSchemaEvolutionTests : PostgresSchemaEvolutionTestBase { private static readonly string[] PreviousVersions = ["v1.8.0", "v1.9.0"]; private static readonly string[] FutureVersions = ["v2.0.0"]; /// /// Initializes a new instance of the class. /// public ScannerSchemaEvolutionTests() : base(NullLogger.Instance) { } /// protected override IReadOnlyList AvailableSchemaVersions => ["v1.8.0", "v1.9.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 scan read operations work against the previous schema version (N-1). /// [Fact] public async Task ScanReadOperations_CompatibleWithPreviousSchema() { // Arrange await InitializeAsync(); // 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 = 'scans' )"); var exists = await cmd.ExecuteScalarAsync(); return exists is true or 1 or (long)1; }, result => result, CancellationToken.None); // Assert results.Should().AllSatisfy(r => r.IsCompatible.Should().BeTrue( because: "scan read operations should work against N-1 schema")); } /// /// Verifies that scan write operations produce valid data for previous schema versions. /// [Fact] public async Task ScanWriteOperations_CompatibleWithPreviousSchema() { // Arrange await InitializeAsync(); // 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 = 'scans' AND column_name = 'id' )"); await cmd.ExecuteScalarAsync(); }, CancellationToken.None); // Assert results.Should().AllSatisfy(r => r.IsCompatible.Should().BeTrue( because: "write operations should be compatible with previous schemas")); } /// /// Verifies that SBOM storage operations work across schema versions. /// [Fact] public async Task SbomStorageOperations_CompatibleAcrossVersions() { // Arrange await InitializeAsync(); // Act var result = await TestAgainstPreviousSchemaAsync( async dataSource => { await using var cmd = dataSource.CreateCommand(@" SELECT COUNT(*) FROM information_schema.tables WHERE table_name LIKE '%sbom%' OR table_name LIKE '%component%'"); await cmd.ExecuteScalarAsync(); }, CancellationToken.None); // Assert result.IsCompatible.Should().BeTrue( because: "SBOM storage should be compatible across schema versions"); } /// /// Verifies that vulnerability mapping operations work across schema versions. /// [Fact] public async Task VulnerabilityMappingOperations_CompatibleAcrossVersions() { // Arrange await InitializeAsync(); // 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 '%vuln%' OR table_name LIKE '%finding%' )"); await cmd.ExecuteScalarAsync(); }, CancellationToken.None); // Assert result.IsCompatible.Should().BeTrue(); } /// /// Verifies that migration rollbacks work correctly. /// [Fact] public async Task MigrationRollbacks_ExecuteSuccessfully() { // Arrange await InitializeAsync(); // Act var results = await TestMigrationRollbacksAsync( migrationsToTest: 3, CancellationToken.None); // Assert - relaxed assertion since migrations may not have down scripts results.Should().NotBeNull(); } }