From e061db860adbf268969054dd83d4f78134d474ed Mon Sep 17 00:00:00 2001 From: "Dario G. Mori" Date: Fri, 12 Apr 2024 11:58:11 +0200 Subject: [PATCH 01/18] feat: gamemode added --- .../main/java/lab/en2b/quizapi/game/Game.java | 59 +++++++++++++++++-- .../lab/en2b/quizapi/game/GameController.java | 4 +- .../java/lab/en2b/quizapi/game/GameMode.java | 11 ++++ .../lab/en2b/quizapi/game/GameService.java | 13 +--- 4 files changed, 70 insertions(+), 17 deletions(-) create mode 100644 api/src/main/java/lab/en2b/quizapi/game/GameMode.java diff --git a/api/src/main/java/lab/en2b/quizapi/game/Game.java b/api/src/main/java/lab/en2b/quizapi/game/Game.java index eec37592..f311865e 100644 --- a/api/src/main/java/lab/en2b/quizapi/game/Game.java +++ b/api/src/main/java/lab/en2b/quizapi/game/Game.java @@ -8,10 +8,11 @@ import lombok.*; import java.time.Instant; -import java.time.LocalDateTime; -import java.time.OffsetDateTime; +import java.util.ArrayList; import java.util.List; +import static lab.en2b.quizapi.game.GameMode.*; + @Entity @Table(name = "games") @NoArgsConstructor @@ -20,7 +21,6 @@ @Setter @Builder public class Game { - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Setter(AccessLevel.NONE) @@ -35,7 +35,8 @@ public class Game { @NonNull private Integer roundDuration; private boolean currentQuestionAnswered; - + @Enumerated(EnumType.STRING) + private GameMode gamemode; @ManyToOne @NotNull @JoinColumn(name = "user_id") @@ -53,6 +54,14 @@ public class Game { private List questions; private boolean isGameOver; + public Game(User user, GameMode gamemode,String lang) { + this.user = user; + setGamemode(gamemode); + this.questions = new ArrayList<>(); + this.actualRound = 0L; + this.language = lang; + } + public void newRound(Question question){ if(getActualRound() != 0){ if (isGameOver()) @@ -110,11 +119,53 @@ public boolean answerQuestion(Long answerId){ return q.isCorrectAnswer(answerId); } public void setLanguage(String language){ + if(language == null){ + language = "en"; + } if(!isLanguageSupported(language)) throw new IllegalArgumentException("The language you provided is not supported"); this.language = language; } + public void setGamemode(GameMode gamemode){ + if(gamemode == null){ + gamemode = KIWI_QUEST; + } + setGamemodeParams(gamemode); + } + private void setGamemodeParams(GameMode gamemode){ //This could be moved to a GameMode entity if we have time + switch(gamemode){ + case KIWI_QUEST: + setRounds(9L); + setRoundDuration(30); + break; + case FOOTBALL_SHOWDOWN: + setRounds(9L); + setRoundDuration(30); + break; + case GEO_GENIUS: + setRounds(9L); + setRoundDuration(30); + break; + case VIDEOGAME_ADVENTURE: + setRounds(9L); + setRoundDuration(30); + break; + case ANCIENT_ODYSSEY: + setRounds(9L); + setRoundDuration(30); + break; + case RANDOM: + setRounds(9L); + setRoundDuration(30); + break; + case CUSTOM: + setRounds(9L); + setRoundDuration(30); + break; + } + + } private boolean isLanguageSupported(String language) { return language.equals("en") || language.equals("es"); } diff --git a/api/src/main/java/lab/en2b/quizapi/game/GameController.java b/api/src/main/java/lab/en2b/quizapi/game/GameController.java index c39409ae..4bfa3757 100644 --- a/api/src/main/java/lab/en2b/quizapi/game/GameController.java +++ b/api/src/main/java/lab/en2b/quizapi/game/GameController.java @@ -27,8 +27,8 @@ public class GameController { @ApiResponse(responseCode = "403", description = "You are not logged in", content = @io.swagger.v3.oas.annotations.media.Content), }) @PostMapping("/new") - public ResponseEntity newGame(Authentication authentication){ - return ResponseEntity.ok(gameService.newGame(authentication)); + public ResponseEntity newGame(@RequestParam(required = false) String lang,@RequestParam(required=false) GameMode gamemode, Authentication authentication){ + return ResponseEntity.ok(gameService.newGame(lang,gamemode,authentication)); } @Operation(summary = "Starts a new round", description = "Starts the round (asks a question and its possible answers to the API and start the timer) for a given authentication (a player)") diff --git a/api/src/main/java/lab/en2b/quizapi/game/GameMode.java b/api/src/main/java/lab/en2b/quizapi/game/GameMode.java new file mode 100644 index 00000000..0c5d1e8b --- /dev/null +++ b/api/src/main/java/lab/en2b/quizapi/game/GameMode.java @@ -0,0 +1,11 @@ +package lab.en2b.quizapi.game; + +public enum GameMode { + KIWI_QUEST, + FOOTBALL_SHOWDOWN, + GEO_GENIUS, + VIDEOGAME_ADVENTURE, + ANCIENT_ODYSSEY, + RANDOM, + CUSTOM +} diff --git a/api/src/main/java/lab/en2b/quizapi/game/GameService.java b/api/src/main/java/lab/en2b/quizapi/game/GameService.java index ab75b519..fb081b32 100644 --- a/api/src/main/java/lab/en2b/quizapi/game/GameService.java +++ b/api/src/main/java/lab/en2b/quizapi/game/GameService.java @@ -34,9 +34,8 @@ public class GameService { private final StatisticsRepository statisticsRepository; @Transactional - public GameResponseDto newGame(Authentication authentication) { + public GameResponseDto newGame(String lang, GameMode gamemode, Authentication authentication) { Optional game = gameRepository.findActiveGameForUser(userService.getUserByAuthentication(authentication).getId()); - if (game.isPresent()){ if (game.get().shouldBeGameOver()){ game.get().setGameOver(true); @@ -46,15 +45,7 @@ public GameResponseDto newGame(Authentication authentication) { return gameResponseDtoMapper.apply(game.get()); } } - return gameResponseDtoMapper.apply(gameRepository.save(Game.builder() - .user(userService.getUserByAuthentication(authentication)) - .questions(new ArrayList<>()) - .rounds(9L) - .actualRound(0L) - .correctlyAnsweredQuestions(0L) - .roundDuration(30) - .language("en") - .build())); + return gameResponseDtoMapper.apply(gameRepository.save(new Game(userService.getUserByAuthentication(authentication),gamemode,lang))); } public GameResponseDto startRound(Long id, Authentication authentication) { From 70c9e520c8082ba4bfa3a57b1f53026e143f79bb Mon Sep 17 00:00:00 2001 From: Dario Date: Fri, 12 Apr 2024 17:36:20 +0200 Subject: [PATCH 02/18] feat:custom game --- .../main/java/lab/en2b/quizapi/game/Game.java | 43 ++++++++++++++++--- .../lab/en2b/quizapi/game/GameController.java | 7 +-- .../lab/en2b/quizapi/game/GameService.java | 6 +-- .../en2b/quizapi/game/dtos/CustomGameDto.java | 33 ++++++++++++++ .../en2b/quizapi/game/GameServiceTest.java | 30 ++++++------- 5 files changed, 92 insertions(+), 27 deletions(-) create mode 100644 api/src/main/java/lab/en2b/quizapi/game/dtos/CustomGameDto.java diff --git a/api/src/main/java/lab/en2b/quizapi/game/Game.java b/api/src/main/java/lab/en2b/quizapi/game/Game.java index f311865e..46401956 100644 --- a/api/src/main/java/lab/en2b/quizapi/game/Game.java +++ b/api/src/main/java/lab/en2b/quizapi/game/Game.java @@ -3,8 +3,10 @@ import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; import lab.en2b.quizapi.commons.user.User; +import lab.en2b.quizapi.game.dtos.CustomGameDto; import lab.en2b.quizapi.questions.answer.Answer; import lab.en2b.quizapi.questions.question.Question; +import lab.en2b.quizapi.questions.question.QuestionCategory; import lombok.*; import java.time.Instant; @@ -53,13 +55,17 @@ public class Game { @OrderColumn private List questions; private boolean isGameOver; + private List questionCategoriesForCustom; - public Game(User user, GameMode gamemode,String lang) { + public Game(User user,GameMode gamemode,String lang, CustomGameDto gameDto){ this.user = user; - setGamemode(gamemode); this.questions = new ArrayList<>(); this.actualRound = 0L; this.language = lang; + if(gamemode == CUSTOM) + setCustomGameMode(gameDto); + else + setGamemode(gamemode); } public void newRound(Question question){ @@ -126,6 +132,12 @@ public void setLanguage(String language){ throw new IllegalArgumentException("The language you provided is not supported"); this.language = language; } + public void setCustomGameMode(CustomGameDto gameDto){ + setRounds(gameDto.getRounds()); + setRoundDuration(gameDto.getRoundDuration()); + setQuestionCategoriesForCustom(gameDto.getCategories()); + this.gamemode = CUSTOM; + } public void setGamemode(GameMode gamemode){ if(gamemode == null){ gamemode = KIWI_QUEST; @@ -159,12 +171,31 @@ private void setGamemodeParams(GameMode gamemode){ //This could be moved to a Ga setRounds(9L); setRoundDuration(30); break; - case CUSTOM: - setRounds(9L); - setRoundDuration(30); - break; + default: + throw new IllegalStateException("Invalid gamemode!"); } + this.gamemode = gamemode; + + } + + public void setQuestionCategoriesForCustom(List questionCategoriesForCustom) { + if(gamemode != CUSTOM) + throw new IllegalStateException("You can't set custom categories for a non-custom gamemode!"); + if(questionCategoriesForCustom == null || questionCategoriesForCustom.isEmpty()) + throw new IllegalArgumentException("You can't set an empty list of categories for a custom gamemode!"); + this.questionCategoriesForCustom = questionCategoriesForCustom; + } + public List getQuestionCategoriesForGamemode(){ + return switch (gamemode) { + case KIWI_QUEST -> List.of(QuestionCategory.GEOGRAPHY, QuestionCategory.MUSIC); + case FOOTBALL_SHOWDOWN -> List.of(QuestionCategory.SPORTS); + case GEO_GENIUS -> List.of(QuestionCategory.GEOGRAPHY); + case VIDEOGAME_ADVENTURE -> List.of(QuestionCategory.VIDEOGAMES); + case ANCIENT_ODYSSEY -> List.of(QuestionCategory.MUSIC,QuestionCategory.ART); + case RANDOM -> List.of(QuestionCategory.values()); + case CUSTOM -> questionCategoriesForCustom; + }; } private boolean isLanguageSupported(String language) { return language.equals("en") || language.equals("es"); diff --git a/api/src/main/java/lab/en2b/quizapi/game/GameController.java b/api/src/main/java/lab/en2b/quizapi/game/GameController.java index 4bfa3757..a0bbb197 100644 --- a/api/src/main/java/lab/en2b/quizapi/game/GameController.java +++ b/api/src/main/java/lab/en2b/quizapi/game/GameController.java @@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import lab.en2b.quizapi.game.dtos.AnswerGameResponseDto; +import lab.en2b.quizapi.game.dtos.CustomGameDto; import lab.en2b.quizapi.game.dtos.GameAnswerDto; import lab.en2b.quizapi.game.dtos.GameResponseDto; import lab.en2b.quizapi.questions.question.QuestionCategory; @@ -26,9 +27,9 @@ public class GameController { @ApiResponse(responseCode = "200", description = "Successfully retrieved"), @ApiResponse(responseCode = "403", description = "You are not logged in", content = @io.swagger.v3.oas.annotations.media.Content), }) - @PostMapping("/new") - public ResponseEntity newGame(@RequestParam(required = false) String lang,@RequestParam(required=false) GameMode gamemode, Authentication authentication){ - return ResponseEntity.ok(gameService.newGame(lang,gamemode,authentication)); + @PostMapping("/start") + public ResponseEntity newGame(@RequestParam(required = false) String lang, @RequestParam(required=false) GameMode gamemode, @RequestBody CustomGameDto customGameDto, Authentication authentication){ + return ResponseEntity.ok(gameService.newGame(lang,gamemode,customGameDto,authentication)); } @Operation(summary = "Starts a new round", description = "Starts the round (asks a question and its possible answers to the API and start the timer) for a given authentication (a player)") diff --git a/api/src/main/java/lab/en2b/quizapi/game/GameService.java b/api/src/main/java/lab/en2b/quizapi/game/GameService.java index fb081b32..68043d71 100644 --- a/api/src/main/java/lab/en2b/quizapi/game/GameService.java +++ b/api/src/main/java/lab/en2b/quizapi/game/GameService.java @@ -2,6 +2,7 @@ import lab.en2b.quizapi.commons.user.UserService; import lab.en2b.quizapi.game.dtos.AnswerGameResponseDto; +import lab.en2b.quizapi.game.dtos.CustomGameDto; import lab.en2b.quizapi.game.dtos.GameAnswerDto; import lab.en2b.quizapi.game.dtos.GameResponseDto; import lab.en2b.quizapi.game.mappers.GameResponseDtoMapper; @@ -29,12 +30,11 @@ public class GameService { private final GameResponseDtoMapper gameResponseDtoMapper; private final UserService userService; private final QuestionService questionService; - private final QuestionRepository questionRepository; private final QuestionResponseDtoMapper questionResponseDtoMapper; private final StatisticsRepository statisticsRepository; @Transactional - public GameResponseDto newGame(String lang, GameMode gamemode, Authentication authentication) { + public GameResponseDto newGame(String lang, GameMode gamemode, CustomGameDto newGameDto, Authentication authentication) { Optional game = gameRepository.findActiveGameForUser(userService.getUserByAuthentication(authentication).getId()); if (game.isPresent()){ if (game.get().shouldBeGameOver()){ @@ -45,7 +45,7 @@ public GameResponseDto newGame(String lang, GameMode gamemode, Authentication au return gameResponseDtoMapper.apply(game.get()); } } - return gameResponseDtoMapper.apply(gameRepository.save(new Game(userService.getUserByAuthentication(authentication),gamemode,lang))); + return gameResponseDtoMapper.apply(gameRepository.save(new Game(userService.getUserByAuthentication(authentication),gamemode,lang,newGameDto))); } public GameResponseDto startRound(Long id, Authentication authentication) { diff --git a/api/src/main/java/lab/en2b/quizapi/game/dtos/CustomGameDto.java b/api/src/main/java/lab/en2b/quizapi/game/dtos/CustomGameDto.java new file mode 100644 index 00000000..4dd9fa22 --- /dev/null +++ b/api/src/main/java/lab/en2b/quizapi/game/dtos/CustomGameDto.java @@ -0,0 +1,33 @@ +package lab.en2b.quizapi.game.dtos; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import lab.en2b.quizapi.questions.question.QuestionCategory; +import lombok.*; + +import java.util.List; + +@Getter +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Setter +public class CustomGameDto { + @Positive + @NotNull + @NonNull + @Schema(description = "Number of rounds for the custom game",example = "9") + private Long rounds; + @Positive + @NotNull + @NonNull + @JsonProperty("round_duration") + @Schema(description = "Duration of the round in seconds",example = "30") + private Integer roundDuration; + @NotNull + @NonNull + @Schema(description = "Categories selected for questions",example = "[\"HISTORY\",\"SCIENCE\"]") + private List categories; +} diff --git a/api/src/test/java/lab/en2b/quizapi/game/GameServiceTest.java b/api/src/test/java/lab/en2b/quizapi/game/GameServiceTest.java index 3b77f596..7b75e883 100644 --- a/api/src/test/java/lab/en2b/quizapi/game/GameServiceTest.java +++ b/api/src/test/java/lab/en2b/quizapi/game/GameServiceTest.java @@ -45,9 +45,6 @@ public class GameServiceTest { @Mock private GameRepository gameRepository; - @Mock - private QuestionRepository questionRepository; - @Mock private StatisticsRepository statisticsRepository; @@ -73,7 +70,7 @@ public class GameServiceTest { @BeforeEach void setUp() { this.questionResponseDtoMapper = new QuestionResponseDtoMapper(); - this.gameService = new GameService(gameRepository,new GameResponseDtoMapper(new UserResponseDtoMapper()), userService, questionService, questionRepository, questionResponseDtoMapper, statisticsRepository); + this.gameService = new GameService(gameRepository,new GameResponseDtoMapper(new UserResponseDtoMapper()), userService, questionService, questionResponseDtoMapper, statisticsRepository); this.defaultUser = User.builder() .id(1L) .email("test@email.com") @@ -135,6 +132,7 @@ void setUp() { .user(defaultUserResponseDto) .rounds(9L) .correctlyAnsweredQuestions(0L) + .roundStartTime(Instant.ofEpochSecond(0L).toString()) .actualRound(0L) .roundDuration(30) .build(); @@ -156,7 +154,7 @@ public void newGame(){ Authentication authentication = mock(Authentication.class); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); - GameResponseDto gameDto = gameService.newGame(authentication); + GameResponseDto gameDto = gameService.newGame(null,null,null,authentication); assertEquals(defaultGameResponseDto, gameDto); } @@ -243,7 +241,7 @@ public void answerQuestionCorrectly(){ when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); when(questionService.findRandomQuestion(any())).thenReturn(defaultQuestion); - gameService.newGame(authentication); + gameService.newGame(null,null,null,authentication); gameService.startRound(1L, authentication); gameService.answerQuestion(1L, new GameAnswerDto(1L), authentication); gameService.getGameDetails(1L, authentication); @@ -257,7 +255,7 @@ public void answerQuestionIncorrectly(){ when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); when(questionService.findRandomQuestion(any())).thenReturn(defaultQuestion); - gameService.newGame(authentication); + gameService.newGame(null,null,null,authentication); gameService.startRound(1L, authentication); gameService.answerQuestion(1L, new GameAnswerDto(2L), authentication); gameService.getGameDetails(1L, authentication); @@ -271,7 +269,7 @@ public void answerQuestionWhenGameHasFinished(){ when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); when(questionService.findRandomQuestion(any())).thenReturn(defaultQuestion); - gameService.newGame(authentication); + gameService.newGame(null,null,null,authentication); gameService.startRound(1L, authentication); defaultGame.setGameOver(true); defaultGame.setActualRound(30L); @@ -284,7 +282,7 @@ public void answerQuestionWhenRoundHasFinished(){ when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); when(questionService.findRandomQuestion(any())).thenReturn(defaultQuestion); - gameService.newGame(authentication); + gameService.newGame(null,null,null,authentication); gameService.startRound(1L, authentication); defaultGame.setRoundStartTime(Instant.now().minusSeconds(100).toEpochMilli()); assertThrows(IllegalStateException.class, () -> gameService.answerQuestion(1L, new GameAnswerDto(1L), authentication)); @@ -296,7 +294,7 @@ public void answerQuestionInvalidId(){ when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); when(questionService.findRandomQuestion(any())).thenReturn(defaultQuestion); - gameService.newGame(authentication); + gameService.newGame(null,null,null,authentication); gameService.startRound(1L, authentication); assertThrows(IllegalArgumentException.class, () -> gameService.answerQuestion(1L, new GameAnswerDto(3L), authentication)); } @@ -306,7 +304,7 @@ public void changeLanguage(){ when(gameRepository.findByIdForUser(any(), any())).thenReturn(Optional.of(defaultGame)); when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); - gameService.newGame(authentication); + gameService.newGame(null,null,null,authentication); gameService.startRound(1L, authentication); gameService.changeLanguage(1L, "es", authentication); gameService.getGameDetails(1L, authentication); @@ -319,7 +317,7 @@ public void changeLanguageGameOver(){ when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); - gameService.newGame(authentication); + gameService.newGame(null,null,null,authentication); gameService.startRound(1L, authentication); defaultGame.setGameOver(true); defaultGame.setActualRound(10L); @@ -332,7 +330,7 @@ public void changeLanguageInvalidLanguage(){ when(gameRepository.findByIdForUser(any(), any())).thenReturn(Optional.of(defaultGame)); when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); - gameService.newGame(authentication); + gameService.newGame(null,null,null,authentication); assertThrows(IllegalArgumentException.class, () -> gameService.changeLanguage(1L, "patata", authentication)); } @@ -341,9 +339,11 @@ public void getGameDetails(){ when(gameRepository.findByIdForUser(any(), any())).thenReturn(Optional.of(defaultGame)); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); - GameResponseDto gameDto = gameService.newGame(authentication); + + GameResponseDto gameDto = gameService.newGame(null,null,null,authentication); gameService.startRound(1L, authentication); gameService.getGameDetails(1L, authentication); + assertEquals(defaultGameResponseDto, gameDto); } @@ -352,7 +352,7 @@ public void getGameDetailsInvalidId(){ when(gameRepository.findByIdForUser(1L, 1L)).thenReturn(Optional.of(defaultGame)); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); - gameService.newGame(authentication); + gameService.newGame(null,null,null,authentication); gameService.startRound(1L, authentication); assertThrows(NoSuchElementException.class, () -> gameService.getGameDetails(2L, authentication)); } From 20436ed675b2ea52c5b57643075db3a1fedcb7bd Mon Sep 17 00:00:00 2001 From: Dario Date: Fri, 12 Apr 2024 17:59:05 +0200 Subject: [PATCH 03/18] refactor: /new is now /play --- .../lab/en2b/quizapi/game/GameController.java | 6 +++-- .../en2b/quizapi/game/GameControllerTest.java | 22 ++++++++++++++++--- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/api/src/main/java/lab/en2b/quizapi/game/GameController.java b/api/src/main/java/lab/en2b/quizapi/game/GameController.java index a0bbb197..e0981306 100644 --- a/api/src/main/java/lab/en2b/quizapi/game/GameController.java +++ b/api/src/main/java/lab/en2b/quizapi/game/GameController.java @@ -27,8 +27,10 @@ public class GameController { @ApiResponse(responseCode = "200", description = "Successfully retrieved"), @ApiResponse(responseCode = "403", description = "You are not logged in", content = @io.swagger.v3.oas.annotations.media.Content), }) - @PostMapping("/start") - public ResponseEntity newGame(@RequestParam(required = false) String lang, @RequestParam(required=false) GameMode gamemode, @RequestBody CustomGameDto customGameDto, Authentication authentication){ + @PostMapping("/play") + public ResponseEntity newGame(@RequestParam(required = false) String lang, @RequestParam(required=false) GameMode gamemode, @RequestBody(required = false) CustomGameDto customGameDto, Authentication authentication){ + if(gamemode == GameMode.CUSTOM && customGameDto == null) + throw new IllegalArgumentException("Custom game mode requires a body"); return ResponseEntity.ok(gameService.newGame(lang,gamemode,customGameDto,authentication)); } diff --git a/api/src/test/java/lab/en2b/quizapi/game/GameControllerTest.java b/api/src/test/java/lab/en2b/quizapi/game/GameControllerTest.java index f9865ad8..d20d9924 100644 --- a/api/src/test/java/lab/en2b/quizapi/game/GameControllerTest.java +++ b/api/src/test/java/lab/en2b/quizapi/game/GameControllerTest.java @@ -41,20 +41,36 @@ public class GameControllerTest { @Test void newQuestionShouldReturn403() throws Exception{ - mockMvc.perform(post("/games/new") + mockMvc.perform(post("/games/play") .contentType("application/json") .with(csrf())) .andExpect(status().isForbidden()); } @Test - void newQuestionShouldReturn200() throws Exception{ - mockMvc.perform(post("/games/new") + void newGameShouldReturn200() throws Exception{ + mockMvc.perform(post("/games/play") .with(user("test").roles("user")) .contentType("application/json") .with(csrf())) .andExpect(status().isOk()); } + @Test + void newGameCustomNoBodyShouldReturn400() throws Exception{ + mockMvc.perform(post("/games/play?gamemode=CUSTOM") + .with(user("test").roles("user")) + .contentType("application/json") + .with(csrf())) + .andExpect(status().isBadRequest()); + } + @Test + void newGameInvalidGameModeShouldReturn400() throws Exception{ + mockMvc.perform(post("/games/play?gamemode=patata") + .with(user("test").roles("user")) + .contentType("application/json") + .with(csrf())) + .andExpect(status().isBadRequest()); + } @Test void startRoundShouldReturn403() throws Exception{ From f86ad7b8d165ea5fcce67a2ce932f8b1662e8f42 Mon Sep 17 00:00:00 2001 From: Dario Date: Fri, 12 Apr 2024 17:59:39 +0200 Subject: [PATCH 04/18] feat: question generation takes gamemode into account --- .../lab/en2b/quizapi/game/GameService.java | 2 +- .../question/QuestionRepository.java | 8 +++-- .../questions/question/QuestionService.java | 32 +++++++++++++++---- 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/api/src/main/java/lab/en2b/quizapi/game/GameService.java b/api/src/main/java/lab/en2b/quizapi/game/GameService.java index 68043d71..09192db0 100644 --- a/api/src/main/java/lab/en2b/quizapi/game/GameService.java +++ b/api/src/main/java/lab/en2b/quizapi/game/GameService.java @@ -55,7 +55,7 @@ public GameResponseDto startRound(Long id, Authentication authentication) { gameRepository.save(game); saveStatistics(game); } - game.newRound(questionService.findRandomQuestion(game.getLanguage())); + game.newRound(questionService.findRandomQuestion(game.getLanguage(),game.getGamemode(),game.getQuestionCategoriesForCustom())); return gameResponseDtoMapper.apply(gameRepository.save(game)); } diff --git a/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionRepository.java b/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionRepository.java index 35c61d92..3e163b20 100644 --- a/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionRepository.java +++ b/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionRepository.java @@ -3,7 +3,11 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import java.util.List; + public interface QuestionRepository extends JpaRepository { - @Query(value = "SELECT q.* FROM questions q INNER JOIN answers a ON q.correct_answer_id=a.id WHERE a.language = ?1 ORDER BY RANDOM() LIMIT 1", nativeQuery = true) - Question findRandomQuestion(String lang); + @Query(value = "SELECT q.* FROM questions q INNER JOIN answers a ON q.correct_answer_id=a.id WHERE a.language = ?1 " + + "AND a.category IN ?2 " + + " ORDER BY RANDOM() LIMIT 1 ", nativeQuery = true) + Question findRandomQuestion(String lang, List questionCategories); } diff --git a/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionService.java b/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionService.java index 264e3605..cf20cc07 100644 --- a/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionService.java +++ b/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionService.java @@ -1,6 +1,7 @@ package lab.en2b.quizapi.questions.question; import lab.en2b.quizapi.commons.exceptions.InternalApiErrorException; +import lab.en2b.quizapi.game.GameMode; import lab.en2b.quizapi.questions.answer.Answer; import lab.en2b.quizapi.questions.answer.AnswerRepository; import lab.en2b.quizapi.questions.answer.dtos.AnswerDto; @@ -42,19 +43,21 @@ else if(question.getAnswers().stream().noneMatch(i -> i.getId().equals(answerDto } public QuestionResponseDto getRandomQuestion(String lang) { - return questionResponseDtoMapper.apply(findRandomQuestion(lang)); + return questionResponseDtoMapper.apply(findRandomQuestion(lang, GameMode.KIWI_QUEST, null)); } /** * Find a random question for the specified language - * @param lang The language to find the question for + * @param language The language to find the question for * @return The random question */ - public Question findRandomQuestion(String lang){ - if (lang==null || lang.isBlank()) { - lang = "en"; + + public Question findRandomQuestion(String language, GameMode gamemode, List questionCategoriesForCustom) { + if (language==null || language.isBlank()) { + language = "en"; } - Question q = questionRepository.findRandomQuestion(lang); + List questionCategories = getQuestionCategoriesForGamemode(gamemode, questionCategoriesForCustom); + Question q = questionRepository.findRandomQuestion(language,questionCategories); if(q==null) { throw new InternalApiErrorException("No questions found for the specified language!"); } @@ -62,6 +65,19 @@ public Question findRandomQuestion(String lang){ return q; } + private List getQuestionCategoriesForGamemode(GameMode gamemode, List questionCategoriesForCustom) { + return switch (gamemode) { + case KIWI_QUEST -> + new ArrayList<>(List.of(QuestionCategory.ART, QuestionCategory.MUSIC, QuestionCategory.GEOGRAPHY)); + case FOOTBALL_SHOWDOWN -> new ArrayList<>(List.of(QuestionCategory.SPORTS)); + case GEO_GENIUS -> new ArrayList<>(List.of(QuestionCategory.GEOGRAPHY)); + case VIDEOGAME_ADVENTURE -> new ArrayList<>(List.of(QuestionCategory.VIDEOGAMES)); + case ANCIENT_ODYSSEY -> new ArrayList<>(List.of(QuestionCategory.ART)); + case CUSTOM -> questionCategoriesForCustom; + default -> new ArrayList<>(List.of(QuestionCategory.values())); + }; + } + public QuestionResponseDto getQuestionById(Long id) { return questionResponseDtoMapper.apply(questionRepository.findById(id).orElseThrow()); } @@ -74,6 +90,9 @@ public QuestionResponseDto getQuestionById(Long id) { //TODO: CHAPUZAS, FIXEAR ESTO private void loadAnswers(Question question) { // Create the new answers list with the distractors + if(question.getAnswers().size() > 1) { + return; + } List answers = new ArrayList<>(QuestionHelper.getDistractors(answerRepository, question)); // Add the correct answers.add(question.getCorrectAnswer()); @@ -84,4 +103,5 @@ private void loadAnswers(Question question) { question.setAnswers(answers); questionRepository.save(question); } + } From 8fde4d91222c0fb66300805eaefed3af3f452677 Mon Sep 17 00:00:00 2001 From: Dario Date: Fri, 12 Apr 2024 17:59:49 +0200 Subject: [PATCH 05/18] test: fixed tests --- .../en2b/quizapi/game/GameServiceTest.java | 20 +++++++++---------- .../questions/QuestionServiceTest.java | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/api/src/test/java/lab/en2b/quizapi/game/GameServiceTest.java b/api/src/test/java/lab/en2b/quizapi/game/GameServiceTest.java index 7b75e883..fdb59c2c 100644 --- a/api/src/test/java/lab/en2b/quizapi/game/GameServiceTest.java +++ b/api/src/test/java/lab/en2b/quizapi/game/GameServiceTest.java @@ -163,7 +163,7 @@ public void newGame(){ public void startRound(){ when(gameRepository.findByIdForUser(any(), any())).thenReturn(Optional.of(defaultGame)); when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); - when(questionService.findRandomQuestion(any())).thenReturn(defaultQuestion); + when(questionService.findRandomQuestion(any(),any(),any())).thenReturn(defaultQuestion); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); GameResponseDto gameDto = gameService.startRound(1L, authentication); GameResponseDto result = defaultGameResponseDto; @@ -176,7 +176,7 @@ public void startRound(){ @Test public void startRoundGameOver(){ when(gameRepository.findByIdForUser(any(), any())).thenReturn(Optional.of(defaultGame)); - when(questionService.findRandomQuestion(any())).thenReturn(defaultQuestion); + when(questionService.findRandomQuestion(any(),any(),any())).thenReturn(defaultQuestion); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); defaultGame.setActualRound(10L); assertThrows(IllegalStateException.class, () -> gameService.startRound(1L,authentication)); @@ -198,7 +198,7 @@ public void startRoundWhenRoundNotFinished(){ public void getCurrentQuestion() { when(gameRepository.findByIdForUser(any(), any())).thenReturn(Optional.of(defaultGame)); when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); - when(questionService.findRandomQuestion(any())).thenReturn(defaultQuestion); + when(questionService.findRandomQuestion(any(),any(),any())).thenReturn(defaultQuestion); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); gameService.startRound(1L,authentication); QuestionResponseDto questionDto = gameService.getCurrentQuestion(1L,authentication); @@ -216,7 +216,7 @@ public void getCurrentQuestionRoundNotStarted() { public void getCurrentQuestionRoundFinished() { when(gameRepository.findByIdForUser(any(), any())).thenReturn(Optional.of(defaultGame)); when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); - when(questionService.findRandomQuestion(any())).thenReturn(defaultQuestion); + when(questionService.findRandomQuestion(any(),any(),any())).thenReturn(defaultQuestion); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); gameService.startRound(1L,authentication); defaultGame.setRoundStartTime(Instant.now().minusSeconds(100).toEpochMilli()); @@ -228,7 +228,7 @@ public void getCurrentQuestionGameFinished() { when(gameRepository.findByIdForUser(any(), any())).thenReturn(Optional.of(defaultGame)); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); - when(questionService.findRandomQuestion(any())).thenReturn(defaultQuestion); + when(questionService.findRandomQuestion(any(),any(),any())).thenReturn(defaultQuestion); gameService.startRound(1L,authentication); defaultGame.setGameOver(true); defaultGame.setActualRound(10L); @@ -240,7 +240,7 @@ public void answerQuestionCorrectly(){ when(gameRepository.findByIdForUser(any(), any())).thenReturn(Optional.of(defaultGame)); when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); - when(questionService.findRandomQuestion(any())).thenReturn(defaultQuestion); + when(questionService.findRandomQuestion(any(),any(),any())).thenReturn(defaultQuestion); gameService.newGame(null,null,null,authentication); gameService.startRound(1L, authentication); gameService.answerQuestion(1L, new GameAnswerDto(1L), authentication); @@ -254,7 +254,7 @@ public void answerQuestionIncorrectly(){ when(gameRepository.findByIdForUser(any(), any())).thenReturn(Optional.of(defaultGame)); when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); - when(questionService.findRandomQuestion(any())).thenReturn(defaultQuestion); + when(questionService.findRandomQuestion(any(),any(),any())).thenReturn(defaultQuestion); gameService.newGame(null,null,null,authentication); gameService.startRound(1L, authentication); gameService.answerQuestion(1L, new GameAnswerDto(2L), authentication); @@ -268,7 +268,7 @@ public void answerQuestionWhenGameHasFinished(){ when(gameRepository.findByIdForUser(any(), any())).thenReturn(Optional.of(defaultGame)); when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); - when(questionService.findRandomQuestion(any())).thenReturn(defaultQuestion); + when(questionService.findRandomQuestion(any(),any(),any())).thenReturn(defaultQuestion); gameService.newGame(null,null,null,authentication); gameService.startRound(1L, authentication); defaultGame.setGameOver(true); @@ -281,7 +281,7 @@ public void answerQuestionWhenRoundHasFinished(){ when(gameRepository.findByIdForUser(any(), any())).thenReturn(Optional.of(defaultGame)); when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); - when(questionService.findRandomQuestion(any())).thenReturn(defaultQuestion); + when(questionService.findRandomQuestion(any(),any(),any())).thenReturn(defaultQuestion); gameService.newGame(null,null,null,authentication); gameService.startRound(1L, authentication); defaultGame.setRoundStartTime(Instant.now().minusSeconds(100).toEpochMilli()); @@ -293,7 +293,7 @@ public void answerQuestionInvalidId(){ when(gameRepository.findByIdForUser(any(), any())).thenReturn(Optional.of(defaultGame)); when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); - when(questionService.findRandomQuestion(any())).thenReturn(defaultQuestion); + when(questionService.findRandomQuestion(any(),any(),any())).thenReturn(defaultQuestion); gameService.newGame(null,null,null,authentication); gameService.startRound(1L, authentication); assertThrows(IllegalArgumentException.class, () -> gameService.answerQuestion(1L, new GameAnswerDto(3L), authentication)); diff --git a/api/src/test/java/lab/en2b/quizapi/questions/QuestionServiceTest.java b/api/src/test/java/lab/en2b/quizapi/questions/QuestionServiceTest.java index 347790a4..cac7c77d 100644 --- a/api/src/test/java/lab/en2b/quizapi/questions/QuestionServiceTest.java +++ b/api/src/test/java/lab/en2b/quizapi/questions/QuestionServiceTest.java @@ -98,7 +98,7 @@ void setUp() { @Test void testGetRandomQuestion() { - when(questionRepository.findRandomQuestion("en")).thenReturn(defaultQuestion); + when(questionRepository.findRandomQuestion(any(),any())).thenReturn(defaultQuestion); QuestionResponseDto response = questionService.getRandomQuestion(""); assertEquals(response.getId(), defaultResponseDto.getId()); From 6d19d3c4f16543e91e070b370142cd0ab92255ac Mon Sep 17 00:00:00 2001 From: Dario Date: Fri, 12 Apr 2024 18:09:45 +0200 Subject: [PATCH 06/18] refactor: moved question types for gamemode to game --- .../main/java/lab/en2b/quizapi/game/Game.java | 3 +-- .../lab/en2b/quizapi/game/GameService.java | 4 +--- .../questions/question/QuestionService.java | 20 +++--------------- .../en2b/quizapi/game/GameControllerTest.java | 2 -- .../en2b/quizapi/game/GameServiceTest.java | 21 ++++++++++--------- 5 files changed, 16 insertions(+), 34 deletions(-) diff --git a/api/src/main/java/lab/en2b/quizapi/game/Game.java b/api/src/main/java/lab/en2b/quizapi/game/Game.java index 46401956..12ac79cd 100644 --- a/api/src/main/java/lab/en2b/quizapi/game/Game.java +++ b/api/src/main/java/lab/en2b/quizapi/game/Game.java @@ -175,7 +175,6 @@ private void setGamemodeParams(GameMode gamemode){ //This could be moved to a Ga throw new IllegalStateException("Invalid gamemode!"); } this.gamemode = gamemode; - } public void setQuestionCategoriesForCustom(List questionCategoriesForCustom) { @@ -188,7 +187,7 @@ public void setQuestionCategoriesForCustom(List questionCatego public List getQuestionCategoriesForGamemode(){ return switch (gamemode) { - case KIWI_QUEST -> List.of(QuestionCategory.GEOGRAPHY, QuestionCategory.MUSIC); + case KIWI_QUEST -> List.of(QuestionCategory.ART, QuestionCategory.MUSIC, QuestionCategory.GEOGRAPHY); case FOOTBALL_SHOWDOWN -> List.of(QuestionCategory.SPORTS); case GEO_GENIUS -> List.of(QuestionCategory.GEOGRAPHY); case VIDEOGAME_ADVENTURE -> List.of(QuestionCategory.VIDEOGAMES); diff --git a/api/src/main/java/lab/en2b/quizapi/game/GameService.java b/api/src/main/java/lab/en2b/quizapi/game/GameService.java index 09192db0..4d2ec474 100644 --- a/api/src/main/java/lab/en2b/quizapi/game/GameService.java +++ b/api/src/main/java/lab/en2b/quizapi/game/GameService.java @@ -7,7 +7,6 @@ import lab.en2b.quizapi.game.dtos.GameResponseDto; import lab.en2b.quizapi.game.mappers.GameResponseDtoMapper; import lab.en2b.quizapi.questions.question.QuestionCategory; -import lab.en2b.quizapi.questions.question.QuestionRepository; import lab.en2b.quizapi.questions.question.QuestionService; import lab.en2b.quizapi.questions.question.dtos.QuestionResponseDto; import lab.en2b.quizapi.questions.question.mappers.QuestionResponseDtoMapper; @@ -18,7 +17,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -55,7 +53,7 @@ public GameResponseDto startRound(Long id, Authentication authentication) { gameRepository.save(game); saveStatistics(game); } - game.newRound(questionService.findRandomQuestion(game.getLanguage(),game.getGamemode(),game.getQuestionCategoriesForCustom())); + game.newRound(questionService.findRandomQuestion(game.getLanguage(),game.getQuestionCategoriesForGamemode())); return gameResponseDtoMapper.apply(gameRepository.save(game)); } diff --git a/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionService.java b/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionService.java index cf20cc07..77a5241e 100644 --- a/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionService.java +++ b/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionService.java @@ -43,7 +43,7 @@ else if(question.getAnswers().stream().noneMatch(i -> i.getId().equals(answerDto } public QuestionResponseDto getRandomQuestion(String lang) { - return questionResponseDtoMapper.apply(findRandomQuestion(lang, GameMode.KIWI_QUEST, null)); + return questionResponseDtoMapper.apply(findRandomQuestion(lang, List.of(QuestionCategory.values()))); } /** @@ -52,12 +52,11 @@ public QuestionResponseDto getRandomQuestion(String lang) { * @return The random question */ - public Question findRandomQuestion(String language, GameMode gamemode, List questionCategoriesForCustom) { + public Question findRandomQuestion(String language, List questionCategoriesForCustom) { if (language==null || language.isBlank()) { language = "en"; } - List questionCategories = getQuestionCategoriesForGamemode(gamemode, questionCategoriesForCustom); - Question q = questionRepository.findRandomQuestion(language,questionCategories); + Question q = questionRepository.findRandomQuestion(language,questionCategoriesForCustom); if(q==null) { throw new InternalApiErrorException("No questions found for the specified language!"); } @@ -65,19 +64,6 @@ public Question findRandomQuestion(String language, GameMode gamemode, List getQuestionCategoriesForGamemode(GameMode gamemode, List questionCategoriesForCustom) { - return switch (gamemode) { - case KIWI_QUEST -> - new ArrayList<>(List.of(QuestionCategory.ART, QuestionCategory.MUSIC, QuestionCategory.GEOGRAPHY)); - case FOOTBALL_SHOWDOWN -> new ArrayList<>(List.of(QuestionCategory.SPORTS)); - case GEO_GENIUS -> new ArrayList<>(List.of(QuestionCategory.GEOGRAPHY)); - case VIDEOGAME_ADVENTURE -> new ArrayList<>(List.of(QuestionCategory.VIDEOGAMES)); - case ANCIENT_ODYSSEY -> new ArrayList<>(List.of(QuestionCategory.ART)); - case CUSTOM -> questionCategoriesForCustom; - default -> new ArrayList<>(List.of(QuestionCategory.values())); - }; - } - public QuestionResponseDto getQuestionById(Long id) { return questionResponseDtoMapper.apply(questionRepository.findById(id).orElseThrow()); } diff --git a/api/src/test/java/lab/en2b/quizapi/game/GameControllerTest.java b/api/src/test/java/lab/en2b/quizapi/game/GameControllerTest.java index d20d9924..5aed8c5f 100644 --- a/api/src/test/java/lab/en2b/quizapi/game/GameControllerTest.java +++ b/api/src/test/java/lab/en2b/quizapi/game/GameControllerTest.java @@ -4,8 +4,6 @@ import lab.en2b.quizapi.auth.jwt.JwtUtils; import lab.en2b.quizapi.commons.user.UserService; import lab.en2b.quizapi.game.dtos.GameAnswerDto; -import lab.en2b.quizapi.questions.answer.dtos.AnswerDto; -import lab.en2b.quizapi.questions.question.QuestionController; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; diff --git a/api/src/test/java/lab/en2b/quizapi/game/GameServiceTest.java b/api/src/test/java/lab/en2b/quizapi/game/GameServiceTest.java index fdb59c2c..1cee4ad8 100644 --- a/api/src/test/java/lab/en2b/quizapi/game/GameServiceTest.java +++ b/api/src/test/java/lab/en2b/quizapi/game/GameServiceTest.java @@ -143,6 +143,7 @@ void setUp() { .rounds(9L) .actualRound(0L) .roundStartTime(0L) + .gamemode(GameMode.KIWI_QUEST) .correctlyAnsweredQuestions(0L) .language("en") .roundDuration(30) @@ -163,7 +164,7 @@ public void newGame(){ public void startRound(){ when(gameRepository.findByIdForUser(any(), any())).thenReturn(Optional.of(defaultGame)); when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); - when(questionService.findRandomQuestion(any(),any(),any())).thenReturn(defaultQuestion); + when(questionService.findRandomQuestion(any(),any())).thenReturn(defaultQuestion); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); GameResponseDto gameDto = gameService.startRound(1L, authentication); GameResponseDto result = defaultGameResponseDto; @@ -176,7 +177,7 @@ public void startRound(){ @Test public void startRoundGameOver(){ when(gameRepository.findByIdForUser(any(), any())).thenReturn(Optional.of(defaultGame)); - when(questionService.findRandomQuestion(any(),any(),any())).thenReturn(defaultQuestion); + when(questionService.findRandomQuestion(any(),any())).thenReturn(defaultQuestion); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); defaultGame.setActualRound(10L); assertThrows(IllegalStateException.class, () -> gameService.startRound(1L,authentication)); @@ -198,7 +199,7 @@ public void startRoundWhenRoundNotFinished(){ public void getCurrentQuestion() { when(gameRepository.findByIdForUser(any(), any())).thenReturn(Optional.of(defaultGame)); when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); - when(questionService.findRandomQuestion(any(),any(),any())).thenReturn(defaultQuestion); + when(questionService.findRandomQuestion(any(),any())).thenReturn(defaultQuestion); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); gameService.startRound(1L,authentication); QuestionResponseDto questionDto = gameService.getCurrentQuestion(1L,authentication); @@ -216,7 +217,7 @@ public void getCurrentQuestionRoundNotStarted() { public void getCurrentQuestionRoundFinished() { when(gameRepository.findByIdForUser(any(), any())).thenReturn(Optional.of(defaultGame)); when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); - when(questionService.findRandomQuestion(any(),any(),any())).thenReturn(defaultQuestion); + when(questionService.findRandomQuestion(any(),any())).thenReturn(defaultQuestion); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); gameService.startRound(1L,authentication); defaultGame.setRoundStartTime(Instant.now().minusSeconds(100).toEpochMilli()); @@ -228,7 +229,7 @@ public void getCurrentQuestionGameFinished() { when(gameRepository.findByIdForUser(any(), any())).thenReturn(Optional.of(defaultGame)); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); - when(questionService.findRandomQuestion(any(),any(),any())).thenReturn(defaultQuestion); + when(questionService.findRandomQuestion(any(),any())).thenReturn(defaultQuestion); gameService.startRound(1L,authentication); defaultGame.setGameOver(true); defaultGame.setActualRound(10L); @@ -240,7 +241,7 @@ public void answerQuestionCorrectly(){ when(gameRepository.findByIdForUser(any(), any())).thenReturn(Optional.of(defaultGame)); when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); - when(questionService.findRandomQuestion(any(),any(),any())).thenReturn(defaultQuestion); + when(questionService.findRandomQuestion(any(),any())).thenReturn(defaultQuestion); gameService.newGame(null,null,null,authentication); gameService.startRound(1L, authentication); gameService.answerQuestion(1L, new GameAnswerDto(1L), authentication); @@ -254,7 +255,7 @@ public void answerQuestionIncorrectly(){ when(gameRepository.findByIdForUser(any(), any())).thenReturn(Optional.of(defaultGame)); when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); - when(questionService.findRandomQuestion(any(),any(),any())).thenReturn(defaultQuestion); + when(questionService.findRandomQuestion(any(),any())).thenReturn(defaultQuestion); gameService.newGame(null,null,null,authentication); gameService.startRound(1L, authentication); gameService.answerQuestion(1L, new GameAnswerDto(2L), authentication); @@ -268,7 +269,7 @@ public void answerQuestionWhenGameHasFinished(){ when(gameRepository.findByIdForUser(any(), any())).thenReturn(Optional.of(defaultGame)); when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); - when(questionService.findRandomQuestion(any(),any(),any())).thenReturn(defaultQuestion); + when(questionService.findRandomQuestion(any(),any())).thenReturn(defaultQuestion); gameService.newGame(null,null,null,authentication); gameService.startRound(1L, authentication); defaultGame.setGameOver(true); @@ -281,7 +282,7 @@ public void answerQuestionWhenRoundHasFinished(){ when(gameRepository.findByIdForUser(any(), any())).thenReturn(Optional.of(defaultGame)); when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); - when(questionService.findRandomQuestion(any(),any(),any())).thenReturn(defaultQuestion); + when(questionService.findRandomQuestion(any(),any())).thenReturn(defaultQuestion); gameService.newGame(null,null,null,authentication); gameService.startRound(1L, authentication); defaultGame.setRoundStartTime(Instant.now().minusSeconds(100).toEpochMilli()); @@ -293,7 +294,7 @@ public void answerQuestionInvalidId(){ when(gameRepository.findByIdForUser(any(), any())).thenReturn(Optional.of(defaultGame)); when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); - when(questionService.findRandomQuestion(any(),any(),any())).thenReturn(defaultQuestion); + when(questionService.findRandomQuestion(any(),any())).thenReturn(defaultQuestion); gameService.newGame(null,null,null,authentication); gameService.startRound(1L, authentication); assertThrows(IllegalArgumentException.class, () -> gameService.answerQuestion(1L, new GameAnswerDto(3L), authentication)); From 4949fcea1eae678ac736d80038b6cdda3b7ae34f Mon Sep 17 00:00:00 2001 From: Dario Date: Fri, 12 Apr 2024 18:39:33 +0200 Subject: [PATCH 07/18] feat; list of gamemodes --- .../lab/en2b/quizapi/game/GameController.java | 15 ++- .../java/lab/en2b/quizapi/game/GameMode.java | 1 + .../lab/en2b/quizapi/game/GameService.java | 125 +++++++++++++----- .../en2b/quizapi/game/dtos/GameModeDto.java | 19 +++ .../questions/question/QuestionService.java | 1 - 5 files changed, 125 insertions(+), 36 deletions(-) create mode 100644 api/src/main/java/lab/en2b/quizapi/game/dtos/GameModeDto.java diff --git a/api/src/main/java/lab/en2b/quizapi/game/GameController.java b/api/src/main/java/lab/en2b/quizapi/game/GameController.java index e0981306..123b7f96 100644 --- a/api/src/main/java/lab/en2b/quizapi/game/GameController.java +++ b/api/src/main/java/lab/en2b/quizapi/game/GameController.java @@ -3,10 +3,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; -import lab.en2b.quizapi.game.dtos.AnswerGameResponseDto; -import lab.en2b.quizapi.game.dtos.CustomGameDto; -import lab.en2b.quizapi.game.dtos.GameAnswerDto; -import lab.en2b.quizapi.game.dtos.GameResponseDto; +import lab.en2b.quizapi.game.dtos.*; import lab.en2b.quizapi.questions.question.QuestionCategory; import lab.en2b.quizapi.questions.question.dtos.QuestionResponseDto; import lombok.RequiredArgsConstructor; @@ -86,6 +83,16 @@ public ResponseEntity getGameDetails(@PathVariable Long id, Aut return ResponseEntity.ok(gameService.getGameDetails(id, authentication)); } + @Operation(summary = "Get the list of gamemodes a game can have") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Successfully retrieved"), + @ApiResponse(responseCode = "403", description = "You are not logged in", content = @io.swagger.v3.oas.annotations.media.Content) + }) + @GetMapping("/gamemodes") + public ResponseEntity> getQuestionGameModes(){ + return ResponseEntity.ok(gameService.getQuestionGameModes()); + } + @GetMapping("/questionCategories") public ResponseEntity> getQuestionCategories(){ return ResponseEntity.ok(gameService.getQuestionCategories()); diff --git a/api/src/main/java/lab/en2b/quizapi/game/GameMode.java b/api/src/main/java/lab/en2b/quizapi/game/GameMode.java index 0c5d1e8b..60e6bf7f 100644 --- a/api/src/main/java/lab/en2b/quizapi/game/GameMode.java +++ b/api/src/main/java/lab/en2b/quizapi/game/GameMode.java @@ -9,3 +9,4 @@ public enum GameMode { RANDOM, CUSTOM } + diff --git a/api/src/main/java/lab/en2b/quizapi/game/GameService.java b/api/src/main/java/lab/en2b/quizapi/game/GameService.java index 4d2ec474..5adaef68 100644 --- a/api/src/main/java/lab/en2b/quizapi/game/GameService.java +++ b/api/src/main/java/lab/en2b/quizapi/game/GameService.java @@ -1,10 +1,7 @@ package lab.en2b.quizapi.game; import lab.en2b.quizapi.commons.user.UserService; -import lab.en2b.quizapi.game.dtos.AnswerGameResponseDto; -import lab.en2b.quizapi.game.dtos.CustomGameDto; -import lab.en2b.quizapi.game.dtos.GameAnswerDto; -import lab.en2b.quizapi.game.dtos.GameResponseDto; +import lab.en2b.quizapi.game.dtos.*; import lab.en2b.quizapi.game.mappers.GameResponseDtoMapper; import lab.en2b.quizapi.questions.question.QuestionCategory; import lab.en2b.quizapi.questions.question.QuestionService; @@ -31,68 +28,106 @@ public class GameService { private final QuestionResponseDtoMapper questionResponseDtoMapper; private final StatisticsRepository statisticsRepository; + /** + * Creates a new game for the user + * @param lang the language of the game, default is ENGLISH + * @param gamemode the gamemode of the game, default is KIWI_QUEST + * @param newGameDto the custom game dto, only required if the gamemode is CUSTOM + * @param authentication the authentication of the user + * @return the newly created game + */ @Transactional public GameResponseDto newGame(String lang, GameMode gamemode, CustomGameDto newGameDto, Authentication authentication) { + // Check if there is an active game for the user Optional game = gameRepository.findActiveGameForUser(userService.getUserByAuthentication(authentication).getId()); - if (game.isPresent()){ - if (game.get().shouldBeGameOver()){ - game.get().setGameOver(true); - gameRepository.save(game.get()); - saveStatistics(game.get()); - }else{ - return gameResponseDtoMapper.apply(game.get()); - } + if (game.isPresent() && !wasGameMeantToBeOver(game.get())){ + // If there is an active game and it should not be over, return it + return gameResponseDtoMapper.apply(game.get()); } - return gameResponseDtoMapper.apply(gameRepository.save(new Game(userService.getUserByAuthentication(authentication),gamemode,lang,newGameDto))); + return gameResponseDtoMapper.apply(gameRepository.save( + new Game(userService.getUserByAuthentication(authentication),gamemode,lang,newGameDto) + )); } + /** + * Starts a new round for the game + * @param id the id of the game to start the round for + * @param authentication the authentication of the user + * @return the game with the new round started + */ public GameResponseDto startRound(Long id, Authentication authentication) { + // Get the game by id and user Game game = gameRepository.findByIdForUser(id, userService.getUserByAuthentication(authentication).getId()).orElseThrow(); - if (game.shouldBeGameOver()){ - game.setGameOver(true); - gameRepository.save(game); - saveStatistics(game); - } + // Check if the game should be over + wasGameMeantToBeOver(game); + // Start a new round game.newRound(questionService.findRandomQuestion(game.getLanguage(),game.getQuestionCategoriesForGamemode())); return gameResponseDtoMapper.apply(gameRepository.save(game)); } + /** + * Gets the current question for the game + * @param id the id of the game to get the question for + * @param authentication the authentication of the user + * @return the current question + */ public QuestionResponseDto getCurrentQuestion(Long id, Authentication authentication){ Game game = gameRepository.findByIdForUser(id, userService.getUserByAuthentication(authentication).getId()).orElseThrow(); return questionResponseDtoMapper.apply(game.getCurrentQuestion()); } + /** + * Answers the current question for the game + * @param id the id of the game to answer the question for + * @param dto the answer dto + * @param authentication the authentication of the user + * @return the response of the answer + */ @Transactional public AnswerGameResponseDto answerQuestion(Long id, GameAnswerDto dto, Authentication authentication){ Game game = gameRepository.findByIdForUser(id, userService.getUserByAuthentication(authentication).getId()).orElseThrow(); + // Check if the game should be over + wasGameMeantToBeOver(game); + // Answer the question boolean wasCorrect = game.answerQuestion(dto.getAnswerId()); - - if (game.shouldBeGameOver()){ - game.setGameOver(true); - gameRepository.save(game); - saveStatistics(game); - } + // Check if the game is over after the answer + wasGameMeantToBeOver(game); return new AnswerGameResponseDto(wasCorrect); } + + /** + * Saves the statistics of the game + * @param game the game to save the statistics for + */ private void saveStatistics(Game game){ + Statistics statistics; if (statisticsRepository.findByUserId(game.getUser().getId()).isPresent()){ - Statistics statistics = statisticsRepository.findByUserId(game.getUser().getId()).get(); + // If there are statistics for the user, update them + statistics = statisticsRepository.findByUserId(game.getUser().getId()).get(); statistics.updateStatistics(game.getCorrectlyAnsweredQuestions(), game.getQuestions().size()-game.getCorrectlyAnsweredQuestions(), game.getRounds()); - statisticsRepository.save(statistics); } else { - Statistics statistics = Statistics.builder() + // If there are no statistics for the user, create new ones + statistics = Statistics.builder() .user(game.getUser()) .correct(game.getCorrectlyAnsweredQuestions()) .wrong(game.getQuestions().size()-game.getCorrectlyAnsweredQuestions()) .total(game.getRounds()) .build(); - statisticsRepository.save(statistics); } + statisticsRepository.save(statistics); } + + /** + * Changes the language of the game. The game language will only change after the next round. + * @param id the id of the game to change the language for + * @param language the language to change to + * @param authentication the authentication of the user + * @return the game with the new language + */ public GameResponseDto changeLanguage(Long id, String language, Authentication authentication) { Game game = gameRepository.findByIdForUser(id, userService.getUserByAuthentication(authentication).getId()).orElseThrow(); if(game.isGameOver()){ @@ -102,17 +137,45 @@ public GameResponseDto changeLanguage(Long id, String language, Authentication a return gameResponseDtoMapper.apply(gameRepository.save(game)); } + /** + * Gets the game details + * @param id the id of the game to get the details for + * @param authentication the authentication of the user + * @return the game details + */ public GameResponseDto getGameDetails(Long id, Authentication authentication) { Game game = gameRepository.findByIdForUser(id, userService.getUserByAuthentication(authentication).getId()).orElseThrow(); + wasGameMeantToBeOver(game); + return gameResponseDtoMapper.apply(game); + } + + public List getQuestionCategories() { + return Arrays.asList(QuestionCategory.values()); + } + + private boolean wasGameMeantToBeOver(Game game) { if (game.shouldBeGameOver()){ game.setGameOver(true); gameRepository.save(game); saveStatistics(game); + return true; } - return gameResponseDtoMapper.apply(game); + return false; } - public List getQuestionCategories() { - return Arrays.asList(QuestionCategory.values()); + /** + * Gets the list of gamemodes a game can have + * @return the list of gamemodes + */ + public List getQuestionGameModes() { + return List.of( + new GameModeDto("Kiwi Quest","Our curated selection of the most exquisite questions. Enjoy with a glass of wine",GameMode.KIWI_QUEST,"FaKiwiBird"), + new GameModeDto("Football Showdown","Like sports? Like balls? This gamemode is for you!",GameMode.FOOTBALL_SHOWDOWN,"IoIosFootball"), + new GameModeDto("Geo Genius","Do you know the capital of Mongolia? I don't, so if you do this game is for you!",GameMode.GEO_GENIUS,"FaGlobeAmericas"), + new GameModeDto("Videogame Adventure","It's dangerous to go alone, guess this!",GameMode.VIDEOGAME_ADVENTURE,"IoLogoGameControllerB"), + new GameModeDto("Ancient Odyssey","Antiques are pricey for a reason!",GameMode.ANCIENT_ODYSSEY,"FaPalette"), + new GameModeDto("Random","Try a bit of everything!",GameMode.RANDOM,"FaRandom"), + new GameModeDto("Custom","Don't like our gamemodes? That's fine! (I only feel a bit offended)",GameMode.CUSTOM,"FaCog") + ); } } diff --git a/api/src/main/java/lab/en2b/quizapi/game/dtos/GameModeDto.java b/api/src/main/java/lab/en2b/quizapi/game/dtos/GameModeDto.java new file mode 100644 index 00000000..01127ccf --- /dev/null +++ b/api/src/main/java/lab/en2b/quizapi/game/dtos/GameModeDto.java @@ -0,0 +1,19 @@ +package lab.en2b.quizapi.game.dtos; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lab.en2b.quizapi.game.GameMode; +import lombok.*; + +@Getter +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Setter +public class GameModeDto { + private String name; + private String description; + @JsonProperty("internal_representation") + private GameMode internalRepresentation; + @JsonProperty("icon_name") + private String iconName; +} diff --git a/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionService.java b/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionService.java index 77a5241e..7c37356c 100644 --- a/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionService.java +++ b/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionService.java @@ -1,7 +1,6 @@ package lab.en2b.quizapi.questions.question; import lab.en2b.quizapi.commons.exceptions.InternalApiErrorException; -import lab.en2b.quizapi.game.GameMode; import lab.en2b.quizapi.questions.answer.Answer; import lab.en2b.quizapi.questions.answer.AnswerRepository; import lab.en2b.quizapi.questions.answer.dtos.AnswerDto; From af375f9ce3783058f32cdc9ca879f23e63939967 Mon Sep 17 00:00:00 2001 From: Dario Date: Fri, 12 Apr 2024 19:06:43 +0200 Subject: [PATCH 08/18] fix: language bugs, question selection and others --- api/src/main/java/lab/en2b/quizapi/game/Game.java | 3 +++ api/src/main/java/lab/en2b/quizapi/game/GameService.java | 1 + .../main/java/lab/en2b/quizapi/game/dtos/GameResponseDto.java | 4 ++++ .../lab/en2b/quizapi/game/mappers/GameResponseDtoMapper.java | 1 + .../en2b/quizapi/questions/question/QuestionRepository.java | 4 ++-- .../lab/en2b/quizapi/questions/question/QuestionService.java | 2 +- api/src/test/java/lab/en2b/quizapi/game/GameServiceTest.java | 1 + 7 files changed, 13 insertions(+), 3 deletions(-) diff --git a/api/src/main/java/lab/en2b/quizapi/game/Game.java b/api/src/main/java/lab/en2b/quizapi/game/Game.java index 12ac79cd..371d2904 100644 --- a/api/src/main/java/lab/en2b/quizapi/game/Game.java +++ b/api/src/main/java/lab/en2b/quizapi/game/Game.java @@ -186,6 +186,9 @@ public void setQuestionCategoriesForCustom(List questionCatego } public List getQuestionCategoriesForGamemode(){ + if(gamemode == null){ + gamemode = KIWI_QUEST; + } return switch (gamemode) { case KIWI_QUEST -> List.of(QuestionCategory.ART, QuestionCategory.MUSIC, QuestionCategory.GEOGRAPHY); case FOOTBALL_SHOWDOWN -> List.of(QuestionCategory.SPORTS); diff --git a/api/src/main/java/lab/en2b/quizapi/game/GameService.java b/api/src/main/java/lab/en2b/quizapi/game/GameService.java index 5adaef68..9b084ef0 100644 --- a/api/src/main/java/lab/en2b/quizapi/game/GameService.java +++ b/api/src/main/java/lab/en2b/quizapi/game/GameService.java @@ -55,6 +55,7 @@ public GameResponseDto newGame(String lang, GameMode gamemode, CustomGameDto new * @param authentication the authentication of the user * @return the game with the new round started */ + @Transactional public GameResponseDto startRound(Long id, Authentication authentication) { // Get the game by id and user Game game = gameRepository.findByIdForUser(id, userService.getUserByAuthentication(authentication).getId()).orElseThrow(); diff --git a/api/src/main/java/lab/en2b/quizapi/game/dtos/GameResponseDto.java b/api/src/main/java/lab/en2b/quizapi/game/dtos/GameResponseDto.java index ffb37336..57a63abc 100644 --- a/api/src/main/java/lab/en2b/quizapi/game/dtos/GameResponseDto.java +++ b/api/src/main/java/lab/en2b/quizapi/game/dtos/GameResponseDto.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; import lab.en2b.quizapi.commons.user.UserResponseDto; +import lab.en2b.quizapi.game.GameMode; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -43,4 +44,7 @@ public class GameResponseDto { @Schema(description = "Whether the game has finished or not", example = "true") private boolean isGameOver; + + @Schema(description = "Game mode for the game", example = "KIWI_QUEST") + private GameMode gamemode; } diff --git a/api/src/main/java/lab/en2b/quizapi/game/mappers/GameResponseDtoMapper.java b/api/src/main/java/lab/en2b/quizapi/game/mappers/GameResponseDtoMapper.java index 4f061c70..3fbc0b0f 100644 --- a/api/src/main/java/lab/en2b/quizapi/game/mappers/GameResponseDtoMapper.java +++ b/api/src/main/java/lab/en2b/quizapi/game/mappers/GameResponseDtoMapper.java @@ -23,6 +23,7 @@ public GameResponseDto apply(Game game) { .actualRound(game.getActualRound()) .roundDuration(game.getRoundDuration()) .roundStartTime(game.getRoundStartTime() != null? Instant.ofEpochMilli(game.getRoundStartTime()).toString(): null) + .gamemode(game.getGamemode()) .isGameOver(game.isGameOver()) .build(); } diff --git a/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionRepository.java b/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionRepository.java index 3e163b20..fd8e2eca 100644 --- a/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionRepository.java +++ b/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionRepository.java @@ -7,7 +7,7 @@ public interface QuestionRepository extends JpaRepository { @Query(value = "SELECT q.* FROM questions q INNER JOIN answers a ON q.correct_answer_id=a.id WHERE a.language = ?1 " + - "AND a.category IN ?2 " + + "AND q.question_category IN ?2 " + " ORDER BY RANDOM() LIMIT 1 ", nativeQuery = true) - Question findRandomQuestion(String lang, List questionCategories); + Question findRandomQuestion(String lang, List questionCategories); } diff --git a/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionService.java b/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionService.java index 7c37356c..47a8e549 100644 --- a/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionService.java +++ b/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionService.java @@ -55,7 +55,7 @@ public Question findRandomQuestion(String language, List quest if (language==null || language.isBlank()) { language = "en"; } - Question q = questionRepository.findRandomQuestion(language,questionCategoriesForCustom); + Question q = questionRepository.findRandomQuestion(language,questionCategoriesForCustom.stream().map(Enum::toString).toList()); if(q==null) { throw new InternalApiErrorException("No questions found for the specified language!"); } diff --git a/api/src/test/java/lab/en2b/quizapi/game/GameServiceTest.java b/api/src/test/java/lab/en2b/quizapi/game/GameServiceTest.java index 1cee4ad8..44b6a06d 100644 --- a/api/src/test/java/lab/en2b/quizapi/game/GameServiceTest.java +++ b/api/src/test/java/lab/en2b/quizapi/game/GameServiceTest.java @@ -135,6 +135,7 @@ void setUp() { .roundStartTime(Instant.ofEpochSecond(0L).toString()) .actualRound(0L) .roundDuration(30) + .gamemode(GameMode.KIWI_QUEST) .build(); this.defaultGame = Game.builder() .id(1L) From 282d2346781484fab259db81320c91b3a9124521 Mon Sep 17 00:00:00 2001 From: Dario Date: Fri, 12 Apr 2024 19:12:58 +0200 Subject: [PATCH 09/18] docs: documented play endpoint --- .../main/java/lab/en2b/quizapi/game/GameController.java | 8 +++++++- .../main/java/lab/en2b/quizapi/game/dtos/GameModeDto.java | 5 +++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/api/src/main/java/lab/en2b/quizapi/game/GameController.java b/api/src/main/java/lab/en2b/quizapi/game/GameController.java index 123b7f96..774de14a 100644 --- a/api/src/main/java/lab/en2b/quizapi/game/GameController.java +++ b/api/src/main/java/lab/en2b/quizapi/game/GameController.java @@ -22,6 +22,7 @@ public class GameController { @Operation(summary = "Starts new game", description = "Requests the API to create a new game for a given authentication (a player)") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Successfully retrieved"), + @ApiResponse(responseCode = "400", description = "Given when: \n * language provided is not valid \n * gamemode provided is not valid \n * body is not provided with custom game", content = @io.swagger.v3.oas.annotations.media.Content), @ApiResponse(responseCode = "403", description = "You are not logged in", content = @io.swagger.v3.oas.annotations.media.Content), }) @PostMapping("/play") @@ -65,7 +66,7 @@ public ResponseEntity answerQuestion(@PathVariable Long i @Operation(summary = "Changing languages", description = "Changes the language of the game for a given authentication (a player) and a language supported. Changes may are applied on the next round.") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Successfully retrieved"), - @ApiResponse(responseCode = "400", description = "Not a valid answer", content = @io.swagger.v3.oas.annotations.media.Content), + @ApiResponse(responseCode = "400", description = "Not a valid language to change to", content = @io.swagger.v3.oas.annotations.media.Content), @ApiResponse(responseCode = "403", description = "You are not logged in", content = @io.swagger.v3.oas.annotations.media.Content), }) @PutMapping("/{id}/language") @@ -93,6 +94,11 @@ public ResponseEntity> getQuestionGameModes(){ return ResponseEntity.ok(gameService.getQuestionGameModes()); } + @Operation(summary = "Get the list of categories a game can have") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Successfully retrieved"), + @ApiResponse(responseCode = "403", description = "You are not logged in", content = @io.swagger.v3.oas.annotations.media.Content) + }) @GetMapping("/questionCategories") public ResponseEntity> getQuestionCategories(){ return ResponseEntity.ok(gameService.getQuestionCategories()); diff --git a/api/src/main/java/lab/en2b/quizapi/game/dtos/GameModeDto.java b/api/src/main/java/lab/en2b/quizapi/game/dtos/GameModeDto.java index 01127ccf..82550eba 100644 --- a/api/src/main/java/lab/en2b/quizapi/game/dtos/GameModeDto.java +++ b/api/src/main/java/lab/en2b/quizapi/game/dtos/GameModeDto.java @@ -1,6 +1,7 @@ package lab.en2b.quizapi.game.dtos; import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lab.en2b.quizapi.game.GameMode; import lombok.*; @@ -10,10 +11,14 @@ @Builder @Setter public class GameModeDto { + @Schema(description = "Beautified name of the game mode",example = "Quiwi Quest") private String name; + @Schema(description = "Description of the game mode",example = "Test description of the game mode") private String description; @JsonProperty("internal_representation") + @Schema(description = "Internal code used for describing the game mode",example = "KIWI_QUEST") private GameMode internalRepresentation; @JsonProperty("icon_name") + @Schema(description = "Code for the icon used in the frontend of the application",example = "FaKiwiBird") private String iconName; } From 203b12c8f07847f78f1ffc422d91f4f7b98403c0 Mon Sep 17 00:00:00 2001 From: Dario Date: Fri, 12 Apr 2024 19:23:34 +0200 Subject: [PATCH 10/18] docs: params and body --- .../lab/en2b/quizapi/game/GameController.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/api/src/main/java/lab/en2b/quizapi/game/GameController.java b/api/src/main/java/lab/en2b/quizapi/game/GameController.java index 774de14a..3855863d 100644 --- a/api/src/main/java/lab/en2b/quizapi/game/GameController.java +++ b/api/src/main/java/lab/en2b/quizapi/game/GameController.java @@ -1,6 +1,8 @@ package lab.en2b.quizapi.game; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import lab.en2b.quizapi.game.dtos.*; @@ -25,6 +27,11 @@ public class GameController { @ApiResponse(responseCode = "400", description = "Given when: \n * language provided is not valid \n * gamemode provided is not valid \n * body is not provided with custom game", content = @io.swagger.v3.oas.annotations.media.Content), @ApiResponse(responseCode = "403", description = "You are not logged in", content = @io.swagger.v3.oas.annotations.media.Content), }) + @Parameters({ + @Parameter(name = "lang", description = "The language of the game", example = "en"), + @Parameter(name = "gamemode", description = "The gamemode of the game", example = "KIWI_QUEST") + }) + @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "The custom game dto, only required if the gamemode is CUSTOM") @PostMapping("/play") public ResponseEntity newGame(@RequestParam(required = false) String lang, @RequestParam(required=false) GameMode gamemode, @RequestBody(required = false) CustomGameDto customGameDto, Authentication authentication){ if(gamemode == GameMode.CUSTOM && customGameDto == null) @@ -37,27 +44,30 @@ public ResponseEntity newGame(@RequestParam(required = false) S @ApiResponse(responseCode = "200", description = "Successfully retrieved"), @ApiResponse(responseCode = "403", description = "You are not logged in", content = @io.swagger.v3.oas.annotations.media.Content), }) + @Parameter(name = "id", description = "The id of the game to start the round for", example = "1") @PostMapping("/{id}/startRound") public ResponseEntity startRound(@PathVariable Long id, Authentication authentication){ return ResponseEntity.ok(gameService.startRound(id, authentication)); } - @Operation(summary = "Starts a new round", description = "Gets the question and its possible answers from the API for a given authentication (a player)") + @Operation(summary = "Gets the current question", description = "Gets the question and its possible answers from the API for a given authentication (a player)") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Successfully retrieved"), @ApiResponse(responseCode = "403", description = "You are not logged in", content = @io.swagger.v3.oas.annotations.media.Content), }) + @Parameter(name = "id", description = "The id of the game to get the current question for", example = "1") @GetMapping("/{id}/question") public ResponseEntity getCurrentQuestion(@PathVariable Long id, Authentication authentication){ return ResponseEntity.ok(gameService.getCurrentQuestion(id, authentication)); } - @Operation(summary = "Starts a new round", description = "Starts the round (getting a question and its possible answers and start the timer) for a given authentication (a player)") + @Operation(summary = "Answers the question", description = "Answers the question for the current game") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Successfully retrieved"), @ApiResponse(responseCode = "400", description = "Not a valid answer", content = @io.swagger.v3.oas.annotations.media.Content), @ApiResponse(responseCode = "403", description = "You are not logged in", content = @io.swagger.v3.oas.annotations.media.Content), }) + @Parameter(name = "id", description = "The id of the game to answer the question for", example = "1") @PostMapping("/{id}/answer") public ResponseEntity answerQuestion(@PathVariable Long id, @RequestBody GameAnswerDto dto, Authentication authentication){ return ResponseEntity.ok(gameService.answerQuestion(id, dto, authentication)); @@ -69,6 +79,7 @@ public ResponseEntity answerQuestion(@PathVariable Long i @ApiResponse(responseCode = "400", description = "Not a valid language to change to", content = @io.swagger.v3.oas.annotations.media.Content), @ApiResponse(responseCode = "403", description = "You are not logged in", content = @io.swagger.v3.oas.annotations.media.Content), }) + @Parameter(name = "id", description = "The id of the game to change the language for", example = "1") @PutMapping("/{id}/language") public ResponseEntity changeLanguage(@PathVariable Long id, @RequestParam String language, Authentication authentication){ return ResponseEntity.ok(gameService.changeLanguage(id, language, authentication)); @@ -79,6 +90,7 @@ public ResponseEntity changeLanguage(@PathVariable Long id, @Re @ApiResponse(responseCode = "200", description = "Successfully retrieved"), @ApiResponse(responseCode = "403", description = "You are not logged in", content = @io.swagger.v3.oas.annotations.media.Content), }) + @Parameter(name = "id", description = "The id of the game to get the summary for", example = "1") @GetMapping("/{id}/details") public ResponseEntity getGameDetails(@PathVariable Long id, Authentication authentication){ return ResponseEntity.ok(gameService.getGameDetails(id, authentication)); From 310f558c01f8d4059da4c6d6e35ebf777b6c9ba5 Mon Sep 17 00:00:00 2001 From: Dario Date: Fri, 12 Apr 2024 20:24:48 +0200 Subject: [PATCH 11/18] test: game tests --- .../main/java/lab/en2b/quizapi/game/Game.java | 7 +- .../en2b/quizapi/game/GameControllerTest.java | 17 ++++ .../en2b/quizapi/game/GameServiceTest.java | 83 +++++++++++++++++-- 3 files changed, 97 insertions(+), 10 deletions(-) diff --git a/api/src/main/java/lab/en2b/quizapi/game/Game.java b/api/src/main/java/lab/en2b/quizapi/game/Game.java index 371d2904..ad83db8d 100644 --- a/api/src/main/java/lab/en2b/quizapi/game/Game.java +++ b/api/src/main/java/lab/en2b/quizapi/game/Game.java @@ -135,8 +135,8 @@ public void setLanguage(String language){ public void setCustomGameMode(CustomGameDto gameDto){ setRounds(gameDto.getRounds()); setRoundDuration(gameDto.getRoundDuration()); - setQuestionCategoriesForCustom(gameDto.getCategories()); this.gamemode = CUSTOM; + setQuestionCategoriesForCustom(gameDto.getCategories()); } public void setGamemode(GameMode gamemode){ if(gamemode == null){ @@ -172,7 +172,8 @@ private void setGamemodeParams(GameMode gamemode){ //This could be moved to a Ga setRoundDuration(30); break; default: - throw new IllegalStateException("Invalid gamemode!"); + setRounds(9L); + setRoundDuration(30); } this.gamemode = gamemode; } @@ -204,6 +205,6 @@ private boolean isLanguageSupported(String language) { } public boolean shouldBeGameOver() { - return getActualRound() >= getRounds() && !isGameOver; + return getActualRound() >= getRounds() && !isGameOver && currentRoundIsOver(); } } diff --git a/api/src/test/java/lab/en2b/quizapi/game/GameControllerTest.java b/api/src/test/java/lab/en2b/quizapi/game/GameControllerTest.java index 5aed8c5f..b0dc2982 100644 --- a/api/src/test/java/lab/en2b/quizapi/game/GameControllerTest.java +++ b/api/src/test/java/lab/en2b/quizapi/game/GameControllerTest.java @@ -192,4 +192,21 @@ void getQuestionCategoriesShouldReturn403() throws Exception{ .andExpect(status().isForbidden()); } + @Test + void getGameModeshouldReturn200() throws Exception{ + mockMvc.perform(get("/games/gamemodes") + .with(user("test").roles("user")) + .contentType("application/json") + .with(csrf())) + .andExpect(status().isOk()); + } + + @Test + void getGameModesShouldReturn403() throws Exception{ + mockMvc.perform(get("/games/gamemodes") + .contentType("application/json") + .with(csrf())) + .andExpect(status().isForbidden()); + } + } diff --git a/api/src/test/java/lab/en2b/quizapi/game/GameServiceTest.java b/api/src/test/java/lab/en2b/quizapi/game/GameServiceTest.java index 44b6a06d..4c8f0cb3 100644 --- a/api/src/test/java/lab/en2b/quizapi/game/GameServiceTest.java +++ b/api/src/test/java/lab/en2b/quizapi/game/GameServiceTest.java @@ -5,6 +5,7 @@ import lab.en2b.quizapi.commons.user.UserResponseDto; import lab.en2b.quizapi.commons.user.UserService; import lab.en2b.quizapi.commons.user.mappers.UserResponseDtoMapper; +import lab.en2b.quizapi.game.dtos.CustomGameDto; import lab.en2b.quizapi.game.dtos.GameAnswerDto; import lab.en2b.quizapi.game.dtos.GameResponseDto; import lab.en2b.quizapi.game.mappers.GameResponseDtoMapper; @@ -14,6 +15,7 @@ import lab.en2b.quizapi.questions.question.*; import lab.en2b.quizapi.questions.question.dtos.QuestionResponseDto; import lab.en2b.quizapi.questions.question.mappers.QuestionResponseDtoMapper; +import lab.en2b.quizapi.statistics.Statistics; import lab.en2b.quizapi.statistics.StatisticsRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -30,8 +32,7 @@ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; @ExtendWith({MockitoExtension.class, SpringExtension.class}) public class GameServiceTest { @@ -151,16 +152,73 @@ void setUp() { .build(); } + // NEW GAME TESTS @Test public void newGame(){ Authentication authentication = mock(Authentication.class); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); GameResponseDto gameDto = gameService.newGame(null,null,null,authentication); + assertEquals(defaultGameResponseDto, gameDto); + } + @Test + public void newGameActiveGame(){ + Authentication authentication = mock(Authentication.class); + when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); + when(gameRepository.findActiveGameForUser(1L)).thenReturn(Optional.of(defaultGame)); + GameResponseDto gameDto = gameService.newGame(null,null,null,authentication); + defaultGameResponseDto.setId(1L); assertEquals(defaultGameResponseDto, gameDto); } + @Test + public void newGameWasMeantToBeOver(){ + Authentication authentication = mock(Authentication.class); + when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); + when(gameRepository.findActiveGameForUser(1L)).thenReturn(Optional.of(defaultGame)); + when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); + defaultGame.setActualRound(10L); + gameService.newGame(null,null,null,authentication); + verify(statisticsRepository, times(1)).save(any()); + } + + @Test + public void newGameWasMeantToBeOverExistingLeaderboard(){ + Authentication authentication = mock(Authentication.class); + when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); + when(gameRepository.findActiveGameForUser(1L)).thenReturn(Optional.of(defaultGame)); + when(statisticsRepository.findByUserId(1L)).thenReturn(Optional.of(Statistics.builder().user(new User()) + .correct(0L) + .wrong(0L) + .total(0L) + .build())); + when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); + defaultGame.setActualRound(10L); + gameService.newGame(null,null,null,authentication); + verify(statisticsRepository, times(1)).save(any()); + } + + @Test + public void newGameCustomGame(){ + Authentication authentication = mock(Authentication.class); + when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); + when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); + GameResponseDto gameDto = gameService.newGame("es",GameMode.CUSTOM, + CustomGameDto.builder() + .roundDuration(30) + .categories(List.of(QuestionCategory.GEOGRAPHY)) + .rounds(10L) + .build() + ,authentication); + defaultGameResponseDto.setGamemode(GameMode.CUSTOM); + defaultGameResponseDto.setRounds(10L); + defaultGameResponseDto.setRoundDuration(30); + + assertEquals(defaultGameResponseDto, gameDto); + } + + // START ROUND TESTS @Test public void startRound(){ when(gameRepository.findByIdForUser(any(), any())).thenReturn(Optional.of(defaultGame)); @@ -184,17 +242,15 @@ public void startRoundGameOver(){ assertThrows(IllegalStateException.class, () -> gameService.startRound(1L,authentication)); } - /** @Test public void startRoundWhenRoundNotFinished(){ when(gameRepository.findByIdForUser(any(), any())).thenReturn(Optional.of(defaultGame)); when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); - when(questionService.findRandomQuestion(any())).thenReturn(defaultQuestion); + when(questionService.findRandomQuestion(any(),any())).thenReturn(defaultQuestion); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); gameService.startRound(1L,authentication); assertThrows(IllegalStateException.class, () -> gameService.startRound(1L,authentication)); } - **/ @Test public void getCurrentQuestion() { @@ -207,12 +263,12 @@ public void getCurrentQuestion() { assertEquals(defaultQuestionResponseDto, questionDto); } - /*@Test + @Test public void getCurrentQuestionRoundNotStarted() { when(gameRepository.findByIdForUser(any(), any())).thenReturn(Optional.of(defaultGame)); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); assertThrows(IllegalStateException.class, () -> gameService.getCurrentQuestion(1L,authentication)); - }*/ + } @Test public void getCurrentQuestionRoundFinished() { @@ -278,6 +334,19 @@ public void answerQuestionWhenGameHasFinished(){ assertThrows(IllegalStateException.class, () -> gameService.answerQuestion(1L, new GameAnswerDto(1L), authentication)); } + @Test + public void answerQuestionLastRound(){ + when(gameRepository.findByIdForUser(any(), any())).thenReturn(Optional.of(defaultGame)); + when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); + when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); + when(questionService.findRandomQuestion(any(),any())).thenReturn(defaultQuestion); + gameService.newGame(null,null,null,authentication); + defaultGame.setActualRound(8L); + gameService.startRound(1L, authentication); + gameService.answerQuestion(1L, new GameAnswerDto(1L), authentication); + verify(statisticsRepository, times(1)).save(any()); + } + @Test public void answerQuestionWhenRoundHasFinished(){ when(gameRepository.findByIdForUser(any(), any())).thenReturn(Optional.of(defaultGame)); From a8861b9021026e7f089e7f771e52197d8a005d4f Mon Sep 17 00:00:00 2001 From: Dario Date: Fri, 12 Apr 2024 20:38:47 +0200 Subject: [PATCH 12/18] fix: invalid custom game body --- .../java/lab/en2b/quizapi/game/GameController.java | 3 ++- .../lab/en2b/quizapi/game/GameControllerTest.java | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/api/src/main/java/lab/en2b/quizapi/game/GameController.java b/api/src/main/java/lab/en2b/quizapi/game/GameController.java index 3855863d..10234d5e 100644 --- a/api/src/main/java/lab/en2b/quizapi/game/GameController.java +++ b/api/src/main/java/lab/en2b/quizapi/game/GameController.java @@ -5,6 +5,7 @@ import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.validation.Valid; import lab.en2b.quizapi.game.dtos.*; import lab.en2b.quizapi.questions.question.QuestionCategory; import lab.en2b.quizapi.questions.question.dtos.QuestionResponseDto; @@ -33,7 +34,7 @@ public class GameController { }) @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "The custom game dto, only required if the gamemode is CUSTOM") @PostMapping("/play") - public ResponseEntity newGame(@RequestParam(required = false) String lang, @RequestParam(required=false) GameMode gamemode, @RequestBody(required = false) CustomGameDto customGameDto, Authentication authentication){ + public ResponseEntity newGame(@RequestParam(required = false) String lang, @RequestParam(required=false) GameMode gamemode, @RequestBody(required = false) @Valid CustomGameDto customGameDto, Authentication authentication){ if(gamemode == GameMode.CUSTOM && customGameDto == null) throw new IllegalArgumentException("Custom game mode requires a body"); return ResponseEntity.ok(gameService.newGame(lang,gamemode,customGameDto,authentication)); diff --git a/api/src/test/java/lab/en2b/quizapi/game/GameControllerTest.java b/api/src/test/java/lab/en2b/quizapi/game/GameControllerTest.java index b0dc2982..2dcac8a1 100644 --- a/api/src/test/java/lab/en2b/quizapi/game/GameControllerTest.java +++ b/api/src/test/java/lab/en2b/quizapi/game/GameControllerTest.java @@ -3,6 +3,7 @@ import lab.en2b.quizapi.auth.config.SecurityConfig; import lab.en2b.quizapi.auth.jwt.JwtUtils; import lab.en2b.quizapi.commons.user.UserService; +import lab.en2b.quizapi.game.dtos.CustomGameDto; import lab.en2b.quizapi.game.dtos.GameAnswerDto; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -62,6 +63,15 @@ void newGameCustomNoBodyShouldReturn400() throws Exception{ .andExpect(status().isBadRequest()); } @Test + void newGameInvalidBodyForCustomShouldReturn400() throws Exception{ + mockMvc.perform(post("/games/play?gamemode=CUSTOM") + .with(user("test").roles("user")) + .content(asJsonString(new CustomGameDto())) + .contentType("application/json") + .with(csrf())) + .andExpect(status().isBadRequest()); + } + @Test void newGameInvalidGameModeShouldReturn400() throws Exception{ mockMvc.perform(post("/games/play?gamemode=patata") .with(user("test").roles("user")) @@ -209,4 +219,5 @@ void getGameModesShouldReturn403() throws Exception{ .andExpect(status().isForbidden()); } + } From 291af7f2a8f8338b65e9ac6fa50ec9106460322b Mon Sep 17 00:00:00 2001 From: Dario Date: Fri, 12 Apr 2024 20:44:15 +0200 Subject: [PATCH 13/18] refactor: renamed get question categories endpoint --- api/src/main/java/lab/en2b/quizapi/game/GameController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/lab/en2b/quizapi/game/GameController.java b/api/src/main/java/lab/en2b/quizapi/game/GameController.java index 10234d5e..08af1201 100644 --- a/api/src/main/java/lab/en2b/quizapi/game/GameController.java +++ b/api/src/main/java/lab/en2b/quizapi/game/GameController.java @@ -112,7 +112,7 @@ public ResponseEntity> getQuestionGameModes(){ @ApiResponse(responseCode = "200", description = "Successfully retrieved"), @ApiResponse(responseCode = "403", description = "You are not logged in", content = @io.swagger.v3.oas.annotations.media.Content) }) - @GetMapping("/questionCategories") + @GetMapping("/question-categories") public ResponseEntity> getQuestionCategories(){ return ResponseEntity.ok(gameService.getQuestionCategories()); } From 1dee3b6108ab578d8d32d7ce570e5faec3099c3b Mon Sep 17 00:00:00 2001 From: Dario Date: Fri, 12 Apr 2024 20:44:31 +0200 Subject: [PATCH 14/18] fix: language not setting correctly --- api/src/main/java/lab/en2b/quizapi/game/Game.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/lab/en2b/quizapi/game/Game.java b/api/src/main/java/lab/en2b/quizapi/game/Game.java index ad83db8d..0fcb18da 100644 --- a/api/src/main/java/lab/en2b/quizapi/game/Game.java +++ b/api/src/main/java/lab/en2b/quizapi/game/Game.java @@ -61,7 +61,7 @@ public Game(User user,GameMode gamemode,String lang, CustomGameDto gameDto){ this.user = user; this.questions = new ArrayList<>(); this.actualRound = 0L; - this.language = lang; + setLanguage(lang); if(gamemode == CUSTOM) setCustomGameMode(gameDto); else From f73f6a229c9dcb52fa785aee5cd73336740c31d7 Mon Sep 17 00:00:00 2001 From: Dario Date: Fri, 12 Apr 2024 20:48:29 +0200 Subject: [PATCH 15/18] fix:saving categories now works with string --- api/src/main/java/lab/en2b/quizapi/game/Game.java | 1 + 1 file changed, 1 insertion(+) diff --git a/api/src/main/java/lab/en2b/quizapi/game/Game.java b/api/src/main/java/lab/en2b/quizapi/game/Game.java index 0fcb18da..a75c9ca5 100644 --- a/api/src/main/java/lab/en2b/quizapi/game/Game.java +++ b/api/src/main/java/lab/en2b/quizapi/game/Game.java @@ -55,6 +55,7 @@ public class Game { @OrderColumn private List questions; private boolean isGameOver; + @Enumerated(EnumType.STRING) private List questionCategoriesForCustom; public Game(User user,GameMode gamemode,String lang, CustomGameDto gameDto){ From 6c05fd55ea1c9f10649a9c7d8444a2357da342f4 Mon Sep 17 00:00:00 2001 From: Dario Date: Fri, 12 Apr 2024 20:53:59 +0200 Subject: [PATCH 16/18] test: no questions found --- .../en2b/quizapi/questions/QuestionServiceTest.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/api/src/test/java/lab/en2b/quizapi/questions/QuestionServiceTest.java b/api/src/test/java/lab/en2b/quizapi/questions/QuestionServiceTest.java index cac7c77d..a46ed90d 100644 --- a/api/src/test/java/lab/en2b/quizapi/questions/QuestionServiceTest.java +++ b/api/src/test/java/lab/en2b/quizapi/questions/QuestionServiceTest.java @@ -1,5 +1,6 @@ package lab.en2b.quizapi.questions; +import lab.en2b.quizapi.commons.exceptions.InternalApiErrorException; import lab.en2b.quizapi.questions.answer.Answer; import lab.en2b.quizapi.questions.answer.AnswerCategory; import lab.en2b.quizapi.questions.answer.AnswerRepository; @@ -103,6 +104,18 @@ void testGetRandomQuestion() { assertEquals(response.getId(), defaultResponseDto.getId()); } + @Test + void testGetRandomQuestionAnswersNotYetLoaded() { + when(questionRepository.findRandomQuestion(any(),any())).thenReturn(defaultQuestion); + defaultQuestion.setAnswers(List.of()); + QuestionResponseDto response = questionService.getRandomQuestion(""); + + assertEquals(response.getId(), defaultResponseDto.getId()); + } + @Test + void testGetRandomQuestionNoQuestionsFound() { + assertThrows(InternalApiErrorException.class,() -> questionService.getRandomQuestion("")); + } @Test void testGetQuestionById(){ From 55235405631be478d1bcac69a612de5c7f5994cf Mon Sep 17 00:00:00 2001 From: Dario Date: Fri, 12 Apr 2024 20:57:26 +0200 Subject: [PATCH 17/18] test: fixed broken test --- .../test/java/lab/en2b/quizapi/game/GameControllerTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/test/java/lab/en2b/quizapi/game/GameControllerTest.java b/api/src/test/java/lab/en2b/quizapi/game/GameControllerTest.java index 2dcac8a1..d570e709 100644 --- a/api/src/test/java/lab/en2b/quizapi/game/GameControllerTest.java +++ b/api/src/test/java/lab/en2b/quizapi/game/GameControllerTest.java @@ -187,7 +187,7 @@ void getGameDetailsShouldReturn200() throws Exception{ @Test void getQuestionCategoriesShouldReturn200() throws Exception{ - mockMvc.perform(get("/games/questionCategories") + mockMvc.perform(get("/games/question-categories") .with(user("test").roles("user")) .contentType("application/json") .with(csrf())) @@ -196,7 +196,7 @@ void getQuestionCategoriesShouldReturn200() throws Exception{ @Test void getQuestionCategoriesShouldReturn403() throws Exception{ - mockMvc.perform(get("/games/questionCategories") + mockMvc.perform(get("/games/question-categories") .contentType("application/json") .with(csrf())) .andExpect(status().isForbidden()); From 871ddddbac2e9151c9002ebd166288b70208ae72 Mon Sep 17 00:00:00 2001 From: Dario Date: Fri, 12 Apr 2024 21:06:13 +0200 Subject: [PATCH 18/18] refactor: moved game mode logic to utils --- .../quizapi/commons/utils/GameModeUtils.java | 57 +++++++++++++++++++ .../main/java/lab/en2b/quizapi/game/Game.java | 52 ++--------------- 2 files changed, 62 insertions(+), 47 deletions(-) create mode 100644 api/src/main/java/lab/en2b/quizapi/commons/utils/GameModeUtils.java diff --git a/api/src/main/java/lab/en2b/quizapi/commons/utils/GameModeUtils.java b/api/src/main/java/lab/en2b/quizapi/commons/utils/GameModeUtils.java new file mode 100644 index 00000000..4e6c2886 --- /dev/null +++ b/api/src/main/java/lab/en2b/quizapi/commons/utils/GameModeUtils.java @@ -0,0 +1,57 @@ +package lab.en2b.quizapi.commons.utils; + +import lab.en2b.quizapi.game.Game; +import lab.en2b.quizapi.game.GameMode; +import lab.en2b.quizapi.questions.question.QuestionCategory; + +import java.util.List; + +import static lab.en2b.quizapi.game.GameMode.KIWI_QUEST; + +public class GameModeUtils { + public static List getQuestionCategoriesForGamemode(GameMode gamemode, List questionCategoriesForCustom){ + if(gamemode == null){ + gamemode = KIWI_QUEST; + } + return switch (gamemode) { + case KIWI_QUEST -> List.of(QuestionCategory.ART, QuestionCategory.MUSIC, QuestionCategory.GEOGRAPHY); + case FOOTBALL_SHOWDOWN -> List.of(QuestionCategory.SPORTS); + case GEO_GENIUS -> List.of(QuestionCategory.GEOGRAPHY); + case VIDEOGAME_ADVENTURE -> List.of(QuestionCategory.VIDEOGAMES); + case ANCIENT_ODYSSEY -> List.of(QuestionCategory.MUSIC,QuestionCategory.ART); + case RANDOM -> List.of(QuestionCategory.values()); + case CUSTOM -> questionCategoriesForCustom; + }; + } + public static void setGamemodeParams(Game game){ + switch(game.getGamemode()){ + case KIWI_QUEST: + game.setRounds(9L); + game.setRoundDuration(30); + break; + case FOOTBALL_SHOWDOWN: + game.setRounds(9L); + game.setRoundDuration(30); + break; + case GEO_GENIUS: + game.setRounds(9L); + game.setRoundDuration(30); + break; + case VIDEOGAME_ADVENTURE: + game.setRounds(9L); + game.setRoundDuration(30); + break; + case ANCIENT_ODYSSEY: + game.setRounds(9L); + game.setRoundDuration(30); + break; + case RANDOM: + game.setRounds(9L); + game.setRoundDuration(30); + break; + default: + game.setRounds(9L); + game.setRoundDuration(30); + } + } +} diff --git a/api/src/main/java/lab/en2b/quizapi/game/Game.java b/api/src/main/java/lab/en2b/quizapi/game/Game.java index a75c9ca5..19097219 100644 --- a/api/src/main/java/lab/en2b/quizapi/game/Game.java +++ b/api/src/main/java/lab/en2b/quizapi/game/Game.java @@ -3,6 +3,7 @@ import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; import lab.en2b.quizapi.commons.user.User; +import lab.en2b.quizapi.commons.utils.GameModeUtils; import lab.en2b.quizapi.game.dtos.CustomGameDto; import lab.en2b.quizapi.questions.answer.Answer; import lab.en2b.quizapi.questions.question.Question; @@ -66,7 +67,7 @@ public Game(User user,GameMode gamemode,String lang, CustomGameDto gameDto){ if(gamemode == CUSTOM) setCustomGameMode(gameDto); else - setGamemode(gamemode); + setGameMode(gamemode); } public void newRound(Question question){ @@ -139,44 +140,12 @@ public void setCustomGameMode(CustomGameDto gameDto){ this.gamemode = CUSTOM; setQuestionCategoriesForCustom(gameDto.getCategories()); } - public void setGamemode(GameMode gamemode){ + public void setGameMode(GameMode gamemode){ if(gamemode == null){ gamemode = KIWI_QUEST; } - setGamemodeParams(gamemode); - } - - private void setGamemodeParams(GameMode gamemode){ //This could be moved to a GameMode entity if we have time - switch(gamemode){ - case KIWI_QUEST: - setRounds(9L); - setRoundDuration(30); - break; - case FOOTBALL_SHOWDOWN: - setRounds(9L); - setRoundDuration(30); - break; - case GEO_GENIUS: - setRounds(9L); - setRoundDuration(30); - break; - case VIDEOGAME_ADVENTURE: - setRounds(9L); - setRoundDuration(30); - break; - case ANCIENT_ODYSSEY: - setRounds(9L); - setRoundDuration(30); - break; - case RANDOM: - setRounds(9L); - setRoundDuration(30); - break; - default: - setRounds(9L); - setRoundDuration(30); - } this.gamemode = gamemode; + GameModeUtils.setGamemodeParams(this); } public void setQuestionCategoriesForCustom(List questionCategoriesForCustom) { @@ -188,18 +157,7 @@ public void setQuestionCategoriesForCustom(List questionCatego } public List getQuestionCategoriesForGamemode(){ - if(gamemode == null){ - gamemode = KIWI_QUEST; - } - return switch (gamemode) { - case KIWI_QUEST -> List.of(QuestionCategory.ART, QuestionCategory.MUSIC, QuestionCategory.GEOGRAPHY); - case FOOTBALL_SHOWDOWN -> List.of(QuestionCategory.SPORTS); - case GEO_GENIUS -> List.of(QuestionCategory.GEOGRAPHY); - case VIDEOGAME_ADVENTURE -> List.of(QuestionCategory.VIDEOGAMES); - case ANCIENT_ODYSSEY -> List.of(QuestionCategory.MUSIC,QuestionCategory.ART); - case RANDOM -> List.of(QuestionCategory.values()); - case CUSTOM -> questionCategoriesForCustom; - }; + return GameModeUtils.getQuestionCategoriesForGamemode(gamemode,questionCategoriesForCustom); } private boolean isLanguageSupported(String language) { return language.equals("en") || language.equals("es");