From f6ac586f2815813e026d983638c995526fd33139 Mon Sep 17 00:00:00 2001 From: mehdiboudjoudi Date: Wed, 5 Mar 2025 10:27:24 +0100 Subject: [PATCH] TD-34: Endpoint for getting all players and tests for this endpoint --- api/api.yml | 69 ++++++++++++++- .../server/admin/AdminApiController.java | 49 ++++++++++- .../server/player/PlayerRepository.java | 3 + .../utils/OrderToDirectionMapperService.java | 18 ++++ .../server/utils/PlayerMapperService.java | 28 ++++++ .../towerdefence/server/IntegrationTest.java | 26 +++++- .../server/server/GetAllPlayersTest.java | 85 +++++++++++++++++++ .../server/server/PlayerRegistrationTest.java | 2 - 8 files changed, 273 insertions(+), 7 deletions(-) create mode 100644 src/main/java/de/towerdefence/server/utils/OrderToDirectionMapperService.java create mode 100644 src/main/java/de/towerdefence/server/utils/PlayerMapperService.java create mode 100644 src/test/java/de/towerdefence/server/server/GetAllPlayersTest.java diff --git a/api/api.yml b/api/api.yml index 83ffca4..3cac665 100644 --- a/api/api.yml +++ b/api/api.yml @@ -7,7 +7,7 @@ servers: - url: /api/v1 - url: http://localhost:8080/api/v1 security: - - JWTAuth: [] + - JWTAuth: [ ] components: securitySchemes: @@ -83,9 +83,47 @@ components: type: object properties: username: - type: string + type: string required: - username + ############################################# + # AdministratablePlayer # + ############################################# + AdministratablePlayer: + description: a Player + type: object + properties: + username: + type: string + required: + - username + ############################################# + # PlayerFilter # + ############################################# + PlayerFilter: + description: Configuration data for query for the getting all players endpoint + type: object + properties: + page: + type: integer + description: "Page number (zero-based index)." + pageSize: + type: integer + description: "Number of players per page." + sortBy: + type: string + description: "Field to sort by (default is username)." + order: + type: string + enum: [ ascending, descending ] + description: "Sorting order (asc or desc)." + username: + type: string + description: "Filter players by username (case-insensitive, partial match)." + required: + - page + - pageSize + - order responses: 201PlayerCreated: description: "201 - Player Created" @@ -198,5 +236,32 @@ paths: $ref: "#/components/responses/401Unauthorized" 500: $ref: "#/components/responses/500InternalError" + 503: + $ref: "#/components/responses/503ServiceUnavailable" + /admin/players: + get: + operationId: "GetAllPlayers" + tags: + - admin + summary: "Retrieve a paginated list of players" + description: "Returns a paginat#ed list of players, optionally filtered by username." + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/PlayerFilter" + responses: + 200: + description: "A List of all Player" + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/AdministratablePlayer" + 401: + $ref: "#/components/responses/401Unauthorized" + 500: + $ref: "#/components/responses/500InternalError" 503: $ref: "#/components/responses/503ServiceUnavailable" \ No newline at end of file diff --git a/src/main/java/de/towerdefence/server/admin/AdminApiController.java b/src/main/java/de/towerdefence/server/admin/AdminApiController.java index a04f9b6..9356c2a 100644 --- a/src/main/java/de/towerdefence/server/admin/AdminApiController.java +++ b/src/main/java/de/towerdefence/server/admin/AdminApiController.java @@ -1,16 +1,26 @@ package de.towerdefence.server.admin; - import com.fasterxml.jackson.databind.ObjectMapper; import de.towerdefence.server.auth.UserSession; import de.towerdefence.server.oas.AdminApi; import de.towerdefence.server.oas.models.AdminAuthInfo; +import de.towerdefence.server.oas.models.AdministratablePlayer; +import de.towerdefence.server.oas.models.PlayerFilter; +import de.towerdefence.server.player.Player; +import de.towerdefence.server.player.PlayerRepository; +import de.towerdefence.server.utils.OrderToDirectionMapperService; +import de.towerdefence.server.utils.PlayerMapperService; import jakarta.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; +import java.util.List; import java.util.Optional; @Controller @@ -20,6 +30,16 @@ public class AdminApiController implements AdminApi { @Autowired UserSession userSession; + @Autowired + PlayerRepository playerRepository; + + @Autowired + PlayerMapperService playerMapperService; + + @Autowired + OrderToDirectionMapperService orderToDirectionMapperService; + + @Override public Optional getObjectMapper() { return Optional.empty(); @@ -36,4 +56,31 @@ public class AdminApiController implements AdminApi { authInfo.setUsername(this.userSession.getUsername()); return ResponseEntity.ok(authInfo); } + + @Override + public ResponseEntity> getAllPlayers(PlayerFilter body) { + + PlayerFilter.OrderEnum order = body.getOrder(); + Integer page = body.getPage(); + Integer pageSize = body.getPageSize(); + String sortBy = body.getSortBy(); + String username = body.getUsername(); + + Sort.Direction direction = orderToDirectionMapperService.orderToDirection(order); + + Pageable pageable = PageRequest.of(page, pageSize, Sort.by(direction, sortBy)); + + Page playerPage; + + if (username != null && !username.isEmpty()) { + playerPage = playerRepository.findByUsernameContainingIgnoreCase(username, pageable); + } else { + playerPage = playerRepository.findAll(pageable); + } + + List playersMapped = + playerMapperService.mapPlayersToAdministratablePlayers(playerPage.getContent()); + + return ResponseEntity.ok(playersMapped); + } } diff --git a/src/main/java/de/towerdefence/server/player/PlayerRepository.java b/src/main/java/de/towerdefence/server/player/PlayerRepository.java index 5ca4c19..612608c 100644 --- a/src/main/java/de/towerdefence/server/player/PlayerRepository.java +++ b/src/main/java/de/towerdefence/server/player/PlayerRepository.java @@ -1,8 +1,11 @@ package de.towerdefence.server.player; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; public interface PlayerRepository extends JpaRepository { Player findByUsername(String username); boolean existsByUsername(String username); + Page findByUsernameContainingIgnoreCase(String username, Pageable pageable); } diff --git a/src/main/java/de/towerdefence/server/utils/OrderToDirectionMapperService.java b/src/main/java/de/towerdefence/server/utils/OrderToDirectionMapperService.java new file mode 100644 index 0000000..acf0e05 --- /dev/null +++ b/src/main/java/de/towerdefence/server/utils/OrderToDirectionMapperService.java @@ -0,0 +1,18 @@ +package de.towerdefence.server.utils; + +import de.towerdefence.server.oas.models.PlayerFilter; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Component; + +@Component +public class OrderToDirectionMapperService { + + public Sort.Direction orderToDirection(PlayerFilter.OrderEnum order) { + + if (order == PlayerFilter.OrderEnum.ASCENDING) { + return Sort.Direction.ASC; + } else { + return Sort.Direction.DESC; + } + } +} diff --git a/src/main/java/de/towerdefence/server/utils/PlayerMapperService.java b/src/main/java/de/towerdefence/server/utils/PlayerMapperService.java new file mode 100644 index 0000000..0de83da --- /dev/null +++ b/src/main/java/de/towerdefence/server/utils/PlayerMapperService.java @@ -0,0 +1,28 @@ +package de.towerdefence.server.utils; + +import de.towerdefence.server.oas.models.AdministratablePlayer; +import de.towerdefence.server.player.Player; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +@Component +public class PlayerMapperService { + + public List mapPlayersToAdministratablePlayers(List players) { + List administratablePlayers = new ArrayList<>(); + for (Player player : players) { + AdministratablePlayer apiPlayer = new AdministratablePlayer(); + apiPlayer.setUsername(player.getUsername()); + administratablePlayers.add(apiPlayer); + } + return administratablePlayers; + } + + public AdministratablePlayer mapPlayerToAdministratablePlayer(Player player) { + AdministratablePlayer administratablePlayer = new AdministratablePlayer(); + administratablePlayer.setUsername(player.getUsername()); + return administratablePlayer; + } +} diff --git a/src/test/java/de/towerdefence/server/IntegrationTest.java b/src/test/java/de/towerdefence/server/IntegrationTest.java index dbf41fa..51b0e56 100644 --- a/src/test/java/de/towerdefence/server/IntegrationTest.java +++ b/src/test/java/de/towerdefence/server/IntegrationTest.java @@ -1,6 +1,7 @@ package de.towerdefence.server; import com.fasterxml.jackson.databind.ObjectMapper; +import de.towerdefence.server.player.Player; import de.towerdefence.server.player.PlayerRepository; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -10,6 +11,10 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; +import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.Map; + @SpringBootTest @AutoConfigureMockMvc(addFilters = false) @ActiveProfiles("test") @@ -23,10 +28,27 @@ public abstract class IntegrationTest { protected ObjectMapper objectMapper; @Autowired protected PlayerRepository playerRepository; - + @Autowired + protected PlayerService playerService; + @BeforeEach void setUp() { playerRepository.deleteAll(); + + Map players = new HashMap<>(); + players.put("Alex", "1234"); + players.put("Zorro", "1234"); + + players.forEach((username, password) -> { + Player player = new Player(); + player.setUsername(username); + try { + playerService.setPassword(player, password); + playerRepository.saveAndFlush(player); + } catch (NoSuchAlgorithmException e) { + System.err.println("Error setting password for player: " + username); + } + }); } @AfterEach @@ -34,5 +56,5 @@ public abstract class IntegrationTest { playerRepository.deleteAll(); } - } +} diff --git a/src/test/java/de/towerdefence/server/server/GetAllPlayersTest.java b/src/test/java/de/towerdefence/server/server/GetAllPlayersTest.java new file mode 100644 index 0000000..556c338 --- /dev/null +++ b/src/test/java/de/towerdefence/server/server/GetAllPlayersTest.java @@ -0,0 +1,85 @@ +package de.towerdefence.server.server; + +import de.towerdefence.server.IntegrationTest; +import de.towerdefence.server.oas.models.PlayerFilter; +import org.junit.jupiter.api.Test; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; + +class GetAllPlayersTest extends IntegrationTest { + + private MockHttpServletRequestBuilder createGetAllPlayersRequest(String requestBody) { + return MockMvcRequestBuilders.get(baseUri + "/admin/players") + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody); + } + + @Test + void playersExist() throws Exception { + PlayerFilter playerFilter = new PlayerFilter(); + playerFilter.setPage(0); + playerFilter.setPageSize(10); + playerFilter.setOrder(PlayerFilter.OrderEnum.DESCENDING); + playerFilter.setUsername(""); + playerFilter.setSortBy("username"); + String requestBody = this.objectMapper.writeValueAsString(playerFilter); + + this.mvc.perform(createGetAllPlayersRequest(requestBody)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$[0]").exists()); + } + + @Test + void playersSortedByAsc() throws Exception { + + PlayerFilter playerFilter = new PlayerFilter(); + playerFilter.setPage(0); + playerFilter.setPageSize(10); + playerFilter.setOrder(PlayerFilter.OrderEnum.ASCENDING); + playerFilter.setUsername(""); + playerFilter.setSortBy("username"); + String requestBody = this.objectMapper.writeValueAsString(playerFilter); + + this.mvc.perform(createGetAllPlayersRequest(requestBody)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].username").value("Alex")) + .andExpect(jsonPath("$[1].username").value("Zorro")); + } + + @Test + void playersSortedByDesc() throws Exception { + PlayerFilter playerFilter = new PlayerFilter(); + playerFilter.setPage(0); + playerFilter.setPageSize(10); + playerFilter.setOrder(PlayerFilter.OrderEnum.DESCENDING); + playerFilter.setUsername(""); + playerFilter.setSortBy("username"); + String requestBody = this.objectMapper.writeValueAsString(playerFilter); + + this.mvc.perform(createGetAllPlayersRequest(requestBody)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[1].username").value("Alex")) + .andExpect(jsonPath("$[0].username").value("Zorro")); + } + + @Test + void playersFiltered() throws Exception { + PlayerFilter playerFilter = new PlayerFilter(); + playerFilter.setPage(0); + playerFilter.setPageSize(10); + playerFilter.setOrder(PlayerFilter.OrderEnum.ASCENDING); + playerFilter.setUsername("Alex"); + playerFilter.setSortBy("username"); + String requestBody = this.objectMapper.writeValueAsString(playerFilter); + + this.mvc.perform(createGetAllPlayersRequest(requestBody)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].username").value("Alex")) + .andExpect(jsonPath("$").isNotEmpty()); + } +} diff --git a/src/test/java/de/towerdefence/server/server/PlayerRegistrationTest.java b/src/test/java/de/towerdefence/server/server/PlayerRegistrationTest.java index 4a76286..df8a3ce 100644 --- a/src/test/java/de/towerdefence/server/server/PlayerRegistrationTest.java +++ b/src/test/java/de/towerdefence/server/server/PlayerRegistrationTest.java @@ -30,6 +30,4 @@ public class PlayerRegistrationTest extends IntegrationTest { .content(this.objectMapper.writeValueAsString(playerRegistrationData)) .contentType(MediaType.APPLICATION_JSON); } - - }