From 34e6e814d1f3a24c1c2762c14eddee0c2c86716e Mon Sep 17 00:00:00 2001 From: lvchkn <> Date: Sun, 20 Aug 2023 03:05:00 +0200 Subject: [PATCH 1/2] add paging --- Application/Sort/ISorter.cs | 2 +- Application/Stories/IStoriesRepository.cs | 2 +- Application/Stories/IStoriesService.cs | 2 +- Application/Stories/StoriesService.cs | 7 +- .../Controllers/StoriesController.cs | 4 +- .../Db/Repositories/StoriesRepository.cs | 10 ++- Infrastructure/Db/StoriesSorter.cs | 4 +- Tests/Integration/StoriesControllerTests.cs | 65 +++++++++++-------- 8 files changed, 59 insertions(+), 37 deletions(-) diff --git a/Application/Sort/ISorter.cs b/Application/Sort/ISorter.cs index 5af0dd0..c374c47 100644 --- a/Application/Sort/ISorter.cs +++ b/Application/Sort/ISorter.cs @@ -2,6 +2,6 @@ public interface ISorter where T : class { - List Sort(IQueryable unsorted, IEnumerable parameters); + IQueryable Sort(IQueryable unsorted, IEnumerable parameters); } diff --git a/Application/Stories/IStoriesRepository.cs b/Application/Stories/IStoriesRepository.cs index 38d2f4e..a695440 100644 --- a/Application/Stories/IStoriesRepository.cs +++ b/Application/Stories/IStoriesRepository.cs @@ -8,7 +8,7 @@ public interface IStoriesRepository Task GetByIdAsync(int id); Task> GetByAuthorAsync(string author); Task> GetAllAsync(); - List GetAll(IEnumerable sortingParameters, string? search); + List GetAll(IEnumerable sortingParameters, string? search, int skip, int top); Task AddAsync(Story story); Task UpdateAsync(int id, Story updatedStory); Task DeleteAsync(int id); diff --git a/Application/Stories/IStoriesService.cs b/Application/Stories/IStoriesService.cs index 02551a7..c8e8b19 100644 --- a/Application/Stories/IStoriesService.cs +++ b/Application/Stories/IStoriesService.cs @@ -4,5 +4,5 @@ public interface IStoriesService { Task AddAsync(StoryDto storyDto); Task> GetAllAsync(); - List GetStories(string? orderBy, string? search); + List GetStories(string? orderBy, string? search, int pageNumber, int pageSize); } \ No newline at end of file diff --git a/Application/Stories/StoriesService.cs b/Application/Stories/StoriesService.cs index 4628e6a..4971fd4 100644 --- a/Application/Stories/StoriesService.cs +++ b/Application/Stories/StoriesService.cs @@ -32,10 +32,13 @@ public async Task> GetAllAsync() return _mapper.Map>(stories); } - public List GetStories(string? orderBy, string? search) + public List GetStories(string? orderBy, string? search, int pageNumber, int pageSize) { var parsedSortingParameters = _sortingParameteresParser.Parse(orderBy); - var sortedStories = _storiesRepository.GetAll(parsedSortingParameters, search); + var skip = (pageNumber - 1) * pageSize; + var take = pageSize; + + var sortedStories = _storiesRepository.GetAll(parsedSortingParameters, search, skip, take); return _mapper.Map>(sortedStories); } diff --git a/HackerNewsCommentsFeed/Controllers/StoriesController.cs b/HackerNewsCommentsFeed/Controllers/StoriesController.cs index cc4f835..a2deaf3 100644 --- a/HackerNewsCommentsFeed/Controllers/StoriesController.cs +++ b/HackerNewsCommentsFeed/Controllers/StoriesController.cs @@ -11,9 +11,11 @@ public static IEndpointRouteBuilder MapItemsEndpoints(this IEndpointRouteBuilder app.MapGet("/api/stories", ( [FromQuery] string? orderBy, [FromQuery] string? search, + [FromQuery] int? pageNumber, + [FromQuery] int? pageSize, [FromServices] IStoriesService storiesService) => { - var stories = storiesService.GetStories(orderBy, search); + var stories = storiesService.GetStories(orderBy, search, pageNumber ?? 1, pageSize ?? 10); return Results.Ok(stories); diff --git a/Infrastructure/Db/Repositories/StoriesRepository.cs b/Infrastructure/Db/Repositories/StoriesRepository.cs index 6d218e9..23bed00 100644 --- a/Infrastructure/Db/Repositories/StoriesRepository.cs +++ b/Infrastructure/Db/Repositories/StoriesRepository.cs @@ -61,7 +61,7 @@ public async Task> GetAllAsync() return stories; } - public List GetAll(IEnumerable sortingParameters, string? search) + public List GetAll(IEnumerable sortingParameters, string? search, int skip, int top) { var included = _dbContext.Stories .AsNoTracking() @@ -70,7 +70,13 @@ public List GetAll(IEnumerable sortingParameters, stri .AsSplitQuery(); var filteredStories = _storiesFilter.Filter(included, search); - var sortedStories = _storiesSorter.Sort(filteredStories, sortingParameters); + + var sortedStories = _storiesSorter + .Sort(filteredStories, sortingParameters) + .OrderByDescending(s => s.Time) + .Skip(skip) + .Take(top) + .ToList(); return sortedStories; } diff --git a/Infrastructure/Db/StoriesSorter.cs b/Infrastructure/Db/StoriesSorter.cs index 6e56bb1..287fe98 100644 --- a/Infrastructure/Db/StoriesSorter.cs +++ b/Infrastructure/Db/StoriesSorter.cs @@ -5,7 +5,7 @@ namespace Infrastructure.Db; public class StoriesSorter : ISorter { - public List Sort(IQueryable stories, IEnumerable parameters) + public IQueryable Sort(IQueryable stories, IEnumerable parameters) { var index = 0; @@ -16,7 +16,7 @@ public List Sort(IQueryable stories, IEnumerable UpdateListOrder( diff --git a/Tests/Integration/StoriesControllerTests.cs b/Tests/Integration/StoriesControllerTests.cs index 76b4bc9..2d75d75 100644 --- a/Tests/Integration/StoriesControllerTests.cs +++ b/Tests/Integration/StoriesControllerTests.cs @@ -17,6 +17,11 @@ public class StoriesControllerTests private readonly CustomWebApplicationFactory _webAppFactory; private record CreateInterestRequest(string Text, int? Id); + private readonly JsonSerializerOptions _jsonSerializerOptions = new () + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + public StoriesControllerTests(CustomWebApplicationFactory webAppFactory) { _webAppFactory = webAppFactory; @@ -26,14 +31,9 @@ public StoriesControllerTests(CustomWebApplicationFactory webAppFactory public async Task All_stories_are_returned_when_no_query_provided() { // Arrange - var jsonSerializerOptions = new JsonSerializerOptions() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - }; - var client = _webAppFactory.CreateClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Test"); - + using var scope = _webAppFactory.Services.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService(); dbContext?.SeedStories(); @@ -41,7 +41,7 @@ public async Task All_stories_are_returned_when_no_query_provided() // Act var response = await client.GetAsync("/api/stories"); var responseJson = await response.Content.ReadAsStringAsync(); - var returnedStories = JsonSerializer.Deserialize(responseJson, jsonSerializerOptions); + var returnedStories = JsonSerializer.Deserialize(responseJson, _jsonSerializerOptions); // Assert returnedStories.Should().NotBeNull(); @@ -52,14 +52,9 @@ public async Task All_stories_are_returned_when_no_query_provided() public async Task Stories_are_sorted_by_title_in_desc_order() { // Arrange - var jsonSerializerOptions = new JsonSerializerOptions() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - }; - var client = _webAppFactory.CreateClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Test"); - + using var scope = _webAppFactory.Services.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService(); dbContext?.SeedStories(); @@ -72,7 +67,7 @@ public async Task Stories_are_sorted_by_title_in_desc_order() // Act var response = await client.GetAsync("/api/stories?orderBy=title,asc"); var responseJson = await response.Content.ReadAsStringAsync(); - var returnedStories = JsonSerializer.Deserialize(responseJson, jsonSerializerOptions); + var returnedStories = JsonSerializer.Deserialize(responseJson, _jsonSerializerOptions); // Assert returnedStories.Should().BeEquivalentTo(expectedResults); @@ -85,14 +80,9 @@ public async Task Stories_are_sorted_by_title_in_desc_order() public async Task Stories_are_filtered_by_title_with_fuzzy_search(string search) { // Arrange - var jsonSerializerOptions = new JsonSerializerOptions() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - }; - var client = _webAppFactory.CreateClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Test"); - + using var scope = _webAppFactory.Services.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService(); dbContext?.SeedStories(); @@ -100,7 +90,7 @@ public async Task Stories_are_filtered_by_title_with_fuzzy_search(string search) // Act var response = await client.GetAsync($"/api/stories?search={search}"); var responseJson = await response.Content.ReadAsStringAsync(); - var returnedStories = JsonSerializer.Deserialize(responseJson, jsonSerializerOptions); + var returnedStories = JsonSerializer.Deserialize(responseJson, _jsonSerializerOptions); // Assert returnedStories?.Length.Should().Be(5); @@ -113,11 +103,6 @@ public async Task Stories_are_filtered_by_title_with_fuzzy_search(string search) public async Task Stories_are_ordered_and_filtered(string search) { // Arrange - var jsonSerializerOptions = new JsonSerializerOptions() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - }; - var client = _webAppFactory.CreateClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Test"); @@ -135,12 +120,38 @@ public async Task Stories_are_ordered_and_filtered(string search) // Act var response = await client.GetAsync($"/api/stories?orderBy={orderBy}&search={search}"); var responseJson = await response.Content.ReadAsStringAsync(); - var returnedStories = JsonSerializer.Deserialize(responseJson, jsonSerializerOptions); + var returnedStories = JsonSerializer.Deserialize(responseJson, _jsonSerializerOptions); // Assert returnedStories?.Should().BeEquivalentTo(expectedResults); } + [Theory] + [InlineData(2, 2, 2)] + [InlineData(2, 1, 1)] + [InlineData(4, 1, 1)] + [InlineData(5, 0, 0)] + [InlineData(5, 1, 1)] + [InlineData(1, 5, 5)] + [InlineData(3, 2, 1)] + public async Task Pagination_Works(int pageNumber, int pageSize, int result) + { + // Arrange + var client = _webAppFactory.CreateClient(); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Test"); + + using var scope = _webAppFactory.Services.CreateScope(); + var dbContext = scope.ServiceProvider.GetRequiredService(); + dbContext?.SeedStories(); + + // Act + var response = await client.GetAsync($"/api/stories?pageNumber={pageNumber}&pageSize={pageSize}"); + var responseJson = await response.Content.ReadAsStringAsync(); + var returnedStories = JsonSerializer.Deserialize(responseJson, _jsonSerializerOptions); + // Assert + returnedStories?.Length.Should().Be(result); + } + private static StoryDto MapToStoryDto(Story story) { return new StoryDto() From 398c2a784991c7170a52280b7cc35b58a9fc274c Mon Sep 17 00:00:00 2001 From: lvchkn <> Date: Sun, 20 Aug 2023 03:08:39 +0200 Subject: [PATCH 2/2] rename parameter --- Application/Stories/IStoriesRepository.cs | 2 +- Infrastructure/Db/Repositories/StoriesRepository.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Application/Stories/IStoriesRepository.cs b/Application/Stories/IStoriesRepository.cs index a695440..c69c878 100644 --- a/Application/Stories/IStoriesRepository.cs +++ b/Application/Stories/IStoriesRepository.cs @@ -8,7 +8,7 @@ public interface IStoriesRepository Task GetByIdAsync(int id); Task> GetByAuthorAsync(string author); Task> GetAllAsync(); - List GetAll(IEnumerable sortingParameters, string? search, int skip, int top); + List GetAll(IEnumerable sortingParameters, string? search, int skip, int take); Task AddAsync(Story story); Task UpdateAsync(int id, Story updatedStory); Task DeleteAsync(int id); diff --git a/Infrastructure/Db/Repositories/StoriesRepository.cs b/Infrastructure/Db/Repositories/StoriesRepository.cs index 23bed00..02fef67 100644 --- a/Infrastructure/Db/Repositories/StoriesRepository.cs +++ b/Infrastructure/Db/Repositories/StoriesRepository.cs @@ -61,7 +61,7 @@ public async Task> GetAllAsync() return stories; } - public List GetAll(IEnumerable sortingParameters, string? search, int skip, int top) + public List GetAll(IEnumerable sortingParameters, string? search, int skip, int take) { var included = _dbContext.Stories .AsNoTracking() @@ -75,7 +75,7 @@ public List GetAll(IEnumerable sortingParameters, stri .Sort(filteredStories, sortingParameters) .OrderByDescending(s => s.Time) .Skip(skip) - .Take(top) + .Take(take) .ToList(); return sortedStories;