From cb20b8a2d5837b25e215d42d015585345ed1d92a Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Sat, 11 Jul 2026 22:17:58 +0100 Subject: [PATCH] Revert "repoduces the bug" This reverts commit fa7794b7a01c41588e54fc4395777b118940deab. --- Directory.Packages.props | 1 - src/Yavsc.Blogs.Tests/BlogApiTests.cs | 9 ++- .../BlogsWebServerFixture.cs | 32 +++------ .../Yavsc.Blogs.Tests.csproj | 1 - .../TestWebApplicationFactory.cs | 69 ------------------- src/Yavsc.Org.Tests/WebServerFixture.cs | 15 +--- src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj | 1 - src/Yavsc.Org/Extensions/HostingExtensions.cs | 56 ++------------- src/Yavsc.Org/Yavsc.Org.csproj | 1 - src/Yavsc.Org/appsettings-org.Testing.json | 2 +- 10 files changed, 18 insertions(+), 169 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 84380e44..e4b09159 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -18,7 +18,6 @@ - diff --git a/src/Yavsc.Blogs.Tests/BlogApiTests.cs b/src/Yavsc.Blogs.Tests/BlogApiTests.cs index 4ac14c50..fd64555a 100644 --- a/src/Yavsc.Blogs.Tests/BlogApiTests.cs +++ b/src/Yavsc.Blogs.Tests/BlogApiTests.cs @@ -30,11 +30,10 @@ public sealed class BlogApiTests : IClassFixture } /// Reset the in-memory database to a known empty state. - /// The fixture now uses SQLite in-memory (see forgejo#3), which - /// shares its store across the lifetime of the - /// instance, so without a - /// per-test reset the test order would leak state between - /// tests. + /// UseInMemoryDatabase shares its store across the + /// lifetime of the instance, + /// so without a per-test reset the test order would leak + /// state between tests. private void ResetDatabase() { using var scope = _fixture.Services.CreateScope(); diff --git a/src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs b/src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs index a76a37a7..6e39fd00 100644 --- a/src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs +++ b/src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs @@ -48,19 +48,6 @@ namespace Yavsc.Blogs.Tests; /// public sealed class BlogsWebServerFixture : WebHostFixture { - // SQLite in-memory database is created once and shared across all - // DbContext instances for the test lifetime. The connection must - // stay open: closing it destroys the in-memory database. The - // Microsoft.Data.Sqlite pool will then open additional connections - // to the same in-memory store, as long as the original connection - // is alive. This is the SQLite equivalent of the EF Core - // InMemoryDatabaseRoot we used to use. - private Microsoft.Data.Sqlite.SqliteConnection? _sharedSqliteConnection; - // Legacy field kept to make the migration diff readable. The - // InMemory provider path is no longer used by this fixture, but - // removing it is out of scope for the SQLite-in-memory migration - // (forgejo#3 follow-up). - [System.Obsolete("Replaced by SQLite in-memory (forgejo#3).")] private InMemoryDatabaseRoot? _inMemoryRoot; protected override WebApplication BuildApp(WebApplicationBuilder builder) @@ -71,18 +58,15 @@ public sealed class BlogsWebServerFixture : WebHostFixture // against an empty table returns an empty list, which is // exactly what the first test wants to assert. // - // We use SQLite in-memory (not the EF Core InMemory provider) - // because the InMemory provider cannot materialise navigation - // properties from IdentityServer8 entity types (see forgejo#3). - // SQLite in-memory is a transient, file-less store that - // executes real SQL, so navigation properties work as - // expected. The shared SqliteConnection keeps the database - // alive for the test lifetime, mirroring the - // InMemoryDatabaseRoot pattern we used previously. - _sharedSqliteConnection = new Microsoft.Data.Sqlite.SqliteConnection("Data Source=:memory:"); - _sharedSqliteConnection.Open(); + // Share a single InMemoryDatabaseRoot across the test + // lifetime so POST + GET on the same fixture see the same + // store. Without the root, EF Core's In-Memory provider + // creates independent stores per DbContext in some + // configurations, and the second request would see an + // empty list even after the first wrote a row. + _inMemoryRoot = new InMemoryDatabaseRoot(); builder.Services.AddDbContext(opt => - opt.UseSqlite(_sharedSqliteConnection)); + opt.UseInMemoryDatabase("Yavsc.Blogs.Tests", _inMemoryRoot)); // Trivial file-system auth: the GET index path never calls // into it, but the DI container needs an instance. diff --git a/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj b/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj index 831097b0..ec1f7f0a 100644 --- a/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj +++ b/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj @@ -17,7 +17,6 @@ - diff --git a/src/Yavsc.Org.Tests/TestWebApplicationFactory.cs b/src/Yavsc.Org.Tests/TestWebApplicationFactory.cs index af40bb78..dfd6edea 100644 --- a/src/Yavsc.Org.Tests/TestWebApplicationFactory.cs +++ b/src/Yavsc.Org.Tests/TestWebApplicationFactory.cs @@ -2,11 +2,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.TestHost; -using Microsoft.Data.Sqlite; -using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Yavsc.Models; using Yavsc.Tests.Shared; namespace Yavsc.Org.Tests; @@ -28,21 +24,6 @@ namespace Yavsc.Org.Tests; /// public class TestWebApplicationFactory : WebApplicationFactory { - // SQLite in-memory: the connection must stay open for the lifetime - // of the host, otherwise the in-memory database is destroyed and - // every new DbContext sees an empty store. We hold the connection - // here so it is disposed only when the factory is disposed. The - // Microsoft.Data.Sqlite pool reuses the underlying in-memory store - // across additional connections opened against the same connection - // string, as long as the original connection is alive. This is the - // SQLite equivalent of the EF Core InMemoryDatabaseRoot pattern. - private readonly SqliteConnection _sharedSqliteConnection = new("Data Source=:memory:"); - - public TestWebApplicationFactory() - { - _sharedSqliteConnection.Open(); - } - protected override void ConfigureWebHost(IWebHostBuilder builder) { // UseEnvironment("Testing") puts the host in a dedicated @@ -57,25 +38,6 @@ public class TestWebApplicationFactory : WebApplicationFactory builder.ConfigureTestServices(services => { - // The production Program.Main calls AddConfiguration("org") - // and then AddIdentityDBAndStores which calls - // GetConnectionString("YavscConnection"). The result is - // "Data Source=:memory:" (from appsettings-org.Testing.json), - // and the production code path in HostingExtensions routes - // that to UseSqlite. However, the EF Core in-memory test - // pattern needs all DbContext instances to see the same - // store; with a raw "Data Source=:memory:" connection string, - // each connection opens its own private database. We - // therefore drop the production DbContext registration and - // re-register ApplicationDbContext with the shared - // SqliteConnection held by this factory. Tests that need - // the schema to exist call EnsureCreated on the resulting - // DbContext (e.g. ClientControllerCollectionTests seeds a - // Client row in its constructor). - services.RemoveAll>(); - services.AddDbContext(opt => - opt.UseSqlite(_sharedSqliteConnection)); - // Replace the production IAuthorizationPolicyProvider with // the test one. The default registered by AddAuthorization // becomes irrelevant: any GetPolicyAsync call is routed here. @@ -86,37 +48,6 @@ public class TestWebApplicationFactory : WebApplicationFactory // TestUserMiddleware runs after UseAuthentication/Authorization. services.AddTransient(); services.AddTransient(); - - // Run EnsureCreated once at host start. With SQLite in-memory - // and a shared connection, this creates the schema once - // and the schema persists for the host lifetime. The - // test code (e.g. ClientControllerCollectionTests seed) can - // then write rows without having to call EnsureCreated - // itself. EnsureCreated is idempotent: re-running it on - // an existing schema is a no-op. - services.AddHostedService(); }); } - - private sealed class SqliteEnsureCreatedHostedService : IHostedService - { - private readonly IServiceProvider _services; - public SqliteEnsureCreatedHostedService(IServiceProvider services) - { - _services = services; - } - public Task StartAsync(CancellationToken cancellationToken) - { - using var scope = _services.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - return db.Database.EnsureCreatedAsync(cancellationToken); - } - public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; - } - - protected override void Dispose(bool disposing) - { - if (disposing) _sharedSqliteConnection.Dispose(); - base.Dispose(disposing); - } } diff --git a/src/Yavsc.Org.Tests/WebServerFixture.cs b/src/Yavsc.Org.Tests/WebServerFixture.cs index ebb50fc7..ed0bd69d 100644 --- a/src/Yavsc.Org.Tests/WebServerFixture.cs +++ b/src/Yavsc.Org.Tests/WebServerFixture.cs @@ -75,20 +75,7 @@ public sealed class WebServerFixture : WebHostFixture // that plus the in-memory overrides below. builder.AddConfiguration(null).AddInMemoryCollection(new Dictionary { - // The EF Core in-memory provider cannot materialise - // entity types from IdentityServer8 (see forgejo#3): - // it crashes with IndexOutOfRangeException on the - // multi-Include query in ClientController.LoadClientAsync - // and on per-collection LoadAsync. SQLite in-memory is - // a transient, file-less store that uses the same - // connection string semantics as the InMemory provider - // ("keep the connection open for the host lifetime") - // but actually executes SQL, so it handles - // navigation-property entities correctly. The - // HostingExtensions code path detects this connection - // string and routes to UseSqlite. See - // doc/testing.md for the test-driver policy. - [$"ConnectionStrings:{YavscConstants.YavscConnectionStringName}"] = "Data Source=:memory:", + [$"ConnectionStrings:{YavscConstants.YavscConnectionStringName}"] = "InMemory", // SMTP test config: UserName non-null so MailSender // exercises the Authenticate branch — the // RecordingSmtpClient captures it. diff --git a/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj b/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj index 5d049626..79a6bae1 100644 --- a/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj +++ b/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj @@ -53,7 +53,6 @@ -