From 6da3d33970eab9daf2b27d4a35b9cca9c0a4d366 Mon Sep 17 00:00:00 2001 From: MortenFriisSiteImprove Date: Tue, 15 Sep 2026 13:56:02 +0200 Subject: [PATCH] Fix admin redirects with conventional site routing --- .../Controllers/SiteimproveAdminController.cs | 6 +-- TESTING.md | 4 +- tests/Plugin.Tests/AuthorizationTests.cs | 42 +++++++++++++++++-- tests/Plugin.Tests/ControllerTests.cs | 18 +++++--- 4 files changed, 55 insertions(+), 15 deletions(-) diff --git a/SiteImprove.Optimizely.Plugin/Controllers/SiteimproveAdminController.cs b/SiteImprove.Optimizely.Plugin/Controllers/SiteimproveAdminController.cs index 217eee4..fa7211c 100644 --- a/SiteImprove.Optimizely.Plugin/Controllers/SiteimproveAdminController.cs +++ b/SiteImprove.Optimizely.Plugin/Controllers/SiteimproveAdminController.cs @@ -77,7 +77,7 @@ public ActionResult Save(bool recheck, bool latestUI, string apiUser, string api _settingsRepo.SaveToken(settings.Token, settings.Recheck, settings.LatestUI, settings.ApiUser, settings.ApiKey, settings.UrlMap); - return RedirectToAction("Index"); + return Redirect(_moduleResourceResolver.ResolvePath(Constants.SiteImproveModuleName, "SiteimproveAdmin")); } private static bool IsWebUrl(string value) @@ -99,10 +99,10 @@ public ActionResult EnablePrepublishCheck(bool enablePrepublishCheck = false) if (!success) { - return RedirectToAction("Index", new { prepublishError = true }); + return Redirect(_moduleResourceResolver.ResolvePath(Constants.SiteImproveModuleName, "SiteimproveAdmin") + "?prepublishError=true"); } - return RedirectToAction("Index"); + return Redirect(_moduleResourceResolver.ResolvePath(Constants.SiteImproveModuleName, "SiteimproveAdmin")); } } } diff --git a/TESTING.md b/TESTING.md index 41a46c7..e6dc817 100644 --- a/TESTING.md +++ b/TESTING.md @@ -14,7 +14,7 @@ dotnet restore tests/Plugin.Tests/Plugin.Tests.csproj --locked-mode dotnet test tests/Plugin.Tests/Plugin.Tests.csproj --configuration Release --no-restore -p:GeneratePackageOnBuild=false --logger 'trx;LogFilePrefix=backend' --results-directory test-results/backend ``` -The 56 backend cases run on both .NET 6 and .NET 8. The tests build and reference +The 64 backend cases run on both .NET 6 and .NET 8. The tests build and reference the actual plugin project and locked Optimizely dependencies. They do not establish compatibility with every CMS 12 release. The plugin still targets .NET 6, which produces an end-of-support build warning; this PR does not change that target. @@ -24,7 +24,7 @@ produces an end-of-support build warning; this PR does not change that target. | Page URL controller | Revision and language; direct Block request returns HTTP 400 without a typed Page lookup. | | URL mapping | Host/scheme/port matching; preservation of page paths and query strings; independent sites and language paths; missing URLs/sites and resolver errors. | | Settings | First save, update and reload; URL maps; token reuse; automatic/manual renewal; failed renewal preserves configuration; concurrent first requests generate one token; unavailable service does not create a blank record. | -| Admin actions | Save inputs, valid and duplicate URL maps, empty mapping rows, and Prepublish enablement outcome. | +| Admin actions | Save inputs, valid and duplicate URL maps, empty mapping rows, and Prepublish enablement outcome; module redirects with and without a conventional route. | | Authorization | Real ASP.NET middleware and production policy/controller attributes; all four configured roles; anonymous and unauthorized callers; protected reads and writes. | | Publish events | Recheck URL/token; disabled rechecks, Blocks, missing URLs and background events; start-page recrawl transition; shutdown/reinitialization avoid duplicate subscriptions. | | Overlay selection | Exactly one script matching the latest-interface setting. | diff --git a/tests/Plugin.Tests/AuthorizationTests.cs b/tests/Plugin.Tests/AuthorizationTests.cs index 319914e..d27f0a0 100644 --- a/tests/Plugin.Tests/AuthorizationTests.cs +++ b/tests/Plugin.Tests/AuthorizationTests.cs @@ -12,6 +12,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Moq; +using SiteImprove.Optimizely.Plugin; using SiteImprove.Optimizely.Plugin.Controllers; using SiteImprove.Optimizely.Plugin.Helper; using SiteImprove.Optimizely.Plugin.Infrastructure; @@ -23,7 +24,7 @@ namespace Plugin.Tests; public class AuthorizationTests { - private static TestServer Server() + private static TestServer Server(bool conventionalRoute = true, bool prepublishSuccess = true) { return new TestServer(new WebHostBuilder().ConfigureServices(services => { services.AddLogging(); @@ -36,13 +37,22 @@ private static TestServer Server() settings.Setup(x => x.GetToken()).Returns("fixture-token"); settings.Setup(x => x.GetSetting()).Returns(new Settings { Token = "fixture-token" }); services.AddSingleton(settings.Object); - services.AddSingleton(Mock.Of()); - services.AddSingleton(Mock.Of()); + var helper = new Mock(); + helper.Setup(x => x.EnablePrepublishCheck(It.IsAny(), It.IsAny())).Returns(prepublishSuccess); + services.AddSingleton(helper.Object); + var resolver = new Mock(); + resolver.Setup(x => x.ResolvePath(Constants.SiteImproveModuleName, "SiteimproveAdmin")) + .Returns("/custom-ui/SiteImprove.Optimizely.Plugin/SiteimproveAdmin"); + services.AddSingleton(resolver.Object); }).Configure(app => { app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); - app.UseEndpoints(endpoints => endpoints.MapControllerRoute("default", "{controller}/{action}")); + app.UseEndpoints(endpoints => { + if (conventionalRoute) + endpoints.MapControllerRoute("default", "{controller}/{action}/{id?}"); + endpoints.MapControllerRoute("module", "custom-ui/SiteImprove.Optimizely.Plugin/{controller}/{action=Index}"); + }); })); } @@ -78,6 +88,30 @@ public async Task Anonymous_and_unauthorized_users_cannot_read_or_change_protect Assert.Equal(HttpStatusCode.Forbidden, (await client.GetAsync("/Siteimprove/IsAuthorized")).StatusCode); } + [Theory] + [InlineData(false, "Save", false, true)] + [InlineData(true, "Save", false, true)] + [InlineData(false, "EnablePrepublishCheck", true, true)] + [InlineData(true, "EnablePrepublishCheck", true, true)] + [InlineData(false, "EnablePrepublishCheck", true, false)] + [InlineData(true, "EnablePrepublishCheck", true, false)] + [InlineData(false, "EnablePrepublishCheck", false, true)] + [InlineData(true, "EnablePrepublishCheck", false, true)] + public async Task Admin_posts_redirect_to_the_module_under_either_route_setup( + bool conventionalRoute, string action, bool enable, bool success) + { + using var server = Server(conventionalRoute, success); + using var client = server.CreateClient(); + client.DefaultRequestHeaders.Add("X-Fixture-Role", "CmsAdmins"); + const string moduleUrl = "/custom-ui/SiteImprove.Optimizely.Plugin/SiteimproveAdmin"; + var response = await client.PostAsync(moduleUrl + "/" + action, + new FormUrlEncodedContent(new Dictionary { + ["enablePrepublishCheck"] = enable.ToString() + })); + Assert.Equal(HttpStatusCode.Redirect, response.StatusCode); + Assert.Equal(moduleUrl + (success ? "" : "?prepublishError=true"), response.Headers.Location?.OriginalString); + } + private sealed class FixtureAuthentication : AuthenticationHandler { #if NET6_0 diff --git a/tests/Plugin.Tests/ControllerTests.cs b/tests/Plugin.Tests/ControllerTests.cs index d366377..f66ad87 100644 --- a/tests/Plugin.Tests/ControllerTests.cs +++ b/tests/Plugin.Tests/ControllerTests.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.DependencyInjection; using Moq; using Newtonsoft.Json.Linq; +using SiteImprove.Optimizely.Plugin; using SiteImprove.Optimizely.Plugin.Controllers; using SiteImprove.Optimizely.Plugin.Helper; using SiteImprove.Optimizely.Plugin.Models; @@ -50,7 +51,14 @@ public void Direct_block_request_returns_bad_request_without_resolving_a_page_ur helper.Verify(x => x.GetExternalUrl(It.IsAny()), Times.Never); } - private SiteimproveAdminController Admin() => new(settings.Object, helper.Object, Mock.Of()); + private const string AdminUrl = "/custom-ui/SiteImprove.Optimizely.Plugin/SiteimproveAdmin"; + + private SiteimproveAdminController Admin() + { + var resolver = new Mock(); + resolver.Setup(x => x.ResolvePath(Constants.SiteImproveModuleName, "SiteimproveAdmin")).Returns(AdminUrl); + return new(settings.Object, helper.Object, resolver.Object); + } [Fact] public void Saving_settings_preserves_token_and_filters_invalid_and_duplicate_mappings() @@ -62,7 +70,7 @@ public void Saving_settings_preserves_token_and_filters_invalid_and_duplicate_ma new KeyValuePair("/relative", "https://public.example"), new KeyValuePair("https://other.example", "/relative") }; - Assert.IsType(Admin().Save(true, false, "fixture-user", "fixture-key", map)); + Assert.Equal(AdminUrl, Assert.IsType(Admin().Save(true, false, "fixture-user", "fixture-key", map)).Url); settings.Verify(x => x.SaveToken("existing-token", true, false, "fixture-user", "fixture-key", It.Is>(m => m.Count == 1 && m["https://cms.example"] == "https://public.example")), Times.Once); } @@ -82,9 +90,7 @@ public void Enabling_prepublish_reports_the_service_outcome(bool success) { settings.Setup(x => x.GetSetting()).Returns(new Settings { ApiUser = "fixture-user", ApiKey = "fixture-key" }); helper.Setup(x => x.EnablePrepublishCheck("fixture-user", "fixture-key")).Returns(success); - var result = Assert.IsType(Admin().EnablePrepublishCheck(true)); - Assert.Equal("Index", result.ActionName); - if (!success) Assert.Equal(true, result.RouteValues["prepublishError"]); - else Assert.Null(result.RouteValues); + var result = Assert.IsType(Admin().EnablePrepublishCheck(true)); + Assert.Equal(AdminUrl + (success ? "" : "?prepublishError=true"), result.Url); } }