diff --git a/ArenaService.Shared/Dtos/PagedSeasonsResponse.cs b/ArenaService.Shared/Dtos/PagedSeasonsResponse.cs new file mode 100644 index 0000000..83a7e1a --- /dev/null +++ b/ArenaService.Shared/Dtos/PagedSeasonsResponse.cs @@ -0,0 +1,14 @@ +using Libplanet.Crypto; + +namespace ArenaService.Shared.Dtos; + +public class PagedSeasonsResponse +{ + public required List Seasons { get; set; } + public required int TotalCount { get; set; } + public required int PageNumber { get; set; } + public required int PageSize { get; set; } + public required int TotalPages { get; set; } + public required bool HasNextPage { get; set; } + public required bool HasPreviousPage { get; set; } +} \ No newline at end of file diff --git a/ArenaService.Shared/Services/SeasonService.cs b/ArenaService.Shared/Services/SeasonService.cs index 1264262..5a52489 100644 --- a/ArenaService.Shared/Services/SeasonService.cs +++ b/ArenaService.Shared/Services/SeasonService.cs @@ -24,6 +24,12 @@ Task> ClassifyByChampionship( Task GetLastSeasonByBlockIndexAsync(long blockIndex); Task> GetCompletedSeasonsBeforeBlock(long blockIndex); + + Task<(List Seasons, int TotalCount, int TotalPages, bool HasNextPage, bool HasPreviousPage)> GetSeasonsPagedAsync( + int pageNumber, + int pageSize, + Func, IQueryable>? includeQuery = null + ); } public class SeasonService : ISeasonService @@ -149,4 +155,19 @@ public async Task> GetCompletedSeasonsBeforeBlock(long blockIndex) .OrderByDescending(s => s.EndBlock) .ToList(); } + + public async Task<(List Seasons, int TotalCount, int TotalPages, bool HasNextPage, bool HasPreviousPage)> GetSeasonsPagedAsync( + int pageNumber, + int pageSize, + Func, IQueryable>? includeQuery = null + ) + { + var seasons = await _seasonRepo.GetSeasonsPagedAsync(pageNumber, pageSize, includeQuery); + var totalCount = await _seasonRepo.GetTotalSeasonsCountAsync(); + var totalPages = (int)Math.Ceiling((double)totalCount / pageSize); + var hasNextPage = pageNumber < totalPages; + var hasPreviousPage = pageNumber > 1; + + return (seasons, totalCount, totalPages, hasNextPage, hasPreviousPage); + } } diff --git a/ArenaService.Tests/Controllers/SeasonControllerTests.cs b/ArenaService.Tests/Controllers/SeasonControllerTests.cs index c143758..fd76d6c 100644 --- a/ArenaService.Tests/Controllers/SeasonControllerTests.cs +++ b/ArenaService.Tests/Controllers/SeasonControllerTests.cs @@ -1,22 +1,125 @@ -// using ArenaService.Controllers; -// using ArenaService.Shared.Dtos; -// using ArenaService.Shared.Models; -// using ArenaService.Shared.Repositories; -// using Microsoft.AspNetCore.Http.HttpResults; -// using Moq; - -// namespace ArenaService.Tests.Controllers; - -// public class SeasonControllerTests -// { -// private readonly SeasonController _controller; -// private Mock _repoMock; - -// public SeasonControllerTests() -// { -// var repoMock = new Mock(); -// _repoMock = repoMock; -// _controller = new SeasonController(_repoMock.Object); -// } - -// } +using ArenaService.Controllers; +using ArenaService.Shared.Constants; +using ArenaService.Shared.Dtos; +using ArenaService.Shared.Models; +using ArenaService.Shared.Models.BattleTicket; +using ArenaService.Shared.Models.RefreshTicket; +using ArenaService.Shared.Models.Ticket; +using ArenaService.Shared.Repositories; +using ArenaService.Shared.Services; +using ArenaService.Options; +using Libplanet.Crypto; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using Moq; + +namespace ArenaService.Tests.Controllers; + +public class SeasonControllerTests +{ + private readonly SeasonController _controller; + private readonly Mock _repoMock; + private readonly Mock _serviceMock; + private readonly Address _recipientAddress; + + public SeasonControllerTests() + { + _repoMock = new Mock(); + _serviceMock = new Mock(); + _recipientAddress = new Address("0x0000000000000000000000000000000000000000"); + + var options = new Mock>(); + options.Setup(x => x.Value).Returns(new OpsConfigOptions + { + RecipientAddress = _recipientAddress.ToString(), + JwtSecretKey = "test-secret-key", + JwtPublicKey = "test-public-key", + ArenaProviderName = "test-provider", + HangfireUsername = "test-user", + HangfirePassword = "test-password" + }); + + _controller = new SeasonController(_repoMock.Object, _serviceMock.Object, options.Object); + } + + [Fact] + public async Task GetSeasonsPaged_ValidParameters_ReturnsOkResult() + { + var seasons = new List + { + new Season + { + Id = 1, + SeasonGroupId = 1, + ArenaType = ArenaType.CHAMPIONSHIP, + StartBlock = 1000, + EndBlock = 2000, + RoundInterval = 100, + RequiredMedalCount = 10, + TotalPrize = 1000, + BattleTicketPolicy = new BattleTicketPolicy { Id = 1, Name = "Test" }, + RefreshTicketPolicy = new RefreshTicketPolicy { Id = 1, Name = "Test" }, + Rounds = new List() + } + }; + + _serviceMock.Setup(x => x.GetSeasonsPagedAsync(1, 10, It.IsAny, IQueryable>>())) + .ReturnsAsync((seasons, 1, 1, false, false)); + + var result = await _controller.GetSeasonsPaged(1, 10); + + var okResult = Assert.IsType(result); + var response = Assert.IsType(okResult.Value); + + Assert.Single(response.Seasons); + Assert.Equal(1, response.TotalCount); + Assert.Equal(1, response.PageNumber); + Assert.Equal(10, response.PageSize); + Assert.Equal(1, response.TotalPages); + Assert.False(response.HasNextPage); + Assert.False(response.HasPreviousPage); + } + + [Fact] + public async Task GetSeasonsPaged_InvalidPageNumber_ReturnsBadRequest() + { + var result = await _controller.GetSeasonsPaged(0, 10); + + var badRequestResult = Assert.IsType(result); + Assert.Equal("Page number must be greater than 0", badRequestResult.Value); + } + + [Fact] + public async Task GetSeasonsPaged_InvalidPageSize_ReturnsBadRequest() + { + var result = await _controller.GetSeasonsPaged(1, 0); + + var badRequestResult = Assert.IsType(result); + Assert.Equal("Page size must be between 1 and 100", badRequestResult.Value); + } + + [Fact] + public async Task GetSeasonsPaged_PageSizeTooLarge_ReturnsBadRequest() + { + var result = await _controller.GetSeasonsPaged(1, 101); + + var badRequestResult = Assert.IsType(result); + Assert.Equal("Page size must be between 1 and 100", badRequestResult.Value); + } + + [Fact] + public async Task GetSeasonsPaged_DefaultParameters_ReturnsCorrectPageInfo() + { + var seasons = new List(); + _serviceMock.Setup(x => x.GetSeasonsPagedAsync(1, 10, It.IsAny, IQueryable>>())) + .ReturnsAsync((seasons, 0, 0, false, false)); + + var result = await _controller.GetSeasonsPaged(); + + var okResult = Assert.IsType(result); + var response = Assert.IsType(okResult.Value); + + Assert.Equal(1, response.PageNumber); + Assert.Equal(10, response.PageSize); + } +} diff --git a/ArenaService/Controllers/SeasonController.cs b/ArenaService/Controllers/SeasonController.cs index fe0ac91..fa3a493 100644 --- a/ArenaService/Controllers/SeasonController.cs +++ b/ArenaService/Controllers/SeasonController.cs @@ -76,4 +76,44 @@ public async Task GetSeasons(long blockIndex) return Ok(response); } + + [HttpGet] + [SwaggerResponse(StatusCodes.Status200OK, "PagedSeasonsResponse", typeof(PagedSeasonsResponse))] + public async Task GetSeasonsPaged( + [FromQuery] int pageNumber = 1, + [FromQuery] int pageSize = 10 + ) + { + if (pageNumber < 1) + { + return BadRequest("Page number must be greater than 0"); + } + + if (pageSize < 1 || pageSize > 100) + { + return BadRequest("Page size must be between 1 and 100"); + } + + var (seasons, totalCount, totalPages, hasNextPage, hasPreviousPage) = await _seasonService.GetSeasonsPagedAsync( + pageNumber, + pageSize, + q => + q.Include(s => s.BattleTicketPolicy) + .Include(s => s.RefreshTicketPolicy) + .Include(s => s.Rounds) + ); + + var response = new PagedSeasonsResponse + { + Seasons = seasons.Select(s => s.ToResponse()).ToList(), + TotalCount = totalCount, + PageNumber = pageNumber, + PageSize = pageSize, + TotalPages = totalPages, + HasNextPage = hasNextPage, + HasPreviousPage = hasPreviousPage + }; + + return Ok(response); + } }