From b7b995f63a8167d8bd95fe272311e71552ac38cb Mon Sep 17 00:00:00 2001 From: Joe de Ronde Date: Fri, 15 May 2026 14:07:00 +0100 Subject: [PATCH] Fix test fixtures: use EnsureCreated+Dialect.InitializeAsync and per-prefix migration history MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two bugs fixed in the EF Core test fixtures (PostgresFixture, MySqlFixture, SqlServerFixture): 1. Replace DatabaseMigrator with EnsureCreatedAsync + Dialect.InitializeAsync TestDbContext* are test-only contexts with no EF migration files, so DatabaseMigrator.InitializeAsync called MigrateAsync which was a complete no-op — no tables were ever created and all integration tests failed with 'relation does not exist'. EnsureCreatedAsync builds the schema directly from the EF Core model, which is the correct approach for contexts without migrations. Dialect.InitializeAsync is then called explicitly to create the database-specific JSON functions (json_exists etc.) that EnsureCreated does not set up. 2. Add per-prefix MigrationsHistoryTable for named ContentDbContext registrations DynamicTables.PrepareAsync calls MigrateAsync on the named ContentDbContext (e.g. PostgresContentDbContext) to create per-app/schema dedicated tables such as '__c5_ContentsAll'. The migration (AddInitial) reads TableName.Prefix to build the table name at runtime. All named contexts shared the default '__EFMigrationsHistory' table, so after the first prefix ran AddInitial and recorded it, every subsequent prefix saw the migration as already applied and skipped it — leaving its dedicated tables uncreated and causing 'relation __cN_ContentsAll does not exist' failures in all but the first dedicated-table test. Setting options.MigrationsHistoryTable(\$"{name}MigrationHistory") gives each prefix its own independent migration history, so AddInitial runs once per prefix and creates the correct tables each time. Also add *.lscache to .gitignore (C# language server cache files). --- .gitignore | 3 +++ .../EntityFramework/TestHelpers/MySqlFixture.cs | 9 +++++++-- .../EntityFramework/TestHelpers/PostgresFixture.cs | 13 ++++++++++--- .../EntityFramework/TestHelpers/SqlServerFixture.cs | 13 ++++++++++--- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 7226094e1..54dce8582 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,6 @@ launchSettings.json /frontend/app-config/localhost-key.pem /frontend/app-config/localhost.pem + +# C# language server cache +*.lscache diff --git a/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/MySqlFixture.cs b/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/MySqlFixture.cs index 8ffc38975..12a49042a 100644 --- a/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/MySqlFixture.cs +++ b/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/MySqlFixture.cs @@ -59,13 +59,13 @@ public class MySqlFixture(string? reuseId = null) : IAsyncLifetime, ISqlContentF builder.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString), options => { options.UseMicrosoftJson(MySqlCommonJsonChangeTrackingOptions.FullHierarchyOptimizedSemantically); + options.MigrationsHistoryTable($"{name}MigrationHistory"); }); builder.ConfigureWarnings(w => w.Ignore(RelationalEventId.PendingModelChangesWarning)); }) .AddSingleton() - .AddSingleton>() .AddSingleton(TestUtils.DefaultSerializer) .BuildServiceProvider(); @@ -74,7 +74,12 @@ public class MySqlFixture(string? reuseId = null) : IAsyncLifetime, ISqlContentF await service.InitializeAsync(default); } - await services.GetRequiredService>().InitializeAsync(default); + await using var dbContext = await services.GetRequiredService>().CreateDbContextAsync(); + await dbContext.Database.EnsureCreatedAsync(); + if (dbContext is IDbContextWithDialect withDialect) + { + await withDialect.Dialect.InitializeAsync(dbContext, default); + } } public async ValueTask DisposeAsync() diff --git a/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/PostgresFixture.cs b/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/PostgresFixture.cs index 4e50bf899..de1791a25 100644 --- a/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/PostgresFixture.cs +++ b/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/PostgresFixture.cs @@ -54,13 +54,15 @@ public class PostgresFixture(string? reuseId) : IAsyncLifetime, ISqlContentFixtu .AddNamedDbContext((builder, name) => { builder.UseBulkInsertPostgreSql(); - builder.UseNpgsql(connectionString); + builder.UseNpgsql(connectionString, options => + { + options.MigrationsHistoryTable($"{name}MigrationHistory"); + }); builder.ConfigureWarnings(w => w.Ignore(RelationalEventId.PendingModelChangesWarning)); }) .AddSingleton() - .AddSingleton>() .AddSingleton(TestUtils.DefaultSerializer) .BuildServiceProvider(); @@ -69,7 +71,12 @@ public class PostgresFixture(string? reuseId) : IAsyncLifetime, ISqlContentFixtu await service.InitializeAsync(default); } - await services.GetRequiredService>().InitializeAsync(default); + await using var dbContext = await services.GetRequiredService>().CreateDbContextAsync(); + await dbContext.Database.EnsureCreatedAsync(); + if (dbContext is IDbContextWithDialect withDialect) + { + await withDialect.Dialect.InitializeAsync(dbContext, default); + } } public async ValueTask DisposeAsync() diff --git a/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/SqlServerFixture.cs b/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/SqlServerFixture.cs index e7d3556ef..51db44a6d 100644 --- a/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/SqlServerFixture.cs +++ b/backend/tests/Squidex.Data.Tests/EntityFramework/TestHelpers/SqlServerFixture.cs @@ -56,13 +56,15 @@ public class SqlServerFixture(string? reuseId = null) : IAsyncLifetime, ISqlCont .AddNamedDbContext((builder, name) => { builder.UseBulkInsertSqlServer(); - builder.UseSqlServer(connectionString); + builder.UseSqlServer(connectionString, options => + { + options.MigrationsHistoryTable($"{name}MigrationHistory"); + }); builder.ConfigureWarnings(w => w.Ignore(RelationalEventId.PendingModelChangesWarning)); }) .AddSingleton() - .AddSingleton>() .AddSingleton(TestUtils.DefaultSerializer) .BuildServiceProvider(); @@ -71,7 +73,12 @@ public class SqlServerFixture(string? reuseId = null) : IAsyncLifetime, ISqlCont await service.InitializeAsync(default); } - await services.GetRequiredService>().InitializeAsync(default); + await using var dbContext = await services.GetRequiredService>().CreateDbContextAsync(); + await dbContext.Database.EnsureCreatedAsync(); + if (dbContext is IDbContextWithDialect withDialect) + { + await withDialect.Dialect.InitializeAsync(dbContext, default); + } } public async ValueTask DisposeAsync()