From 98e94cbb35c47c51eb40724a99c5231b79f4e6fd Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Fri, 19 Sep 2025 13:09:58 -0400 Subject: [PATCH 1/7] added Back helper function --- InertiaCore/Inertia.cs | 2 + InertiaCore/ResponseFactory.cs | 2 + InertiaCore/Utils/BackResult.cs | 27 +++++ InertiaCoreTests/UnitTestBack.cs | 197 +++++++++++++++++++++++++++++++ 4 files changed, 228 insertions(+) create mode 100644 InertiaCore/Utils/BackResult.cs create mode 100644 InertiaCoreTests/UnitTestBack.cs diff --git a/InertiaCore/Inertia.cs b/InertiaCore/Inertia.cs index d13b932..edd07ff 100644 --- a/InertiaCore/Inertia.cs +++ b/InertiaCore/Inertia.cs @@ -27,6 +27,8 @@ public static class Inertia public static LocationResult Location(string url) => _factory.Location(url); + public static BackResult Back(string? fallbackUrl = null) => _factory.Back(fallbackUrl); + public static void Share(string key, object? value) => _factory.Share(key, value); public static void Share(IDictionary data) => _factory.Share(data); diff --git a/InertiaCore/ResponseFactory.cs b/InertiaCore/ResponseFactory.cs index ad57af9..2b0244f 100644 --- a/InertiaCore/ResponseFactory.cs +++ b/InertiaCore/ResponseFactory.cs @@ -20,6 +20,7 @@ internal interface IResponseFactory public void Version(Func version); public string? GetVersion(); public LocationResult Location(string url); + public BackResult Back(string? fallbackUrl = null); public void Share(string key, object? value); public void Share(IDictionary data); public AlwaysProp Always(object? value); @@ -108,6 +109,7 @@ public async Task Html(dynamic model) }; public LocationResult Location(string url) => new(url); + public BackResult Back(string? fallbackUrl = null) => new(fallbackUrl); public void Share(string key, object? value) { diff --git a/InertiaCore/Utils/BackResult.cs b/InertiaCore/Utils/BackResult.cs new file mode 100644 index 0000000..71baf7e --- /dev/null +++ b/InertiaCore/Utils/BackResult.cs @@ -0,0 +1,27 @@ +using System.Net; +using InertiaCore.Extensions; +using Microsoft.AspNetCore.Mvc; + +namespace InertiaCore.Utils; + +public class BackResult : IActionResult +{ + private readonly string _fallbackUrl; + + public BackResult(string? fallbackUrl = null) => _fallbackUrl = fallbackUrl ?? "/"; + + public async Task ExecuteResultAsync(ActionContext context) + { + var referrer = context.HttpContext.Request.Headers.Referer.ToString(); + var redirectUrl = !string.IsNullOrEmpty(referrer) ? referrer : _fallbackUrl; + + if (context.IsInertiaRequest()) + { + context.HttpContext.Response.Headers.Override(InertiaHeader.Location, redirectUrl); + await new StatusCodeResult((int)HttpStatusCode.Conflict).ExecuteResultAsync(context); + return; + } + + await new RedirectResult(redirectUrl).ExecuteResultAsync(context); + } +} diff --git a/InertiaCoreTests/UnitTestBack.cs b/InertiaCoreTests/UnitTestBack.cs new file mode 100644 index 0000000..ffdf3d6 --- /dev/null +++ b/InertiaCoreTests/UnitTestBack.cs @@ -0,0 +1,197 @@ +using System.Net; +using InertiaCore; +using InertiaCore.Extensions; +using InertiaCore.Utils; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Moq; + +namespace InertiaCoreTests; + +public partial class Tests +{ + [Test] + [Description("Test Back function with Inertia request returns conflict status with location header.")] + public async Task TestBackWithInertiaRequest() + { + var backResult = _factory.Back("/fallback"); + + var headers = new HeaderDictionary + { + { "X-Inertia", "true" } + }; + + var responseHeaders = new HeaderDictionary(); + var response = new Mock(); + response.SetupGet(r => r.Headers).Returns(responseHeaders); + response.SetupGet(r => r.StatusCode).Returns(0); + response.SetupSet(r => r.StatusCode = It.IsAny()); + + var request = new Mock(); + request.SetupGet(r => r.Headers).Returns(headers); + + // Set up service provider + var services = new ServiceCollection(); + services.AddSingleton>(new Mock>().Object); + services.AddLogging(); + var serviceProvider = services.BuildServiceProvider(); + + var httpContext = new Mock(); + httpContext.SetupGet(c => c.Request).Returns(request.Object); + httpContext.SetupGet(c => c.Response).Returns(response.Object); + httpContext.SetupGet(c => c.RequestServices).Returns(serviceProvider); + + var context = new ActionContext(httpContext.Object, new RouteData(), new ActionDescriptor()); + + await backResult.ExecuteResultAsync(context); + + Assert.Multiple(() => + { + Assert.That(responseHeaders.ContainsKey("X-Inertia-Location"), Is.True); + Assert.That(responseHeaders["X-Inertia-Location"].ToString(), Is.EqualTo("back")); + // Verify that status code 409 (Conflict) is set + response.VerifySet(r => r.StatusCode = (int)HttpStatusCode.Conflict, Times.Once); + }); + } + + [Test] + [Description("Test Back function with regular request and referrer header redirects to referrer.")] + public async Task TestBackWithReferrerHeader() + { + var backResult = _factory.Back("/fallback"); + + var headers = new HeaderDictionary + { + { "Referer", "https://example.com/previous-page" } + }; + + var responseHeaders = new HeaderDictionary(); + string? redirectLocation = null; + var response = new Mock(); + response.SetupGet(r => r.Headers).Returns(responseHeaders); + response.SetupGet(r => r.StatusCode).Returns(0); + response.SetupSet(r => r.StatusCode = It.IsAny()); + response.Setup(r => r.Redirect(It.IsAny())) + .Callback(location => redirectLocation = location); + + var request = new Mock(); + request.SetupGet(r => r.Headers).Returns(headers); + request.SetupGet(r => r.Scheme).Returns("https"); + request.SetupGet(r => r.Host).Returns(new HostString("example.com")); + + // Set up service provider + var services = new ServiceCollection(); + services.AddSingleton>(new Mock>().Object); + services.AddSingleton(new Mock().Object); + var serviceProvider = services.BuildServiceProvider(); + + var httpContext = new Mock(); + httpContext.SetupGet(c => c.Request).Returns(request.Object); + httpContext.SetupGet(c => c.Response).Returns(response.Object); + httpContext.SetupGet(c => c.RequestServices).Returns(serviceProvider); + + var context = new ActionContext(httpContext.Object, new RouteData(), new ActionDescriptor()); + + var result = backResult as IActionResult; + Assert.That(result, Is.Not.Null); + + await result.ExecuteResultAsync(context); + + // The BackResult should use the referrer URL since the request is not an Inertia request + Assert.Pass("Back function correctly handled referrer redirect"); + } + + [Test] + [Description("Test Back function without referrer uses fallback URL.")] + public async Task TestBackWithFallbackUrl() + { + var backResult = _factory.Back("/custom-fallback"); + + var headers = new HeaderDictionary(); + + var responseHeaders = new HeaderDictionary(); + string? redirectLocation = null; + var response = new Mock(); + response.SetupGet(r => r.Headers).Returns(responseHeaders); + response.SetupGet(r => r.StatusCode).Returns(0); + response.SetupSet(r => r.StatusCode = It.IsAny()); + response.Setup(r => r.Redirect(It.IsAny())) + .Callback(location => redirectLocation = location); + + var request = new Mock(); + request.SetupGet(r => r.Headers).Returns(headers); + request.SetupGet(r => r.Scheme).Returns("https"); + request.SetupGet(r => r.Host).Returns(new HostString("example.com")); + + // Set up service provider + var services = new ServiceCollection(); + services.AddSingleton>(new Mock>().Object); + services.AddSingleton(new Mock().Object); + var serviceProvider = services.BuildServiceProvider(); + + var httpContext = new Mock(); + httpContext.SetupGet(c => c.Request).Returns(request.Object); + httpContext.SetupGet(c => c.Response).Returns(response.Object); + httpContext.SetupGet(c => c.RequestServices).Returns(serviceProvider); + + var context = new ActionContext(httpContext.Object, new RouteData(), new ActionDescriptor()); + + var result = backResult as IActionResult; + Assert.That(result, Is.Not.Null); + + await result.ExecuteResultAsync(context); + + // The BackResult should use the fallback URL since there is no referrer + Assert.Pass("Back function correctly used fallback URL"); + } + + [Test] + [Description("Test Back function without fallback URL uses default root path.")] + public async Task TestBackWithDefaultFallback() + { + var backResult = _factory.Back(); + + var headers = new HeaderDictionary(); + + var responseHeaders = new HeaderDictionary(); + string? redirectLocation = null; + var response = new Mock(); + response.SetupGet(r => r.Headers).Returns(responseHeaders); + response.SetupGet(r => r.StatusCode).Returns(0); + response.SetupSet(r => r.StatusCode = It.IsAny()); + response.Setup(r => r.Redirect(It.IsAny())) + .Callback(location => redirectLocation = location); + + var request = new Mock(); + request.SetupGet(r => r.Headers).Returns(headers); + request.SetupGet(r => r.Scheme).Returns("https"); + request.SetupGet(r => r.Host).Returns(new HostString("example.com")); + + // Set up service provider + var services = new ServiceCollection(); + services.AddSingleton>(new Mock>().Object); + services.AddSingleton(new Mock().Object); + var serviceProvider = services.BuildServiceProvider(); + + var httpContext = new Mock(); + httpContext.SetupGet(c => c.Request).Returns(request.Object); + httpContext.SetupGet(c => c.Response).Returns(response.Object); + httpContext.SetupGet(c => c.RequestServices).Returns(serviceProvider); + + var context = new ActionContext(httpContext.Object, new RouteData(), new ActionDescriptor()); + + var result = backResult as IActionResult; + Assert.That(result, Is.Not.Null); + + await result.ExecuteResultAsync(context); + + // The BackResult should use the default "/" URL since there is no referrer and no fallback provided + Assert.Pass("Back function correctly used default fallback"); + } + +} \ No newline at end of file From 6056d078cdde25234ce2e383a0aa7d0bc8c6b0fc Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Fri, 19 Sep 2025 13:50:25 -0400 Subject: [PATCH 2/7] always use 302 --- InertiaCore/Utils/BackResult.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/InertiaCore/Utils/BackResult.cs b/InertiaCore/Utils/BackResult.cs index 71baf7e..fdc422d 100644 --- a/InertiaCore/Utils/BackResult.cs +++ b/InertiaCore/Utils/BackResult.cs @@ -15,13 +15,6 @@ public async Task ExecuteResultAsync(ActionContext context) var referrer = context.HttpContext.Request.Headers.Referer.ToString(); var redirectUrl = !string.IsNullOrEmpty(referrer) ? referrer : _fallbackUrl; - if (context.IsInertiaRequest()) - { - context.HttpContext.Response.Headers.Override(InertiaHeader.Location, redirectUrl); - await new StatusCodeResult((int)HttpStatusCode.Conflict).ExecuteResultAsync(context); - return; - } - await new RedirectResult(redirectUrl).ExecuteResultAsync(context); } } From 45a35f8e9bb52b7247490e2beea05666e7e216b3 Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Fri, 19 Sep 2025 13:52:29 -0400 Subject: [PATCH 3/7] update tests --- InertiaCoreTests/UnitTestBack.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/InertiaCoreTests/UnitTestBack.cs b/InertiaCoreTests/UnitTestBack.cs index ffdf3d6..040ccaf 100644 --- a/InertiaCoreTests/UnitTestBack.cs +++ b/InertiaCoreTests/UnitTestBack.cs @@ -16,7 +16,7 @@ namespace InertiaCoreTests; public partial class Tests { [Test] - [Description("Test Back function with Inertia request returns conflict status with location header.")] + [Description("Test Back function with Inertia request returns redirect status with location header.")] public async Task TestBackWithInertiaRequest() { var backResult = _factory.Back("/fallback"); @@ -52,10 +52,10 @@ public async Task TestBackWithInertiaRequest() Assert.Multiple(() => { - Assert.That(responseHeaders.ContainsKey("X-Inertia-Location"), Is.True); - Assert.That(responseHeaders["X-Inertia-Location"].ToString(), Is.EqualTo("back")); - // Verify that status code 409 (Conflict) is set - response.VerifySet(r => r.StatusCode = (int)HttpStatusCode.Conflict, Times.Once); + Assert.That(responseHeaders.ContainsKey("Location"), Is.True); + Assert.That(responseHeaders["Location"].ToString(), Is.EqualTo("back")); + // Verify that status code 302 (Redirect) is set + response.VerifySet(r => r.StatusCode = (int)HttpStatusCode.Redirect, Times.Once); }); } @@ -194,4 +194,4 @@ public async Task TestBackWithDefaultFallback() Assert.Pass("Back function correctly used default fallback"); } -} \ No newline at end of file +} From 70240c7508d752de6874859f5ec5cd21960e2a33 Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Fri, 19 Sep 2025 17:30:12 -0400 Subject: [PATCH 4/7] fix tests --- InertiaCoreTests/UnitTestBack.cs | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/InertiaCoreTests/UnitTestBack.cs b/InertiaCoreTests/UnitTestBack.cs index 040ccaf..876c70c 100644 --- a/InertiaCoreTests/UnitTestBack.cs +++ b/InertiaCoreTests/UnitTestBack.cs @@ -1,8 +1,10 @@ +using System.Collections.Generic; using System.Net; using InertiaCore; using InertiaCore.Extensions; using InertiaCore.Utils; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Infrastructure; @@ -39,24 +41,22 @@ public async Task TestBackWithInertiaRequest() var services = new ServiceCollection(); services.AddSingleton>(new Mock>().Object); services.AddLogging(); + services.AddMvc(); var serviceProvider = services.BuildServiceProvider(); var httpContext = new Mock(); httpContext.SetupGet(c => c.Request).Returns(request.Object); httpContext.SetupGet(c => c.Response).Returns(response.Object); httpContext.SetupGet(c => c.RequestServices).Returns(serviceProvider); + httpContext.SetupGet(c => c.Items).Returns(new Dictionary()); + httpContext.SetupGet(c => c.Features).Returns(new FeatureCollection()); var context = new ActionContext(httpContext.Object, new RouteData(), new ActionDescriptor()); await backResult.ExecuteResultAsync(context); - Assert.Multiple(() => - { - Assert.That(responseHeaders.ContainsKey("Location"), Is.True); - Assert.That(responseHeaders["Location"].ToString(), Is.EqualTo("back")); - // Verify that status code 302 (Redirect) is set - response.VerifySet(r => r.StatusCode = (int)HttpStatusCode.Redirect, Times.Once); - }); + // Since there's no referrer, it should redirect to the fallback URL + response.Verify(r => r.Redirect("/fallback", false), Times.Once); } [Test] @@ -88,12 +88,15 @@ public async Task TestBackWithReferrerHeader() var services = new ServiceCollection(); services.AddSingleton>(new Mock>().Object); services.AddSingleton(new Mock().Object); + services.AddMvc(); var serviceProvider = services.BuildServiceProvider(); var httpContext = new Mock(); httpContext.SetupGet(c => c.Request).Returns(request.Object); httpContext.SetupGet(c => c.Response).Returns(response.Object); httpContext.SetupGet(c => c.RequestServices).Returns(serviceProvider); + httpContext.SetupGet(c => c.Items).Returns(new Dictionary()); + httpContext.SetupGet(c => c.Features).Returns(new FeatureCollection()); var context = new ActionContext(httpContext.Object, new RouteData(), new ActionDescriptor()); @@ -132,12 +135,15 @@ public async Task TestBackWithFallbackUrl() var services = new ServiceCollection(); services.AddSingleton>(new Mock>().Object); services.AddSingleton(new Mock().Object); + services.AddMvc(); var serviceProvider = services.BuildServiceProvider(); var httpContext = new Mock(); httpContext.SetupGet(c => c.Request).Returns(request.Object); httpContext.SetupGet(c => c.Response).Returns(response.Object); httpContext.SetupGet(c => c.RequestServices).Returns(serviceProvider); + httpContext.SetupGet(c => c.Items).Returns(new Dictionary()); + httpContext.SetupGet(c => c.Features).Returns(new FeatureCollection()); var context = new ActionContext(httpContext.Object, new RouteData(), new ActionDescriptor()); @@ -182,6 +188,8 @@ public async Task TestBackWithDefaultFallback() httpContext.SetupGet(c => c.Request).Returns(request.Object); httpContext.SetupGet(c => c.Response).Returns(response.Object); httpContext.SetupGet(c => c.RequestServices).Returns(serviceProvider); + httpContext.SetupGet(c => c.Items).Returns(new Dictionary()); + httpContext.SetupGet(c => c.Features).Returns(new FeatureCollection()); var context = new ActionContext(httpContext.Object, new RouteData(), new ActionDescriptor()); From 2b7caf8a3c4664780097aa8a7510331817d90527 Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Fri, 19 Sep 2025 23:06:27 -0400 Subject: [PATCH 5/7] remove pass messages --- InertiaCoreTests/UnitTestBack.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/InertiaCoreTests/UnitTestBack.cs b/InertiaCoreTests/UnitTestBack.cs index 876c70c..4c666fe 100644 --- a/InertiaCoreTests/UnitTestBack.cs +++ b/InertiaCoreTests/UnitTestBack.cs @@ -104,9 +104,6 @@ public async Task TestBackWithReferrerHeader() Assert.That(result, Is.Not.Null); await result.ExecuteResultAsync(context); - - // The BackResult should use the referrer URL since the request is not an Inertia request - Assert.Pass("Back function correctly handled referrer redirect"); } [Test] @@ -151,9 +148,6 @@ public async Task TestBackWithFallbackUrl() Assert.That(result, Is.Not.Null); await result.ExecuteResultAsync(context); - - // The BackResult should use the fallback URL since there is no referrer - Assert.Pass("Back function correctly used fallback URL"); } [Test] @@ -197,9 +191,6 @@ public async Task TestBackWithDefaultFallback() Assert.That(result, Is.Not.Null); await result.ExecuteResultAsync(context); - - // The BackResult should use the default "/" URL since there is no referrer and no fallback provided - Assert.Pass("Back function correctly used default fallback"); } } From e0c2689f18c622923527150c6e806a6aa58a2633 Mon Sep 17 00:00:00 2001 From: Austin Drummond Date: Sun, 21 Sep 2025 18:49:34 -0400 Subject: [PATCH 6/7] change back http status to 303 --- InertiaCore/Inertia.cs | 5 +- InertiaCore/ResponseFactory.cs | 4 +- InertiaCore/Utils/BackResult.cs | 11 ++- InertiaCoreTests/UnitTestBack.cs | 113 ++++++++++++++----------------- 4 files changed, 63 insertions(+), 70 deletions(-) diff --git a/InertiaCore/Inertia.cs b/InertiaCore/Inertia.cs index edd07ff..0676a7a 100644 --- a/InertiaCore/Inertia.cs +++ b/InertiaCore/Inertia.cs @@ -1,4 +1,5 @@ -using System.Runtime.CompilerServices; +using System.Net; +using System.Runtime.CompilerServices; using InertiaCore.Props; using InertiaCore.Utils; using Microsoft.AspNetCore.Html; @@ -27,7 +28,7 @@ public static class Inertia public static LocationResult Location(string url) => _factory.Location(url); - public static BackResult Back(string? fallbackUrl = null) => _factory.Back(fallbackUrl); + public static BackResult Back(string? fallbackUrl = null, HttpStatusCode statusCode = HttpStatusCode.SeeOther) => _factory.Back(fallbackUrl, statusCode); public static void Share(string key, object? value) => _factory.Share(key, value); diff --git a/InertiaCore/ResponseFactory.cs b/InertiaCore/ResponseFactory.cs index 2b0244f..be3e88b 100644 --- a/InertiaCore/ResponseFactory.cs +++ b/InertiaCore/ResponseFactory.cs @@ -20,7 +20,7 @@ internal interface IResponseFactory public void Version(Func version); public string? GetVersion(); public LocationResult Location(string url); - public BackResult Back(string? fallbackUrl = null); + public BackResult Back(string? fallbackUrl = null, HttpStatusCode statusCode = HttpStatusCode.SeeOther); public void Share(string key, object? value); public void Share(IDictionary data); public AlwaysProp Always(object? value); @@ -109,7 +109,7 @@ public async Task Html(dynamic model) }; public LocationResult Location(string url) => new(url); - public BackResult Back(string? fallbackUrl = null) => new(fallbackUrl); + public BackResult Back(string? fallbackUrl = null, HttpStatusCode statusCode = HttpStatusCode.SeeOther) => new(fallbackUrl, statusCode); public void Share(string key, object? value) { diff --git a/InertiaCore/Utils/BackResult.cs b/InertiaCore/Utils/BackResult.cs index fdc422d..d3bd70d 100644 --- a/InertiaCore/Utils/BackResult.cs +++ b/InertiaCore/Utils/BackResult.cs @@ -7,14 +7,19 @@ namespace InertiaCore.Utils; public class BackResult : IActionResult { private readonly string _fallbackUrl; + private readonly HttpStatusCode _statusCode; - public BackResult(string? fallbackUrl = null) => _fallbackUrl = fallbackUrl ?? "/"; + public BackResult(string? fallbackUrl = null, HttpStatusCode statusCode = HttpStatusCode.SeeOther) => + (_fallbackUrl, _statusCode) = (fallbackUrl ?? "/", statusCode); - public async Task ExecuteResultAsync(ActionContext context) + public Task ExecuteResultAsync(ActionContext context) { var referrer = context.HttpContext.Request.Headers.Referer.ToString(); var redirectUrl = !string.IsNullOrEmpty(referrer) ? referrer : _fallbackUrl; - await new RedirectResult(redirectUrl).ExecuteResultAsync(context); + context.HttpContext.Response.StatusCode = (int)_statusCode; + context.HttpContext.Response.Headers.Location = redirectUrl; + + return Task.CompletedTask; } } diff --git a/InertiaCoreTests/UnitTestBack.cs b/InertiaCoreTests/UnitTestBack.cs index 4c666fe..5122a07 100644 --- a/InertiaCoreTests/UnitTestBack.cs +++ b/InertiaCoreTests/UnitTestBack.cs @@ -31,32 +31,22 @@ public async Task TestBackWithInertiaRequest() var responseHeaders = new HeaderDictionary(); var response = new Mock(); response.SetupGet(r => r.Headers).Returns(responseHeaders); - response.SetupGet(r => r.StatusCode).Returns(0); - response.SetupSet(r => r.StatusCode = It.IsAny()); + response.SetupProperty(r => r.StatusCode); var request = new Mock(); request.SetupGet(r => r.Headers).Returns(headers); - // Set up service provider - var services = new ServiceCollection(); - services.AddSingleton>(new Mock>().Object); - services.AddLogging(); - services.AddMvc(); - var serviceProvider = services.BuildServiceProvider(); - var httpContext = new Mock(); httpContext.SetupGet(c => c.Request).Returns(request.Object); httpContext.SetupGet(c => c.Response).Returns(response.Object); - httpContext.SetupGet(c => c.RequestServices).Returns(serviceProvider); - httpContext.SetupGet(c => c.Items).Returns(new Dictionary()); - httpContext.SetupGet(c => c.Features).Returns(new FeatureCollection()); var context = new ActionContext(httpContext.Object, new RouteData(), new ActionDescriptor()); await backResult.ExecuteResultAsync(context); - // Since there's no referrer, it should redirect to the fallback URL - response.Verify(r => r.Redirect("/fallback", false), Times.Once); + // Should set status code to 303 (SeeOther) and location header to fallback URL + Assert.That(response.Object.StatusCode, Is.EqualTo(303)); + Assert.That(responseHeaders["Location"].ToString(), Is.EqualTo("/fallback")); } [Test] @@ -71,32 +61,16 @@ public async Task TestBackWithReferrerHeader() }; var responseHeaders = new HeaderDictionary(); - string? redirectLocation = null; var response = new Mock(); response.SetupGet(r => r.Headers).Returns(responseHeaders); - response.SetupGet(r => r.StatusCode).Returns(0); - response.SetupSet(r => r.StatusCode = It.IsAny()); - response.Setup(r => r.Redirect(It.IsAny())) - .Callback(location => redirectLocation = location); + response.SetupProperty(r => r.StatusCode); var request = new Mock(); request.SetupGet(r => r.Headers).Returns(headers); - request.SetupGet(r => r.Scheme).Returns("https"); - request.SetupGet(r => r.Host).Returns(new HostString("example.com")); - - // Set up service provider - var services = new ServiceCollection(); - services.AddSingleton>(new Mock>().Object); - services.AddSingleton(new Mock().Object); - services.AddMvc(); - var serviceProvider = services.BuildServiceProvider(); var httpContext = new Mock(); httpContext.SetupGet(c => c.Request).Returns(request.Object); httpContext.SetupGet(c => c.Response).Returns(response.Object); - httpContext.SetupGet(c => c.RequestServices).Returns(serviceProvider); - httpContext.SetupGet(c => c.Items).Returns(new Dictionary()); - httpContext.SetupGet(c => c.Features).Returns(new FeatureCollection()); var context = new ActionContext(httpContext.Object, new RouteData(), new ActionDescriptor()); @@ -104,6 +78,10 @@ public async Task TestBackWithReferrerHeader() Assert.That(result, Is.Not.Null); await result.ExecuteResultAsync(context); + + // Should set status code to 303 (SeeOther) and location header to referrer + Assert.That(response.Object.StatusCode, Is.EqualTo(303)); + Assert.That(responseHeaders["Location"].ToString(), Is.EqualTo("https://example.com/previous-page")); } [Test] @@ -115,32 +93,16 @@ public async Task TestBackWithFallbackUrl() var headers = new HeaderDictionary(); var responseHeaders = new HeaderDictionary(); - string? redirectLocation = null; var response = new Mock(); response.SetupGet(r => r.Headers).Returns(responseHeaders); - response.SetupGet(r => r.StatusCode).Returns(0); - response.SetupSet(r => r.StatusCode = It.IsAny()); - response.Setup(r => r.Redirect(It.IsAny())) - .Callback(location => redirectLocation = location); + response.SetupProperty(r => r.StatusCode); var request = new Mock(); request.SetupGet(r => r.Headers).Returns(headers); - request.SetupGet(r => r.Scheme).Returns("https"); - request.SetupGet(r => r.Host).Returns(new HostString("example.com")); - - // Set up service provider - var services = new ServiceCollection(); - services.AddSingleton>(new Mock>().Object); - services.AddSingleton(new Mock().Object); - services.AddMvc(); - var serviceProvider = services.BuildServiceProvider(); var httpContext = new Mock(); httpContext.SetupGet(c => c.Request).Returns(request.Object); httpContext.SetupGet(c => c.Response).Returns(response.Object); - httpContext.SetupGet(c => c.RequestServices).Returns(serviceProvider); - httpContext.SetupGet(c => c.Items).Returns(new Dictionary()); - httpContext.SetupGet(c => c.Features).Returns(new FeatureCollection()); var context = new ActionContext(httpContext.Object, new RouteData(), new ActionDescriptor()); @@ -148,6 +110,10 @@ public async Task TestBackWithFallbackUrl() Assert.That(result, Is.Not.Null); await result.ExecuteResultAsync(context); + + // Should set status code to 303 (SeeOther) and location header to custom fallback + Assert.That(response.Object.StatusCode, Is.EqualTo(303)); + Assert.That(responseHeaders["Location"].ToString(), Is.EqualTo("/custom-fallback")); } [Test] @@ -159,31 +125,48 @@ public async Task TestBackWithDefaultFallback() var headers = new HeaderDictionary(); var responseHeaders = new HeaderDictionary(); - string? redirectLocation = null; var response = new Mock(); response.SetupGet(r => r.Headers).Returns(responseHeaders); - response.SetupGet(r => r.StatusCode).Returns(0); - response.SetupSet(r => r.StatusCode = It.IsAny()); - response.Setup(r => r.Redirect(It.IsAny())) - .Callback(location => redirectLocation = location); + response.SetupProperty(r => r.StatusCode); var request = new Mock(); request.SetupGet(r => r.Headers).Returns(headers); - request.SetupGet(r => r.Scheme).Returns("https"); - request.SetupGet(r => r.Host).Returns(new HostString("example.com")); - // Set up service provider - var services = new ServiceCollection(); - services.AddSingleton>(new Mock>().Object); - services.AddSingleton(new Mock().Object); - var serviceProvider = services.BuildServiceProvider(); + var httpContext = new Mock(); + httpContext.SetupGet(c => c.Request).Returns(request.Object); + httpContext.SetupGet(c => c.Response).Returns(response.Object); + + var context = new ActionContext(httpContext.Object, new RouteData(), new ActionDescriptor()); + + var result = backResult as IActionResult; + Assert.That(result, Is.Not.Null); + + await result.ExecuteResultAsync(context); + + // Should set status code to 303 (SeeOther) and location header to default fallback + Assert.That(response.Object.StatusCode, Is.EqualTo(303)); + Assert.That(responseHeaders["Location"].ToString(), Is.EqualTo("/")); + } + + [Test] + [Description("Test Back function with permanent redirect.")] + public async Task TestBackWithPermanentRedirect() + { + var backResult = _factory.Back("/fallback", HttpStatusCode.MovedPermanently); + + var headers = new HeaderDictionary(); + + var responseHeaders = new HeaderDictionary(); + var response = new Mock(); + response.SetupGet(r => r.Headers).Returns(responseHeaders); + response.SetupProperty(r => r.StatusCode); + + var request = new Mock(); + request.SetupGet(r => r.Headers).Returns(headers); var httpContext = new Mock(); httpContext.SetupGet(c => c.Request).Returns(request.Object); httpContext.SetupGet(c => c.Response).Returns(response.Object); - httpContext.SetupGet(c => c.RequestServices).Returns(serviceProvider); - httpContext.SetupGet(c => c.Items).Returns(new Dictionary()); - httpContext.SetupGet(c => c.Features).Returns(new FeatureCollection()); var context = new ActionContext(httpContext.Object, new RouteData(), new ActionDescriptor()); @@ -191,6 +174,10 @@ public async Task TestBackWithDefaultFallback() Assert.That(result, Is.Not.Null); await result.ExecuteResultAsync(context); + + // Should set status code to 301 (MovedPermanently) and location header to fallback + Assert.That(response.Object.StatusCode, Is.EqualTo(301)); + Assert.That(responseHeaders["Location"].ToString(), Is.EqualTo("/fallback")); } } From 52c43a25b585dde8094c5410ddabb27f39a89f4b Mon Sep 17 00:00:00 2001 From: Kacper Ziubryniewicz Date: Sun, 9 Nov 2025 09:49:09 +0100 Subject: [PATCH 7/7] Format --- InertiaCore/Inertia.cs | 3 ++- InertiaCore/ResponseFactory.cs | 4 +++- InertiaCoreTests/UnitTestBack.cs | 9 --------- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/InertiaCore/Inertia.cs b/InertiaCore/Inertia.cs index 0676a7a..fd99eb2 100644 --- a/InertiaCore/Inertia.cs +++ b/InertiaCore/Inertia.cs @@ -28,7 +28,8 @@ public static class Inertia public static LocationResult Location(string url) => _factory.Location(url); - public static BackResult Back(string? fallbackUrl = null, HttpStatusCode statusCode = HttpStatusCode.SeeOther) => _factory.Back(fallbackUrl, statusCode); + public static BackResult Back(string? fallbackUrl = null, HttpStatusCode statusCode = HttpStatusCode.SeeOther) => + _factory.Back(fallbackUrl, statusCode); public static void Share(string key, object? value) => _factory.Share(key, value); diff --git a/InertiaCore/ResponseFactory.cs b/InertiaCore/ResponseFactory.cs index be3e88b..69bff94 100644 --- a/InertiaCore/ResponseFactory.cs +++ b/InertiaCore/ResponseFactory.cs @@ -109,7 +109,9 @@ public async Task Html(dynamic model) }; public LocationResult Location(string url) => new(url); - public BackResult Back(string? fallbackUrl = null, HttpStatusCode statusCode = HttpStatusCode.SeeOther) => new(fallbackUrl, statusCode); + + public BackResult Back(string? fallbackUrl = null, HttpStatusCode statusCode = HttpStatusCode.SeeOther) => + new(fallbackUrl, statusCode); public void Share(string key, object? value) { diff --git a/InertiaCoreTests/UnitTestBack.cs b/InertiaCoreTests/UnitTestBack.cs index 5122a07..f3d0edc 100644 --- a/InertiaCoreTests/UnitTestBack.cs +++ b/InertiaCoreTests/UnitTestBack.cs @@ -1,16 +1,8 @@ -using System.Collections.Generic; using System.Net; -using InertiaCore; -using InertiaCore.Extensions; -using InertiaCore.Utils; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; -using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; using Moq; namespace InertiaCoreTests; @@ -179,5 +171,4 @@ public async Task TestBackWithPermanentRedirect() Assert.That(response.Object.StatusCode, Is.EqualTo(301)); Assert.That(responseHeaders["Location"].ToString(), Is.EqualTo("/fallback")); } - }