From b2071f7e1034bcce762e622c409dd36c8126288e Mon Sep 17 00:00:00 2001 From: smtp4dev-automation Date: Sun, 14 Sep 2025 08:05:59 +0100 Subject: [PATCH 1/7] fix: Broken logo image and redirects for basepath --- .vscode/launch.json | 4 +- .../ClientApp/src/components/home/home.vue | 4 +- Rnwood.Smtp4dev/Startup.cs | 42 +++++++++++++------ 3 files changed, 33 insertions(+), 17 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index af9950600..f0bbe007d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,7 +11,7 @@ // If you have changed target frameworks, make sure to update the program path. "program": "${workspaceFolder}/Rnwood.Smtp4dev/bin/Debug/net8.0/Rnwood.Smtp4dev.dll", "cwd": "${workspaceFolder}/Rnwood.Smtp4dev", - "args": ["--recreatedb", "--urls", "http://localhost:5000"], + "args": ["--recreatedb", "--urls", "http://localhost:5000", "--basepath=/smtp4dev"], "stopAtEntry": false, "internalConsoleOptions": "openOnSessionStart", "preLaunchTask": "build Rnwood.Smtp4dev", @@ -30,7 +30,7 @@ "name": "Chrome (Client Debug)", "type": "chrome", "request": "launch", - "url": "http://localhost:5000", + "url": "http://localhost:5000/smtp4dev", "webRoot": "${workspaceFolder}/Rnwood.Smtp4dev/ClientApp/src", "sourceMapPathOverrides": { "../../ClientApp/src/*": "${webRoot}/*", diff --git a/Rnwood.Smtp4dev/ClientApp/src/components/home/home.vue b/Rnwood.Smtp4dev/ClientApp/src/components/home/home.vue index 28f6b4726..cf9ea171f 100644 --- a/Rnwood.Smtp4dev/ClientApp/src/components/home/home.vue +++ b/Rnwood.Smtp4dev/ClientApp/src/components/home/home.vue @@ -4,8 +4,8 @@
- - + +
diff --git a/Rnwood.Smtp4dev/Startup.cs b/Rnwood.Smtp4dev/Startup.cs index f7f2fe635..886713a6e 100644 --- a/Rnwood.Smtp4dev/Startup.cs +++ b/Rnwood.Smtp4dev/Startup.cs @@ -60,7 +60,7 @@ private static void ValidateDatabaseVersionCompatibility(Smtp4devDbContext conte // Get all migrations that have been applied to the database var appliedMigrations = context.Database.GetAppliedMigrations().ToList(); - + // Get all migrations available in the current application var availableMigrations = context.Database.GetMigrations().ToList(); @@ -92,7 +92,7 @@ public void ConfigureServices(IServiceCollection services) //Remove the JSON content type from the actions where it is not supported. NSwag.OpenApiOperationDescription sendOp = d.Operations.FirstOrDefault(o => o.Path.EndsWith("/send") || o.Path.EndsWith("/reply")); sendOp.Operation.RequestBody.Content.Remove("application/json"); - + }; }); @@ -131,10 +131,10 @@ public void ConfigureServices(IServiceCollection services) using var context = new Smtp4devDbContext((DbContextOptions)opt.Options); - + // Validate database version compatibility before attempting any operations ValidateDatabaseVersionCompatibility(context); - + if (string.IsNullOrEmpty(serverOptions.Database)) { context.Database.Migrate(); @@ -178,7 +178,7 @@ public void ConfigureServices(IServiceCollection services) { Log.Logger.Information("Populating MIME metadata for {count} existing messages during startup", messagesWithoutMetadata.Count); var mimeProcessingService = new MimeProcessingService(); - + int processed = 0; int batchSize = 50; // Process in batches to avoid memory issues @@ -335,14 +335,30 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) if (!string.IsNullOrEmpty(serverOptions.BasePath) && serverOptions.BasePath != "/") { - RewriteOptions rewrites = new RewriteOptions(); - rewrites.AddRedirect("^" + serverOptions.BasePath.TrimEnd('/') + "$", serverOptions.BasePath.TrimEnd('/') + "/"); - ; - rewrites.AddRedirect("^(/)?$", serverOptions.BasePath.TrimEnd('/') + "/"); - ; - app.UseRewriter(rewrites); - - app.Map(serverOptions.BasePath, configure); + string basePathNoSlash = serverOptions.BasePath.TrimEnd('/'); + string redirectTarget = basePathNoSlash + "/"; + + // Global middleware to redirect /smtp4dev to /smtp4dev/ + app.Use(async (context, next) => + { + if (context.Request.Path.Equals(basePathNoSlash, StringComparison.OrdinalIgnoreCase) + && !context.Request.Path.Value.EndsWith("/")) + { + var queryString = context.Request.QueryString.HasValue ? context.Request.QueryString.Value : ""; + context.Response.Redirect(redirectTarget + queryString, true); + return; + } + else if (context.Request.Path.Value.Equals("/") + || context.Request.Path.Value == String.Empty) + { + var queryString = context.Request.QueryString.HasValue ? context.Request.QueryString.Value : ""; + context.Response.Redirect(redirectTarget + queryString, true); + return; + } + await next(); + }); + + app.Map(serverOptions.BasePath.TrimEnd('/'), configure); } else { From 734355e2cafa33fea684aa229bd61ccb9684ac0d Mon Sep 17 00:00:00 2001 From: smtp4dev-automation Date: Sun, 14 Sep 2025 08:17:21 +0100 Subject: [PATCH 2/7] Add more tests --- .vscode/tasks.json | 1 - Rnwood.Smtp4dev.Tests/E2E/E2ETests.cs | 10 ++++++---- ...WebUI_CheckMessageIsReceivedAndDisplayed.cs | 18 +++++++++++------- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 44d2e972c..f427156e8 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -7,7 +7,6 @@ "type": "process", "args": [ "build", - "${workspaceFolder}/Rnwood.Smtp4dev", "/property:GenerateFullPaths=true" ], "problemMatcher": "$msCompile", diff --git a/Rnwood.Smtp4dev.Tests/E2E/E2ETests.cs b/Rnwood.Smtp4dev.Tests/E2E/E2ETests.cs index f39ba4cc6..1e8869bbe 100644 --- a/Rnwood.Smtp4dev.Tests/E2E/E2ETests.cs +++ b/Rnwood.Smtp4dev.Tests/E2E/E2ETests.cs @@ -24,9 +24,11 @@ public E2ETests(ITestOutputHelper output) public class E2ETestOptions { - public bool InMemoryDB { get; set; } - public string BasePath { get; set; } - public IDictionary EnvironmentVariables { get; set; } = new Dictionary(); + public required bool InMemoryDB { get; set; } + public required string BasePath { get; set; } + + public string TestPath { get; set; } + public IDictionary EnvironmentVariables { get; set; } = new Dictionary(); } public class E2ETestContext @@ -156,7 +158,7 @@ protected void RunE2ETest(Action test, E2ETestOptions options = if (newLine.StartsWith("Now listening on: http://")) { int portNumber = int.Parse(Regex.Replace(newLine, @".*http://[^\s]+:(\d+)", "$1")); - baseUrl = new Uri($"http://localhost:{portNumber}{options.BasePath ?? ""}"); + baseUrl = new Uri($"http://localhost:{portNumber}{options.TestPath ?? options.BasePath ?? ""}"); } if (newLine.StartsWith("SMTP Server is listening on port")) diff --git a/Rnwood.Smtp4dev.Tests/E2E/E2ETests_WebUI_CheckMessageIsReceivedAndDisplayed.cs b/Rnwood.Smtp4dev.Tests/E2E/E2ETests_WebUI_CheckMessageIsReceivedAndDisplayed.cs index 387d94c39..4ad1d1c99 100644 --- a/Rnwood.Smtp4dev.Tests/E2E/E2ETests_WebUI_CheckMessageIsReceivedAndDisplayed.cs +++ b/Rnwood.Smtp4dev.Tests/E2E/E2ETests_WebUI_CheckMessageIsReceivedAndDisplayed.cs @@ -17,11 +17,14 @@ public E2ETests_WebUI_CheckMessageIsReceivedAndDisplayed(ITestOutputHelper outpu { } - [Theory] - [InlineData("/", false)] - [InlineData("/", true)] - [InlineData("/smtp4dev", true)] - public void CheckMessageIsReceivedAndDisplayed(string basePath, bool inMemoryDb) + [Theory] + [InlineData("", "", false)] + [InlineData("", "/", true)] + [InlineData("/smtp4dev", "/smtp4dev", true)] + [InlineData("/smtp4dev", "/smtp4dev/", true)] + [InlineData("/smtp4dev", "", true)] + [InlineData("/smtp4dev", "/", true)] + public void CheckMessageIsReceivedAndDisplayed(string basePath, string testPath, bool inMemoryDb) { RunUITestAsync($"{nameof(CheckMessageIsReceivedAndDisplayed)}-{basePath}-{inMemoryDb}", async (page, baseUrl, smtpPortNumber) => { @@ -59,13 +62,14 @@ public void CheckMessageIsReceivedAndDisplayed(string basePath, bool inMemoryDb) var rows = await grid.GetRowsAsync(); return rows.FirstOrDefault(); }); - + Assert.NotNull(messageRow); Assert.True(await messageRow.ContainsTextAsync(messageSubject)); }, new UITestOptions { InMemoryDB = inMemoryDb, - BasePath = basePath + BasePath = basePath, + TestPath = testPath }); } } From 7f61c14faebb32b5eafd068fc17c4c370c6a6e00 Mon Sep 17 00:00:00 2001 From: smtp4dev-automation Date: Sun, 14 Sep 2025 08:29:54 +0100 Subject: [PATCH 3/7] Fix test build errors --- Rnwood.Smtp4dev.Tests/E2E/E2ETests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Rnwood.Smtp4dev.Tests/E2E/E2ETests.cs b/Rnwood.Smtp4dev.Tests/E2E/E2ETests.cs index 1e8869bbe..344ff10a6 100644 --- a/Rnwood.Smtp4dev.Tests/E2E/E2ETests.cs +++ b/Rnwood.Smtp4dev.Tests/E2E/E2ETests.cs @@ -24,8 +24,8 @@ public E2ETests(ITestOutputHelper output) public class E2ETestOptions { - public required bool InMemoryDB { get; set; } - public required string BasePath { get; set; } + public bool InMemoryDB { get; set; } + public string BasePath { get; set; } public string TestPath { get; set; } public IDictionary EnvironmentVariables { get; set; } = new Dictionary(); From d69ccd7e3f2027fbe46ebfe1257739c0817cfc34 Mon Sep 17 00:00:00 2001 From: smtp4dev-automation Date: Sun, 14 Sep 2025 08:42:30 +0100 Subject: [PATCH 4/7] chore(ci): Better task titles for matrix items with no testing --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index eeea843a9..1509b5a83 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -75,7 +75,7 @@ stages: condition: succeeded() jobs: - job: BuildMatrix - displayName: Build and test - + displayName: ${{ if eq(variables['runTests'], true) }}: 'Build and test -' ${{ else }}: 'Build' timeoutInMinutes: 90 pool: vmImage: $(vmImage) From 756b2a10eef108d5aebf577d0893e50e35dd524a Mon Sep 17 00:00:00 2001 From: smtp4dev-automation Date: Sun, 14 Sep 2025 13:03:54 +0100 Subject: [PATCH 5/7] fix(ci): Don't list winget package for unstable builds and fix broken link. --- azure-pipelines.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 1509b5a83..bb0fe8ac5 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1100,8 +1100,10 @@ stages: File Name | Description -- | -- - [Winget package](https://winget.run/pkg/rnwood/smtp4dev/desktop)| Rnwood.Smtp4dev.Desktop Winget package (recommended easy option for Win10/11) - [Winget package](https://winget.run/pkg/rnwood/smtp4dev)| Rnwood.Smtp4dev Winget package Win10/11) + ${{ if eq(variables['isreleasebuild'], true) }} + Winget package ```winget install -v "${tag}" -e Rnwood.Smtp4dev.Desktop``` | Rnwood.Smtp4dev.Desktop Winget package (recommended easy option for Win10/11) + Winget package ```winget install -v "${tag}" -e Rnwood.Smtp4dev``` | Rnwood.Smtp4dev Winget package Win10/11) + ${{ end }} [Rnwood.Smtp4dev-win-x64-$(tag).zip](../../releases/download/$(tag)/Rnwood.Smtp4dev-win-x64-$(tag).zip) | Windows x64 binary standalone - Server edition [Rnwood.Smtp4dev.Desktop-win-x64-$(tag).zip](../../releases/download/$(tag)/Rnwood.Smtp4dev.Desktop-win-x64-$(tag).zip) | Windows x64 binary standalone - Desktop app edition. [Rnwood.Smtp4dev-win-arm64-$(tag).zip](../../releases/download/$(tag)/Rnwood.Smtp4dev-win-arm64-$(tag).zip) | Windows ARM 62-bit binary standalone From 060ea3a24afe06754969bff76e5765ed858d0b3f Mon Sep 17 00:00:00 2001 From: Rob Wood Date: Sun, 14 Sep 2025 16:05:06 +0100 Subject: [PATCH 6/7] Fix display name condition in build job --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index bb0fe8ac5..bc3e94f23 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -75,7 +75,7 @@ stages: condition: succeeded() jobs: - job: BuildMatrix - displayName: ${{ if eq(variables['runTests'], true) }}: 'Build and test -' ${{ else }}: 'Build' + displayName: Build timeoutInMinutes: 90 pool: vmImage: $(vmImage) From e6c0b4ad139732a79a92b4922766a53c544d9052 Mon Sep 17 00:00:00 2001 From: Rob Wood Date: Sun, 14 Sep 2025 16:08:33 +0100 Subject: [PATCH 7/7] Remove conditional block for release build in YAML --- azure-pipelines.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index bc3e94f23..573613ecb 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1100,10 +1100,9 @@ stages: File Name | Description -- | -- - ${{ if eq(variables['isreleasebuild'], true) }} Winget package ```winget install -v "${tag}" -e Rnwood.Smtp4dev.Desktop``` | Rnwood.Smtp4dev.Desktop Winget package (recommended easy option for Win10/11) Winget package ```winget install -v "${tag}" -e Rnwood.Smtp4dev``` | Rnwood.Smtp4dev Winget package Win10/11) - ${{ end }} + [.NET tool - ```dotnet tool install -g Rnwood.Smtp4dev --version "$(tag)"```](https://www.nuget.org/packages/Rnwood.Smtp4dev/$(tag)) | .NET tool (recommended option for Mac OS) - [How to use dotnet tool](https://github.com/rnwood/smtp4dev/blob/master/docs/Installation.md#how-to-run-smtp4dev-as-a-dotnet-global-tool) [Rnwood.Smtp4dev-win-x64-$(tag).zip](../../releases/download/$(tag)/Rnwood.Smtp4dev-win-x64-$(tag).zip) | Windows x64 binary standalone - Server edition [Rnwood.Smtp4dev.Desktop-win-x64-$(tag).zip](../../releases/download/$(tag)/Rnwood.Smtp4dev.Desktop-win-x64-$(tag).zip) | Windows x64 binary standalone - Desktop app edition. [Rnwood.Smtp4dev-win-arm64-$(tag).zip](../../releases/download/$(tag)/Rnwood.Smtp4dev-win-arm64-$(tag).zip) | Windows ARM 62-bit binary standalone @@ -1111,10 +1110,7 @@ stages: [Rnwood.Smtp4dev-linux-musl-x64-$(tag).zip](../../releases/download/$(tag)/Rnwood.Smtp4dev-linux-musl-x64-$(tag).zip) | Linux MUSL x64 binary standalone for Linux distros using MUSL libc [Rnwood.Smtp4dev-noruntime-$(tag).zip](../../releases/download/$(tag)/Rnwood.Smtp4dev-noruntime-$(tag).zip) | Architecture independent version. Should run on any platform where the .NET 8.0 (or greater) runtime is installed [Docker images for Windows and Linux](https://hub.docker.com/layers/rnwood/smtp4dev/$(tag)) - [How to use Docker image](https://github.com/rnwood/smtp4dev/blob/master/docs/Installation.md#how-to-run-smtp4dev-in-docker) - [.NET tool Rnwood.Smtp4dev $(tag)](https://www.nuget.org/packages/Rnwood.Smtp4dev/$(tag)) | .NET tool (recommended option for Mac OS) - [How to use dotnet tool](https://github.com/rnwood/smtp4dev/blob/master/docs/Installation.md#how-to-run-smtp4dev-as-a-dotnet-global-tool) - - - + ${{ if eq(variables['isreleasebuild'], true) }}: action: edit isPreRelease: false