diff --git a/modules/funixbot/client/src/main/java/fr/funixgaming/api/funixbot/client/dtos/FunixBotCommandDTO.java b/modules/funixbot/client/src/main/java/fr/funixgaming/api/funixbot/client/dtos/FunixBotCommandDTO.java index 663ec88..e174732 100644 --- a/modules/funixbot/client/src/main/java/fr/funixgaming/api/funixbot/client/dtos/FunixBotCommandDTO.java +++ b/modules/funixbot/client/src/main/java/fr/funixgaming/api/funixbot/client/dtos/FunixBotCommandDTO.java @@ -1,7 +1,9 @@ package fr.funixgaming.api.funixbot.client.dtos; import com.funixproductions.core.crud.dtos.ApiDTO; +import fr.funixgaming.api.funixbot.client.enums.FunixBotCommandType; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.Getter; import lombok.Setter; @@ -11,6 +13,9 @@ public class FunixBotCommandDTO extends ApiDTO { @NotBlank private String command; + @NotNull + private FunixBotCommandType type; + @NotBlank private String message; } diff --git a/modules/funixbot/client/src/main/java/fr/funixgaming/api/funixbot/client/enums/FunixBotCommandType.java b/modules/funixbot/client/src/main/java/fr/funixgaming/api/funixbot/client/enums/FunixBotCommandType.java new file mode 100644 index 0000000..81f84c2 --- /dev/null +++ b/modules/funixbot/client/src/main/java/fr/funixgaming/api/funixbot/client/enums/FunixBotCommandType.java @@ -0,0 +1,9 @@ +package fr.funixgaming.api.funixbot.client.enums; + +public enum FunixBotCommandType { + INFO, + VIEWER, + MODERATION, + FUN, + OTHER +} diff --git a/modules/funixbot/service/src/main/java/fr/funixgaming/api/funixbot/service/entities/FunixBotCommand.java b/modules/funixbot/service/src/main/java/fr/funixgaming/api/funixbot/service/entities/FunixBotCommand.java index fc80c40..6fba435 100644 --- a/modules/funixbot/service/src/main/java/fr/funixgaming/api/funixbot/service/entities/FunixBotCommand.java +++ b/modules/funixbot/service/src/main/java/fr/funixgaming/api/funixbot/service/entities/FunixBotCommand.java @@ -1,8 +1,11 @@ package fr.funixgaming.api.funixbot.service.entities; import com.funixproductions.core.crud.entities.ApiEntity; +import fr.funixgaming.api.funixbot.client.enums.FunixBotCommandType; import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; import lombok.Getter; import lombok.Setter; @@ -13,6 +16,10 @@ public class FunixBotCommand extends ApiEntity { @Column(nullable = false, unique = true, length = 30) private String command; - @Column(nullable = false) + @Enumerated(EnumType.STRING) + @Column(nullable = false, columnDefinition = "varchar(200) DEFAULT 'OTHER'") + private FunixBotCommandType type; + + @Column(nullable = false, length = 500) private String message; } diff --git a/modules/funixbot/service/src/main/java/fr/funixgaming/api/funixbot/service/repositories/FunixBotCommandRepository.java b/modules/funixbot/service/src/main/java/fr/funixgaming/api/funixbot/service/repositories/FunixBotCommandRepository.java index 2682696..4ef7a4c 100644 --- a/modules/funixbot/service/src/main/java/fr/funixgaming/api/funixbot/service/repositories/FunixBotCommandRepository.java +++ b/modules/funixbot/service/src/main/java/fr/funixgaming/api/funixbot/service/repositories/FunixBotCommandRepository.java @@ -4,9 +4,7 @@ import fr.funixgaming.api.funixbot.service.entities.FunixBotCommand; import org.springframework.stereotype.Repository; -import java.util.Optional; - @Repository public interface FunixBotCommandRepository extends ApiRepository { - Optional findByCommand(String command); + boolean existsFunixBotCommandByCommandContainsIgnoreCase(String command); } diff --git a/modules/funixbot/service/src/main/java/fr/funixgaming/api/funixbot/service/services/FunixBotCommandsService.java b/modules/funixbot/service/src/main/java/fr/funixgaming/api/funixbot/service/services/FunixBotCommandsService.java index 4a12149..d086a03 100644 --- a/modules/funixbot/service/src/main/java/fr/funixgaming/api/funixbot/service/services/FunixBotCommandsService.java +++ b/modules/funixbot/service/src/main/java/fr/funixgaming/api/funixbot/service/services/FunixBotCommandsService.java @@ -1,18 +1,61 @@ package fr.funixgaming.api.funixbot.service.services; import com.funixproductions.core.crud.services.ApiService; +import com.funixproductions.core.exceptions.ApiBadRequestException; import fr.funixgaming.api.funixbot.client.dtos.FunixBotCommandDTO; import fr.funixgaming.api.funixbot.service.entities.FunixBotCommand; import fr.funixgaming.api.funixbot.service.mappers.FunixBotCommandMapper; import fr.funixgaming.api.funixbot.service.repositories.FunixBotCommandRepository; +import lombok.NonNull; import org.springframework.stereotype.Service; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + @Service public class FunixBotCommandsService extends ApiService { + private static final int MAX_LEN_MESSAGE = 500; + private static final int MAX_LEN_COMMAND = 30; + private static final String ERROR_MESSAGE_COMMAND_MAX_LEN = String.format("La commande ne peut pas dépasser %d caractères.", MAX_LEN_COMMAND); + private static final String ERROR_MESSAGE_MAX_LEN = String.format("Le message de la commande ne peut pas dépasser %d caractères.", MAX_LEN_MESSAGE); + private static final String ERROR_MESSAGE_NOT_ALPHANUMERIC = "La commande ne peut contenir que des caractères alphanumériques."; + private final Pattern pattern = Pattern.compile("^[a-zA-Z0-9]+$"); + public FunixBotCommandsService(FunixBotCommandRepository repository, FunixBotCommandMapper mapper) { super(repository, mapper); } + @Override + public void beforeMappingToEntity(@NonNull Iterable request) { + for (FunixBotCommandDTO command : request) { + if (command.getMessage().length() > MAX_LEN_MESSAGE) { + throw new ApiBadRequestException(ERROR_MESSAGE_MAX_LEN); + } + + if (command.getCommand().startsWith("!")) { + command.setCommand(command.getCommand().substring(1)); + } + if (command.getCommand().length() > MAX_LEN_COMMAND) { + throw new ApiBadRequestException(ERROR_MESSAGE_COMMAND_MAX_LEN); + } + + if (!isAlphanumeric(command.getCommand())) { + throw new ApiBadRequestException(ERROR_MESSAGE_NOT_ALPHANUMERIC); + } + + if (super.getRepository().existsFunixBotCommandByCommandContainsIgnoreCase(command.getCommand())) { + throw new ApiBadRequestException(String.format("La commande '%s' existe déjà.", command.getCommand())); + } + + command.setCommand(command.getCommand().toLowerCase()); + } + } + + private boolean isAlphanumeric(String input) { + Matcher matcher = pattern.matcher(input); + return matcher.matches(); + } + } diff --git a/modules/funixbot/service/src/main/resources/db/migration/V3__funixbot_command_type.sql b/modules/funixbot/service/src/main/resources/db/migration/V3__funixbot_command_type.sql new file mode 100644 index 0000000..8949121 --- /dev/null +++ b/modules/funixbot/service/src/main/resources/db/migration/V3__funixbot_command_type.sql @@ -0,0 +1,3 @@ +ALTER TABLE funixbot_commands ADD COLUMN type VARCHAR(200) DEFAULT 'OTHER' NOT NULL; + +ALTER TABLE funixbot_commands ALTER COLUMN message TYPE VARCHAR(500); diff --git a/modules/funixbot/service/src/test/java/fr/funixgaming/api/funixbot/service/resources/FunixBotCommandResourceTest.java b/modules/funixbot/service/src/test/java/fr/funixgaming/api/funixbot/service/resources/FunixBotCommandResourceTest.java index e39a938..00f8a27 100644 --- a/modules/funixbot/service/src/test/java/fr/funixgaming/api/funixbot/service/resources/FunixBotCommandResourceTest.java +++ b/modules/funixbot/service/src/test/java/fr/funixgaming/api/funixbot/service/resources/FunixBotCommandResourceTest.java @@ -1,12 +1,26 @@ package fr.funixgaming.api.funixbot.service.resources; +import com.funixproductions.api.user.client.clients.UserAuthClient; +import com.funixproductions.api.user.client.dtos.UserDTO; +import com.funixproductions.api.user.client.enums.UserRole; +import com.funixproductions.core.test.beans.JsonHelper; +import fr.funixgaming.api.funixbot.client.dtos.FunixBotCommandDTO; +import fr.funixgaming.api.funixbot.client.enums.FunixBotCommandType; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @SpringBootTest @@ -16,10 +30,152 @@ class FunixBotCommandResourceTest { @Autowired private MockMvc mockMvc; + @Autowired + private JsonHelper jsonHelper; + + @MockBean + private UserAuthClient userAuthClient; + @Test void testGetCommands() throws Exception { mockMvc.perform(get("/funixbot/command")) .andExpect(status().isOk()); } + @Test + void testCreateCommandNoAccess() throws Exception { + final UserDTO userDTO = new UserDTO(); + userDTO.setRole(UserRole.USER); + userDTO.setUsername(UUID.randomUUID().toString()); + userDTO.setEmail(UUID.randomUUID().toString()); + userDTO.setId(UUID.randomUUID()); + userDTO.setValid(true); + when(userAuthClient.current(any())).thenReturn(userDTO); + + mockMvc.perform(post("/funixbot/command")) + .andExpect(status().isUnauthorized()); + + mockMvc.perform(post("/funixbot/command") + .header("Authorization", "Bearer " + UUID.randomUUID())) + .andExpect(status().isForbidden()); + + mockMvc.perform(patch("/funixbot/command") + .header("Authorization", "Bearer " + UUID.randomUUID())) + .andExpect(status().isForbidden()); + } + + @Test + void testCreateAndEditCommand() throws Exception { + final UserDTO userDTO = new UserDTO(); + userDTO.setRole(UserRole.MODERATOR); + userDTO.setUsername(UUID.randomUUID().toString()); + userDTO.setEmail(UUID.randomUUID().toString()); + userDTO.setId(UUID.randomUUID()); + userDTO.setValid(true); + when(userAuthClient.current(any())).thenReturn(userDTO); + + final String commandName = "tEst1"; + final FunixBotCommandDTO commandDTO = new FunixBotCommandDTO(); + commandDTO.setCommand("!" + commandName); + commandDTO.setMessage("testMessage"); + commandDTO.setType(FunixBotCommandType.FUN); + + MvcResult mvcResult = mockMvc.perform(post("/funixbot/command") + .header("Authorization", "Bearer " + UUID.randomUUID()) + .contentType(MediaType.APPLICATION_JSON) + .content(jsonHelper.toJson(commandDTO))) + .andExpect(status().isOk()).andReturn(); + final FunixBotCommandDTO createdCommand = jsonHelper.fromJson(mvcResult.getResponse().getContentAsString(), FunixBotCommandDTO.class); + assertEquals(commandName.toLowerCase(), createdCommand.getCommand()); + createdCommand.setCommand("test2patched"); + + mvcResult = mockMvc.perform(patch("/funixbot/command") + .header("Authorization", "Bearer " + UUID.randomUUID()) + .contentType(MediaType.APPLICATION_JSON) + .content(jsonHelper.toJson(createdCommand))) + .andExpect(status().isOk()).andReturn(); + final FunixBotCommandDTO editedCommand = jsonHelper.fromJson(mvcResult.getResponse().getContentAsString(), FunixBotCommandDTO.class); + assertEquals(createdCommand.getCommand(), editedCommand.getCommand()); + } + + @Test + void testCreateCommandWithCommandTooLong() throws Exception { + final FunixBotCommandDTO commandDTO = new FunixBotCommandDTO(); + commandDTO.setCommand("testdfgldfskghdflkghdflskgjhdfslkgjhdflkgjhdskflhfghdgfhdgfhdfghdfghfdghfdgh"); + commandDTO.setMessage("testMessage"); + commandDTO.setType(FunixBotCommandType.FUN); + + handleBadRequest(commandDTO); + } + + @Test + void testCreateCommandWithMessageTooLong() throws Exception { + final FunixBotCommandDTO commandDTO = new FunixBotCommandDTO(); + commandDTO.setCommand("test"); + commandDTO.setMessage("testdfgldfskghdflkghdflskgjhdfslkgjhdflkgjhdskflhfghdgfhdgfhdfghdfghfdghfdghtestdfgldfskghdflkghdflskgjhdfslkgjhdflkgjhdskflhfghdgfhdgfhdfghdfghfdghfdghtestdfgldfskghdflkghdflskgjhdfslkgjhdflkgjhdskflhfghdgfhdgfhdfghdfghfdghfdghtestdfgldfskghdflkghdflskgjhdfslkgjhdflkgjhdskflhfghdgfhdgfhdfghdfghfdghfdghtestdfgldfskghdflkghdflskgjhdfslkgjhdflkgjhdskflhfghdgfhdgfhdfghdfghfdghfdghtestdfgldfskghdflkghdflskgjhdfslkgjhdflkgjhdskflhfghdgfhdgfhdfghdfghfdghfdghtestdfgldfskghdflkghdflskgjhdfslkgjhdflkgjhdskflhfghdgfhdgfhdfghdfghfdghfdghtestdfgldfskghdflkghdflskgjhdfslkgjhdflkgjhdskflhfghdgfhdgfhdfghdfghfdghfdgh"); + commandDTO.setType(FunixBotCommandType.FUN); + + handleBadRequest(commandDTO); + } + + @Test + void testCreateCommandNotAlphanumeric() throws Exception { + final FunixBotCommandDTO commandDTO = new FunixBotCommandDTO(); + commandDTO.setCommand("test!"); + commandDTO.setMessage("testMessage"); + commandDTO.setType(FunixBotCommandType.FUN); + + handleBadRequest(commandDTO); + + commandDTO.setCommand("test@"); + handleBadRequest(commandDTO); + commandDTO.setCommand("test#"); + handleBadRequest(commandDTO); + } + + @Test + void testCreateDuplicateCommands() throws Exception { + final FunixBotCommandDTO commandDTO = new FunixBotCommandDTO(); + commandDTO.setCommand("testDupplicateCmd"); + commandDTO.setMessage("testMessage"); + commandDTO.setType(FunixBotCommandType.FUN); + + final UserDTO userDTO = new UserDTO(); + userDTO.setRole(UserRole.MODERATOR); + userDTO.setUsername(UUID.randomUUID().toString()); + userDTO.setEmail(UUID.randomUUID().toString()); + userDTO.setId(UUID.randomUUID()); + userDTO.setValid(true); + when(userAuthClient.current(any())).thenReturn(userDTO); + + mockMvc.perform(post("/funixbot/command") + .header("Authorization", "Bearer " + UUID.randomUUID()) + .contentType(MediaType.APPLICATION_JSON) + .content(jsonHelper.toJson(commandDTO))) + .andExpect(status().isOk()); + + commandDTO.setId(null); + mockMvc.perform(post("/funixbot/command") + .header("Authorization", "Bearer " + UUID.randomUUID()) + .contentType(MediaType.APPLICATION_JSON) + .content(jsonHelper.toJson(commandDTO))) + .andExpect(status().isBadRequest()); + } + + private void handleBadRequest(final FunixBotCommandDTO commandDTO) throws Exception { + final UserDTO userDTO = new UserDTO(); + userDTO.setRole(UserRole.MODERATOR); + userDTO.setUsername(UUID.randomUUID().toString()); + userDTO.setEmail(UUID.randomUUID().toString()); + userDTO.setId(UUID.randomUUID()); + userDTO.setValid(true); + when(userAuthClient.current(any())).thenReturn(userDTO); + + mockMvc.perform(post("/funixbot/command") + .header("Authorization", "Bearer " + UUID.randomUUID()) + .contentType(MediaType.APPLICATION_JSON) + .content(jsonHelper.toJson(commandDTO))) + .andExpect(status().isBadRequest()); + } + } diff --git a/modules/funixbot/service/src/test/java/fr/funixgaming/api/funixbot/service/resources/FunixBotUserExperienceResourceTest.java b/modules/funixbot/service/src/test/java/fr/funixgaming/api/funixbot/service/resources/FunixBotUserExperienceResourceTest.java index ae22171..8453c2a 100644 --- a/modules/funixbot/service/src/test/java/fr/funixgaming/api/funixbot/service/resources/FunixBotUserExperienceResourceTest.java +++ b/modules/funixbot/service/src/test/java/fr/funixgaming/api/funixbot/service/resources/FunixBotUserExperienceResourceTest.java @@ -1,12 +1,25 @@ package fr.funixgaming.api.funixbot.service.resources; +import com.funixproductions.api.user.client.clients.UserAuthClient; +import com.funixproductions.api.user.client.dtos.UserDTO; +import com.funixproductions.api.user.client.enums.UserRole; +import com.funixproductions.core.test.beans.JsonHelper; +import fr.funixgaming.api.funixbot.client.dtos.FunixBotUserExperienceDTO; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @SpringBootTest @@ -16,10 +29,87 @@ class FunixBotUserExperienceResourceTest { @Autowired private MockMvc mockMvc; + @Autowired + private JsonHelper jsonHelper; + + @MockBean + private UserAuthClient userAuthClient; + @Test void testGetExp() throws Exception { mockMvc.perform(get("/funixbot/user/exp")) .andExpect(status().isOk()); } + @Test + void testCreateNoPermissions() throws Exception { + final UserDTO userDTO = new UserDTO(); + userDTO.setRole(UserRole.USER); + userDTO.setUsername(UUID.randomUUID().toString()); + userDTO.setEmail(UUID.randomUUID().toString()); + userDTO.setId(UUID.randomUUID()); + userDTO.setValid(true); + + when(userAuthClient.current(any())).thenReturn(userDTO); + + mockMvc.perform(post("/funixbot/user/exp") + .contentType(MediaType.APPLICATION_JSON) + .content(jsonHelper.toJson(new FunixBotUserExperienceDTO()))) + .andExpect(status().isUnauthorized()); + + mockMvc.perform(post("/funixbot/user/exp") + .header("Authorization", "Bearer " + UUID.randomUUID()) + .contentType(MediaType.APPLICATION_JSON) + .content(jsonHelper.toJson(new FunixBotUserExperienceDTO()))) + .andExpect(status().isForbidden()); + + mockMvc.perform(patch("/funixbot/user/exp") + .contentType(MediaType.APPLICATION_JSON) + .content(jsonHelper.toJson(new FunixBotUserExperienceDTO()))) + .andExpect(status().isUnauthorized()); + + mockMvc.perform(patch("/funixbot/user/exp") + .header("Authorization", "Bearer " + UUID.randomUUID()) + .contentType(MediaType.APPLICATION_JSON) + .content(jsonHelper.toJson(new FunixBotUserExperienceDTO()))) + .andExpect(status().isForbidden()); + } + + @Test + void testCreateAndUpdateExp() throws Exception { + final UserDTO userDTO = new UserDTO(); + userDTO.setRole(UserRole.MODERATOR); + userDTO.setUsername(UUID.randomUUID().toString()); + userDTO.setEmail(UUID.randomUUID().toString()); + userDTO.setId(UUID.randomUUID()); + userDTO.setValid(true); + + when(userAuthClient.current(any())).thenReturn(userDTO); + + final FunixBotUserExperienceDTO experienceDTO = new FunixBotUserExperienceDTO(); + experienceDTO.setXp(10); + experienceDTO.setLevel(1); + experienceDTO.setLastMessageDateSeconds(1L); + experienceDTO.setTwitchUserId("qsd"); + experienceDTO.setXpNextLevel(100); + + MvcResult mvcResult = this.mockMvc.perform(post("/funixbot/user/exp") + .header("Authorization", "Bearer " + UUID.randomUUID()) + .contentType(MediaType.APPLICATION_JSON) + .content(jsonHelper.toJson(experienceDTO))) + .andExpect(status().isOk()) + .andReturn(); + final FunixBotUserExperienceDTO createdDTO = jsonHelper.fromJson(mvcResult.getResponse().getContentAsString(), FunixBotUserExperienceDTO.class); + assertEquals(experienceDTO.getXp(), createdDTO.getXp()); + createdDTO.setLevel(10); + + mvcResult = this.mockMvc.perform(patch("/funixbot/user/exp") + .header("Authorization", "Bearer " + UUID.randomUUID()) + .contentType(MediaType.APPLICATION_JSON) + .content(jsonHelper.toJson(createdDTO))) + .andExpect(status().isOk()).andReturn(); + final FunixBotUserExperienceDTO updatedDTO = jsonHelper.fromJson(mvcResult.getResponse().getContentAsString(), FunixBotUserExperienceDTO.class); + assertEquals(createdDTO.getLevel(), updatedDTO.getLevel()); + } + }