using Dapper; using Microsoft.EntityFrameworkCore; using Npgsql; using Testcontainers.PostgreSql; using WorkClub.Infrastructure.Data; namespace WorkClub.Tests.Integration.Data; public class MigrationTests : IAsyncLifetime { private PostgreSqlContainer? _container; private string? _connectionString; public async Task InitializeAsync() { _container = new PostgreSqlBuilder() .WithImage("postgres:16-alpine") .Build(); await _container.StartAsync(); _connectionString = _container.GetConnectionString(); } public async Task DisposeAsync() { if (_container != null) { await _container.DisposeAsync(); } } [Fact] public async Task Migration_AppliesSuccessfully_CreatesAllTables() { // Arrange var options = new DbContextOptionsBuilder() .UseNpgsql(_connectionString) .Options; // Act await using var context = new AppDbContext(options); await context.Database.MigrateAsync(); // Assert - verify all expected tables exist await using var connection = new NpgsqlConnection(_connectionString); var tables = (await connection.QueryAsync( @"SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' ORDER BY table_name")).ToList(); Assert.Contains("clubs", tables); Assert.Contains("members", tables); Assert.Contains("work_items", tables); Assert.Contains("shifts", tables); Assert.Contains("shift_signups", tables); } [Fact] public async Task Migration_CreatesCorrectIndexes() { // Arrange var options = new DbContextOptionsBuilder() .UseNpgsql(_connectionString) .Options; // Act await using var context = new AppDbContext(options); await context.Database.MigrateAsync(); // Assert - verify critical indexes exist await using var connection = new NpgsqlConnection(_connectionString); var indexes = (await connection.QueryAsync( @"SELECT indexname FROM pg_indexes WHERE schemaname = 'public' ORDER BY indexname")).ToList(); // TenantId indexes Assert.Contains(indexes, i => i.Contains("tenant_id")); // ClubId indexes Assert.Contains(indexes, i => i.Contains("club_id")); // Status indexes for WorkItem Assert.Contains(indexes, i => i.Contains("status")); } [Fact] public async Task Migration_CreatesExpectedSchema() { // Arrange var options = new DbContextOptionsBuilder() .UseNpgsql(_connectionString) .Options; // Act await using var context = new AppDbContext(options); await context.Database.MigrateAsync(); // Assert - verify schema integrity (RLS and policies are applied via SeedDataService, not migrations) await using var connection = new NpgsqlConnection(_connectionString); // Verify tenant_id columns exist on all tables var tenantColumns = (await connection.QueryAsync( @"SELECT table_name FROM information_schema.columns WHERE table_schema = 'public' AND column_name = 'TenantId' ORDER BY table_name")).ToList(); Assert.Contains("clubs", tenantColumns); Assert.Contains("members", tenantColumns); Assert.Contains("work_items", tenantColumns); Assert.Contains("shifts", tenantColumns); Assert.Contains("shift_signups", tenantColumns); } }