diff --git a/src/FluentCMS.Admin/Components/Pages/Desk.razor b/src/FluentCMS.Admin/Components/Pages/Desk.razor index bb86fb5ca..d48d98185 100644 --- a/src/FluentCMS.Admin/Components/Pages/Desk.razor +++ b/src/FluentCMS.Admin/Components/Pages/Desk.razor @@ -223,8 +223,9 @@ [SupplyParameterFromQuery(Name = "queue")] private string? QueueSlug { get; set; } + // Read as text, so a page that is not a number, or too large for one, shows the first page rather than failing the request. [SupplyParameterFromQuery(Name = "page")] - private int? PageNumber { get; set; } + private string? PageText { get; set; } // What the set view did before it came back here, because the queue it was reviewing is now empty. [SupplyParameterFromQuery(Name = "done")] @@ -250,7 +251,7 @@ { // An unknown queue shows the default one rather than an error, as a mistyped link would otherwise land nowhere. _queue = Slugs.FirstOrDefault(pair => pair.Value == QueueSlug).Key; - _page = Math.Max(1, PageNumber ?? 1); + _page = int.TryParse(PageText, NumberStyles.Integer, CultureInfo.InvariantCulture, out var page) ? Math.Max(1, page) : 1; _offset = (int)Math.Min((_page - 1L) * PageSize, int.MaxValue - PageSize); var counts = await Queues.CountAsync(); diff --git a/src/FluentCMS.Admin/Components/Pages/SecurityEventsView.razor b/src/FluentCMS.Admin/Components/Pages/SecurityEventsView.razor index a16359528..9be85866e 100644 --- a/src/FluentCMS.Admin/Components/Pages/SecurityEventsView.razor +++ b/src/FluentCMS.Admin/Components/Pages/SecurityEventsView.razor @@ -99,12 +99,13 @@ else private int _offset; private string? _error; + // Read as text, so a page that is not a number, or too large for one, shows the first page rather than failing the request. [SupplyParameterFromQuery(Name = "page")] - private int? PageNumber { get; set; } + private string? PageText { get; set; } protected override async Task OnInitializedAsync() { - _page = Math.Max(1, PageNumber ?? 1); + _page = int.TryParse(PageText, NumberStyles.Integer, CultureInfo.InvariantCulture, out var page) ? Math.Max(1, page) : 1; _offset = (int)Math.Min((_page - 1L) * PageSize, int.MaxValue - PageSize); var listed = await AuditLists.ListAsync(new AuditEventListQuery { Operations = SecurityEvents.Operations, Offset = _offset, Limit = PageSize }); diff --git a/tests/FluentCMS.IntegrationTests/Desk/DeskScreenTests.cs b/tests/FluentCMS.IntegrationTests/Desk/DeskScreenTests.cs index e62b3875c..4a7106cd7 100644 --- a/tests/FluentCMS.IntegrationTests/Desk/DeskScreenTests.cs +++ b/tests/FluentCMS.IntegrationTests/Desk/DeskScreenTests.cs @@ -62,6 +62,21 @@ public async Task The_desk_renders_right_to_left() Assert.Contains("data-testid=\"desk-queues\"", desk); } + [Fact] + public async Task A_page_that_is_not_a_number_shows_the_first_page() + { + using var install = new TemporaryInstall(); + await using var host = await AdminHost.StartAsync(install); + await host.CreateFirstAdministratorAsync(); + await host.SignInAsync(AdminHost.Owner); + + foreach (var path in new[] { "/?queue=reverted&page=abc", "/?queue=reverted&page=99999999999", "/security?page=99999999999", "/security?page=abc" }) + { + var response = await host.Client.GetAsync(path); + Assert.True(response.StatusCode == System.Net.HttpStatusCode.OK, $"{path}: {(int)response.StatusCode}"); + } + } + private static async Task AwaitingReviewAsync(AdminHost host, Guid userId, string slug) { var type = await host.AsUserAsync(userId, async directory => diff --git a/tests/FluentCMS.Tests/Admin/DeskTests.cs b/tests/FluentCMS.Tests/Admin/DeskTests.cs index 341d37e83..c23822c4c 100644 --- a/tests/FluentCMS.Tests/Admin/DeskTests.cs +++ b/tests/FluentCMS.Tests/Admin/DeskTests.cs @@ -111,6 +111,9 @@ public void The_queue_and_page_come_from_the_address() [InlineData("/?queue=nonsense", DeskQueue.AwaitingMe, 0)] [InlineData("/?queue=reverted&page=0", DeskQueue.Reverted, 0)] [InlineData("/?queue=reverted&page=-3", DeskQueue.Reverted, 0)] + [InlineData("/?queue=reverted&page=abc", DeskQueue.Reverted, 0)] + [InlineData("/?queue=reverted&page=99999999999", DeskQueue.Reverted, 0)] + [InlineData("/?queue=reverted&page=", DeskQueue.Reverted, 0)] public void An_unknown_queue_or_page_falls_back_to_the_first(string address, DeskQueue queue, int offset) { var queues = new FakeQueues(); diff --git a/tests/FluentCMS.Tests/Admin/SecurityEventsViewTests.cs b/tests/FluentCMS.Tests/Admin/SecurityEventsViewTests.cs index 6a8c16a2e..6e03e8a74 100644 --- a/tests/FluentCMS.Tests/Admin/SecurityEventsViewTests.cs +++ b/tests/FluentCMS.Tests/Admin/SecurityEventsViewTests.cs @@ -50,6 +50,21 @@ public void The_page_comes_from_the_address_and_links_to_its_neighbours() Assert.Equal("security?page=3", cut.Find("a[rel=next]").GetAttribute("href")); } + [Theory] + [InlineData("/security?page=abc")] + [InlineData("/security?page=99999999999")] + [InlineData("/security?page=")] + public void A_page_that_is_not_a_number_shows_the_first(string address) + { + var lists = new FakeLists { Total = 160 }; + using var context = Context(lists); + context.Services.GetRequiredService().NavigateTo(address); + + context.Render(); + + Assert.Equal(0, lists.Queries[0].Offset); + } + [Fact] public void An_empty_log_says_so() {