Skip to content

Commit e42ec28

Browse files
committed
fix(tests): migrate the shared test database once per session
The four WebApplicationFactory variants are each their own ClassDataSource, so each boots its own API host, but all of them share a single PerTestSession Postgres container. Every host applied migrations on startup, and EF's migrator is not safe to run concurrently: two hosts booting together both see the same pending migrations and both apply them. TUnit 1.70 happened to schedule those initializations so the first migrator finished before the others started; 1.74 does not. That surfaced the latent race as 30 failures in API.IntegrationTests - "type/relation/collation already exists", duplicate keys on __EFMigrationsHistory, and queries hitting a half-migrated schema. Apply migrations in InMemoryDatabase.InitializeAsync, which runs once per session, and set SKIPMIGRATION on the factories so no host migrates on boot. This mirrors what Cron.IntegrationTests already does for the Cron host. It also removes the stale Npgsql type-catalog hazard, since the schema is complete before any host opens its first connection, and runs the migrations once rather than up to four times.
1 parent 0d3330e commit e42ec28

2 files changed

Lines changed: 28 additions & 4 deletions

File tree

‎API.IntegrationTests/Docker/InMemoryDatabase.cs‎

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
using OpenShock.Common.Utils;
1+
using Microsoft.EntityFrameworkCore;
2+
using Microsoft.Extensions.Logging.Abstractions;
3+
using OpenShock.Common.OpenShockDb;
4+
using OpenShock.Common.Utils;
25
using Testcontainers.PostgreSql;
36
using TUnit.Core.Interfaces;
47

@@ -27,7 +30,25 @@ public PostgreSqlContainer Container
2730
return _container;
2831
}
2932
}
30-
31-
public Task InitializeAsync() => Container.StartAsync();
33+
34+
/// <summary>
35+
/// Starts the container and brings the schema up to date. Migrations are applied here, once per
36+
/// test session, rather than by the API hosts on boot: several <see cref="WebApplicationFactory"/>
37+
/// variants share this one database, and each boots its own host. EF's migrator is not safe to run
38+
/// concurrently against the same database, so letting each host migrate on startup races - two
39+
/// migrators both see the same pending migrations and both apply them, which fails with
40+
/// "type/relation/collation already exists", a duplicate key on __EFMigrationsHistory, or queries
41+
/// hitting a half-migrated schema. The factories set OPENSHOCK__DB__SKIPMIGRATION so this is the
42+
/// only migrator, matching how Cron.IntegrationTests already seeds its schema.
43+
/// </summary>
44+
public async Task InitializeAsync()
45+
{
46+
await Container.StartAsync();
47+
48+
await using var migrationContext = new MigrationOpenShockContext(
49+
Container.GetConnectionString(), false, NullLoggerFactory.Instance);
50+
await migrationContext.Database.MigrateAsync();
51+
}
52+
3253
public ValueTask DisposeAsync() => Container.DisposeAsync();
3354
}

‎API.IntegrationTests/WebApplicationFactory.cs‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,10 @@ protected override void ConfigureWebHost(IWebHostBuilder builder)
5151
{ "ASPNETCORE_UNDER_INTEGRATION_TEST", "1" },
5252

5353
{ "OPENSHOCK__DB__CONN", PostgreSql.Container.GetConnectionString() },
54-
{ "OPENSHOCK__DB__SKIPMIGRATION", "false" },
54+
// InMemoryDatabase migrates the shared database once per test session. Every factory
55+
// variant boots its own host against that same database, and concurrent EF migrators
56+
// corrupt each other, so no host migrates on boot here.
57+
{ "OPENSHOCK__DB__SKIPMIGRATION", "true" },
5558
{ "OPENSHOCK__DB__DEBUG", "false" },
5659

5760
{ "OPENSHOCK__REDIS__CONN", Redis.Container.GetConnectionString() },

0 commit comments

Comments
 (0)