using MeterVault.Infrastructure.Persistence; using Microsoft.EntityFrameworkCore; using Testcontainers.PostgreSql; namespace MeterVault.Integration.Tests; /// /// Spins up exactly one TimescaleDB container per test run (shared via the "Timescale" /// collection) and applies migrations once. Tests reuse it and isolate themselves with Respawn. /// The image tag is pinned: the compression DDL (add_compression_policy) was renamed toward /// add_columnstore_policy in newer Timescale, so a floating tag would risk breaking migrations. /// public sealed class TimescaleFixture : IAsyncLifetime { private readonly PostgreSqlContainer _db = new PostgreSqlBuilder("timescale/timescaledb:2.17.2-pg16") .WithDatabase("metervault") .WithUsername("metervault") .WithPassword("metervault") .Build(); public string ConnectionString => _db.GetConnectionString(); public MeterVaultDbContext CreateContext() { var options = new DbContextOptionsBuilder() .UseNpgsql(ConnectionString, npgsql => npgsql.MigrationsAssembly(typeof(MeterVaultDbContext).Assembly.FullName)) .UseSnakeCaseNamingConvention() .Options; return new MeterVaultDbContext(options); } public async Task InitializeAsync() { await _db.StartAsync(); await using var ctx = CreateContext(); await ctx.Database.MigrateAsync(); // The fixtures are historical (2022–2023), so freshly-inserted chunks immediately fall // past the 30-day compression horizon. Pause the compression job's scheduler so its // background worker can't deadlock a test's import transaction. The policy still exists // (SchemaTests verifies that); only its scheduling is disabled. await ctx.Database.ExecuteSqlRawAsync( "SELECT alter_job(job_id, scheduled => false) FROM timescaledb_information.jobs " + "WHERE proc_name IN ('policy_compression', 'policy_columnstore');"); } public async Task DisposeAsync() => await _db.DisposeAsync(); } /// Binds the shared to all tests in the "Timescale" collection. [CollectionDefinition("Timescale")] public sealed class TimescaleCollection : ICollectionFixture;