From 713f66f1a716bfa4f99e2ecf507e2bf9772e569f Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Sun, 12 Jul 2026 04:36:35 +0100 Subject: [PATCH 1/3] ? --- src/Yavsc.Org.Tests/Mandatory/Remoting.cs | 5 ++-- src/Yavsc.Org.Tests/appsettings.json | 4 ++-- .../20260711173717_EnforceBlogAuthorFKs.cs | 23 ------------------- 3 files changed, 5 insertions(+), 27 deletions(-) diff --git a/src/Yavsc.Org.Tests/Mandatory/Remoting.cs b/src/Yavsc.Org.Tests/Mandatory/Remoting.cs index 0f829c04..72aa6679 100644 --- a/src/Yavsc.Org.Tests/Mandatory/Remoting.cs +++ b/src/Yavsc.Org.Tests/Mandatory/Remoting.cs @@ -18,11 +18,12 @@ namespace Yavsc.Org.Tests [Fact] public async Task ObtainServiceToken() { - var serverUrl = _serverFixture.Addresses.FirstOrDefault(u => u.StartsWith("https:")); + var serverUrl = _serverFixture.SiteSettings.Authority; if (string.IsNullOrEmpty(serverUrl)) throw new InvalidOperationException("No HTTPS server address found"); HttpClient client = NewHttpClient(); + var disco = await client.GetDiscoveryDocumentAsync(serverUrl); if (disco.IsError) throw new Exception(disco.Error); @@ -45,7 +46,7 @@ namespace Yavsc.Org.Tests [Fact] public async Task ObtainResourceOwnerPasswordToken() { - var serverUrl = _serverFixture.Addresses.FirstOrDefault(u => u.StartsWith("https:")); + var serverUrl = _serverFixture.SiteSettings.Authority; if (string.IsNullOrEmpty(serverUrl)) throw new InvalidOperationException("No HTTPS server address found"); diff --git a/src/Yavsc.Org.Tests/appsettings.json b/src/Yavsc.Org.Tests/appsettings.json index bf8599e0..964674b1 100644 --- a/src/Yavsc.Org.Tests/appsettings.json +++ b/src/Yavsc.Org.Tests/appsettings.json @@ -1,6 +1,6 @@ { "Site": { - "Authority": "https://mercure.pschneider.fr", + "Authority": "https://localhost:5001", "Title": "Yavsc dev", "Slogan": "Yavsc : WIP.", "Banner": "/images/yavsc.png", @@ -63,5 +63,5 @@ "Password": "f/\\kePassw0rd" } } - + } diff --git a/src/Yavsc.Org/Migrations/20260711173717_EnforceBlogAuthorFKs.cs b/src/Yavsc.Org/Migrations/20260711173717_EnforceBlogAuthorFKs.cs index b13ef467..6e3e0f06 100644 --- a/src/Yavsc.Org/Migrations/20260711173717_EnforceBlogAuthorFKs.cs +++ b/src/Yavsc.Org/Migrations/20260711173717_EnforceBlogAuthorFKs.cs @@ -10,29 +10,6 @@ namespace Yavsc.Migrations /// protected override void Up(MigrationBuilder migrationBuilder) { - // Assainir les orphelins AVANT d'enforcer la FK Restrict. - // En prod (Postgres), la migration aurait sinon planté - // sur des billets/commentaires dont l'AuthorId pointe - // vers un user déjà supprimé. La logique métier refuse - // désormais l'orphelin (cf. BlogSpotService.Details) — on - // aligne l'état de la base avec ce contrat. - migrationBuilder.Sql(@" - DO $$ - DECLARE n_comments int; - n_posts int; - BEGIN - DELETE FROM ""Comment"" - WHERE ""AuthorId"" NOT IN (SELECT ""Id"" FROM ""AspNetUsers""); - GET DIAGNOSTICS n_comments = ROW_COUNT; - - DELETE FROM ""BlogSpot"" - WHERE ""AuthorId"" NOT IN (SELECT ""Id"" FROM ""AspNetUsers""); - GET DIAGNOSTICS n_posts = ROW_COUNT; - - RAISE NOTICE 'EnforceBlogAuthorFKs: % orphaned comments deleted, % orphaned blog posts deleted', - n_comments, n_posts; - END $$; - "); migrationBuilder.DropForeignKey( name: "FK_BlogSpot_AspNetUsers_AuthorId", From 4aacaf5e5152561215a770794cc4addae0d107a3 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Sun, 12 Jul 2026 05:48:47 +0100 Subject: [PATCH 2/3] tests: configure static fixture ports and update org test config --- .../BlogsWebServerFixture.cs | 2 ++ src/Yavsc.Org.Tests/Mandatory/Remoting.cs | 1 - src/Yavsc.Org.Tests/WebServerFixture.cs | 7 +++++++ src/Yavsc.Org.Tests/appsettings.json | 12 ++++++++++- src/Yavsc.Tests.Shared/WebHostFixture.cs | 20 ++++++++++++------- 5 files changed, 33 insertions(+), 9 deletions(-) diff --git a/src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs b/src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs index 6e39fd00..2b8bf705 100644 --- a/src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs +++ b/src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs @@ -48,6 +48,8 @@ namespace Yavsc.Blogs.Tests; /// public sealed class BlogsWebServerFixture : WebHostFixture { + protected override int HttpsPort => 5103; + private InMemoryDatabaseRoot? _inMemoryRoot; protected override WebApplication BuildApp(WebApplicationBuilder builder) diff --git a/src/Yavsc.Org.Tests/Mandatory/Remoting.cs b/src/Yavsc.Org.Tests/Mandatory/Remoting.cs index 72aa6679..73f3da18 100644 --- a/src/Yavsc.Org.Tests/Mandatory/Remoting.cs +++ b/src/Yavsc.Org.Tests/Mandatory/Remoting.cs @@ -23,7 +23,6 @@ namespace Yavsc.Org.Tests throw new InvalidOperationException("No HTTPS server address found"); HttpClient client = NewHttpClient(); - var disco = await client.GetDiscoveryDocumentAsync(serverUrl); if (disco.IsError) throw new Exception(disco.Error); diff --git a/src/Yavsc.Org.Tests/WebServerFixture.cs b/src/Yavsc.Org.Tests/WebServerFixture.cs index daa16098..69629b8e 100644 --- a/src/Yavsc.Org.Tests/WebServerFixture.cs +++ b/src/Yavsc.Org.Tests/WebServerFixture.cs @@ -39,6 +39,8 @@ namespace Yavsc.Org.Tests; [CollectionDefinition("Yavsc Server")] public sealed class WebServerFixture : WebHostFixture { + protected override int HttpsPort => 5101; + private static IConfiguration? _sharedConfiguration; private static SiteSettings? _sharedSiteSettings; private static ILogger? _sharedLogger; @@ -81,6 +83,11 @@ public sealed class WebServerFixture : WebHostFixture ["Smtp:Port"] = "465", ["Smtp:UserName"] = "test-user", ["Smtp:Password"] = "test-pass", + // Kestrel test config: override the default port from + // WebApplication.CreateBuilder() so that the test host + // binds to the same port as the production host would. + ["Kestrel:Endpoints:Http:Url"] = "http://localhost:5100", + ["Kestrel:Endpoints:Https:Url"] = builder.Configuration["Site:Authority"] ?? "https://localhost:5101" }); Configuration = builder.Configuration; diff --git a/src/Yavsc.Org.Tests/appsettings.json b/src/Yavsc.Org.Tests/appsettings.json index 964674b1..5f353cd8 100644 --- a/src/Yavsc.Org.Tests/appsettings.json +++ b/src/Yavsc.Org.Tests/appsettings.json @@ -1,6 +1,6 @@ { "Site": { - "Authority": "https://localhost:5001", + "Authority": "https://localhost:5101", "Title": "Yavsc dev", "Slogan": "Yavsc : WIP.", "Banner": "/images/yavsc.png", @@ -62,6 +62,16 @@ "UserName": "fakeuser", "Password": "f/\\kePassw0rd" } + }, + "Kestrel": { + "Endpoints": { + "Http": { + "Url": "http://localhost:5100" + }, + "Https": { + "Url": "https://localhost:5101" + } + } } } diff --git a/src/Yavsc.Tests.Shared/WebHostFixture.cs b/src/Yavsc.Tests.Shared/WebHostFixture.cs index fb2e5984..417d29d8 100644 --- a/src/Yavsc.Tests.Shared/WebHostFixture.cs +++ b/src/Yavsc.Tests.Shared/WebHostFixture.cs @@ -16,8 +16,8 @@ namespace Yavsc.Tests.Shared; /// /// /// Kestrel with a self-signed HTTPS certificate -/// on a dynamically-allocated port (no port collisions between -/// parallel xUnit test classes). +/// on a fixture-defined fixed port for deterministic integration +/// test endpoints. /// A per-process single-instance host initialised /// on first construction and torn down when the last fixture is /// disposed — same lazy + lock + count pattern as the original Org @@ -86,11 +86,12 @@ public abstract class WebHostFixture : IDisposable protected virtual void CopySpecialisedSharedState() { } /// Specialisations register their services and middleware - /// here. The base class has already configured Kestrel HTTPS on a - /// dynamic port — do not bind additional listeners. + /// here. The base class has already configured Kestrel HTTPS on + /// the fixture-defined test port — do not bind additional + /// listeners. /// The - /// configured with Kestrel HTTPS on a dynamic port and the shared - /// self-signed certificate. + /// configured with Kestrel HTTPS on the fixture-defined test port + /// and the shared self-signed certificate. /// The fully built , ready /// for ConfigurePipeline + StartAsync. protected abstract WebApplication BuildApp(WebApplicationBuilder builder); @@ -104,13 +105,18 @@ public abstract class WebHostFixture : IDisposable return app; } + /// HTTPS port used by this fixture's Kestrel host. + /// Override in derived fixtures when they must not share the same + /// listen port. + protected virtual int HttpsPort => 5101; + private async Task InitializeAsync() { var builder = WebApplication.CreateBuilder(); builder.WebHost.ConfigureKestrel(options => { - options.Listen(IPAddress.Loopback, 0, listenOptions => + options.Listen(IPAddress.Loopback, HttpsPort, listenOptions => { listenOptions.UseHttps(_selfSignedCertificate.Value); }); From 6c100ff7599a7005d2bf6d636df4da0ce6f8b9d4 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Sun, 12 Jul 2026 06:01:42 +0100 Subject: [PATCH 3/3] use an available port for authority --- src/Yavsc.Org.Tests/WebServerFixture.cs | 29 ++++++++++++++++++++----- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/Yavsc.Org.Tests/WebServerFixture.cs b/src/Yavsc.Org.Tests/WebServerFixture.cs index 69629b8e..a623bc9e 100644 --- a/src/Yavsc.Org.Tests/WebServerFixture.cs +++ b/src/Yavsc.Org.Tests/WebServerFixture.cs @@ -8,6 +8,8 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using System.Net; +using System.Net.Sockets; using Yavsc; using Yavsc.Extensions; using Yavsc.Interfaces; @@ -39,7 +41,9 @@ namespace Yavsc.Org.Tests; [CollectionDefinition("Yavsc Server")] public sealed class WebServerFixture : WebHostFixture { - protected override int HttpsPort => 5101; + private static readonly int _httpsPort = GetAvailableLoopbackPort(); + + protected override int HttpsPort => _httpsPort; private static IConfiguration? _sharedConfiguration; private static SiteSettings? _sharedSiteSettings; @@ -66,6 +70,8 @@ public sealed class WebServerFixture : WebHostFixture protected override WebApplication BuildApp(WebApplicationBuilder builder) { + var authority = $"https://localhost:{_httpsPort}"; + // WebApplication.CreateBuilder defaults WebRootPath to // {ContentRoot}/wwwroot. The test assembly runs from // src/Yavsc.Org.Tests/bin/.../, which has no wwwroot of @@ -83,11 +89,7 @@ public sealed class WebServerFixture : WebHostFixture ["Smtp:Port"] = "465", ["Smtp:UserName"] = "test-user", ["Smtp:Password"] = "test-pass", - // Kestrel test config: override the default port from - // WebApplication.CreateBuilder() so that the test host - // binds to the same port as the production host would. - ["Kestrel:Endpoints:Http:Url"] = "http://localhost:5100", - ["Kestrel:Endpoints:Https:Url"] = builder.Configuration["Site:Authority"] ?? "https://localhost:5101" + ["Site:Authority"] = authority }); Configuration = builder.Configuration; @@ -283,4 +285,19 @@ public sealed class WebServerFixture : WebHostFixture TestingUser = dbContext.Users.FirstOrDefault(u => u.UserName == testingUserName); } } + + private static int GetAvailableLoopbackPort() + { + var listener = new TcpListener(IPAddress.Loopback, 0); + listener.Start(); + + try + { + return ((IPEndPoint)listener.LocalEndpoint).Port; + } + finally + { + listener.Stop(); + } + } }