From f29693015c8fa9092fb75280245b2091d1695863 Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Thu, 17 Jun 2021 17:38:35 -0700 Subject: [PATCH 01/39] Update the timer task setup. --- .../main/service/GameManagementService.java | 12 ++++++++++-- .../codejoust/main/util/EndGameTimerTask.java | 10 +++++++++- .../main/task/EndGameTimerTaskTests.java | 16 ++++++++++------ 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/codejoust/main/service/GameManagementService.java b/src/main/java/com/codejoust/main/service/GameManagementService.java index 96a9bca0e..104ba9b68 100644 --- a/src/main/java/com/codejoust/main/service/GameManagementService.java +++ b/src/main/java/com/codejoust/main/service/GameManagementService.java @@ -174,7 +174,7 @@ public void setStartGameTimer(Game game, Long duration) { game.setGameTimer(gameTimer); // Schedule the game to end after seconds. - EndGameTimerTask endGameTimerTask = new EndGameTimerTask(socketService, game); + EndGameTimerTask endGameTimerTask = new EndGameTimerTask(this, socketService, game); gameTimer.getTimer().schedule(endGameTimerTask, duration * 1000); } @@ -283,7 +283,7 @@ public GameDto manuallyEndGame(String roomId, EndGameRequest request) { return gameDto; } - protected void handleEndGame(Game game) { + public void handleEndGame(Game game) { // Cancel all previously scheduled timers GameTimer gameTimer = game.getGameTimer(); gameTimer.getTimer().cancel(); @@ -291,6 +291,14 @@ protected void handleEndGame(Game game) { for (Timer timer : gameTimer.getNotificationTimers()) { timer.cancel(); } + + // Create new game report based on all information + // TODO: Wait for existing submission calls to complete...? + createGameReport(); + } + + protected void createGameReport() { + } protected boolean isGameOver(Game game) { diff --git a/src/main/java/com/codejoust/main/util/EndGameTimerTask.java b/src/main/java/com/codejoust/main/util/EndGameTimerTask.java index 1249baf3e..438b3e337 100644 --- a/src/main/java/com/codejoust/main/util/EndGameTimerTask.java +++ b/src/main/java/com/codejoust/main/util/EndGameTimerTask.java @@ -7,15 +7,20 @@ import com.codejoust.main.exception.TimerError; import com.codejoust.main.exception.api.ApiException; import com.codejoust.main.game_object.Game; +import com.codejoust.main.service.GameManagementService; import com.codejoust.main.service.SocketService; public class EndGameTimerTask extends TimerTask { private final Game game; + private final GameManagementService gameManagementService; + private final SocketService socketService; - public EndGameTimerTask(SocketService socketService, Game game) { + public EndGameTimerTask(GameManagementService gameManagementService, + SocketService socketService, Game game) { + this.gameManagementService = gameManagementService; this.socketService = socketService; this.game = game; @@ -33,6 +38,9 @@ public void run() { // Get the Game DTO and send the relevant socket update. GameDto gameDto = GameMapper.toDto(game); socketService.sendSocketUpdate(gameDto); + + // Create the game report. + gameManagementService.handleEndGame(game); } } diff --git a/src/test/java/com/codejoust/main/task/EndGameTimerTaskTests.java b/src/test/java/com/codejoust/main/task/EndGameTimerTaskTests.java index 1dcad45d3..d69b69250 100644 --- a/src/test/java/com/codejoust/main/task/EndGameTimerTaskTests.java +++ b/src/test/java/com/codejoust/main/task/EndGameTimerTaskTests.java @@ -14,6 +14,7 @@ import com.codejoust.main.model.Room; import com.codejoust.main.model.User; import com.codejoust.main.model.problem.ProblemDifficulty; +import com.codejoust.main.service.GameManagementService; import com.codejoust.main.service.SocketService; import com.codejoust.main.util.EndGameTimerTask; @@ -28,6 +29,9 @@ @ExtendWith(MockitoExtension.class) public class EndGameTimerTaskTests { + @Mock + private GameManagementService gameManagementService; + @Mock private SocketService socketService; @@ -38,7 +42,7 @@ public void setup() throws Exception { @Test public void endGameTimerTaskSocketMessageNullGame() { - assertThrows(ApiException.class, () -> new EndGameTimerTask(socketService, null)); + assertThrows(ApiException.class, () -> new EndGameTimerTask(gameManagementService, socketService, null)); } @Test @@ -58,7 +62,7 @@ public void endGameTimerTaskSocketMessageNullSocketService() { GameTimer gameTimer = new GameTimer(10L); game.setGameTimer(gameTimer); - assertThrows(ApiException.class, () -> new EndGameTimerTask(null, game)); + assertThrows(ApiException.class, () -> new EndGameTimerTask(gameManagementService, null, game)); } @Test @@ -76,7 +80,7 @@ public void endGameTimerTaskSocketMessageNullGameTimer() { Game game = GameMapper.fromRoom(room); - assertThrows(ApiException.class, () -> new EndGameTimerTask(socketService, game)); + assertThrows(ApiException.class, () -> new EndGameTimerTask(gameManagementService, socketService, game)); } @Test @@ -85,7 +89,7 @@ public void endGameTimerTaskSocketMessageNullRoom() { GameTimer gameTimer = new GameTimer(10L); game.setGameTimer(gameTimer); - assertThrows(ApiException.class, () -> new EndGameTimerTask(socketService, game)); + assertThrows(ApiException.class, () -> new EndGameTimerTask(gameManagementService, socketService, game)); } @Test @@ -104,7 +108,7 @@ public void endGameTimerTaskSocketMessageNullRoomId() { GameTimer gameTimer = new GameTimer(10L); game.setGameTimer(gameTimer); - assertThrows(ApiException.class, () -> new EndGameTimerTask(socketService, game)); + assertThrows(ApiException.class, () -> new EndGameTimerTask(gameManagementService, socketService, game)); } @Test @@ -130,7 +134,7 @@ public void endGameTimerTaskSocketMessage() { MockitoAnnotations.initMocks(this); - EndGameTimerTask endGameTimerTask = new EndGameTimerTask(socketService, game); + EndGameTimerTask endGameTimerTask = new EndGameTimerTask(gameManagementService, socketService, game); gameTimer.getTimer().schedule(endGameTimerTask, 1000L); /** From a2164a3648e7e8477d8f6d4dd255434bde7705ae Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Fri, 18 Jun 2021 20:05:53 -0700 Subject: [PATCH 02/39] Add createGameReport method and update corresponding data models. --- .../main/dto/game/SubmissionMapper.java | 18 +++++++ .../com/codejoust/main/model/Account.java | 4 ++ .../java/com/codejoust/main/model/User.java | 4 ++ .../main/model/report/GameReport.java | 14 ++++- .../model/report/SubmissionGroupReport.java | 12 +++++ .../main/service/GameManagementService.java | 52 ++++++++++++++++++- 6 files changed, 100 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/codejoust/main/dto/game/SubmissionMapper.java b/src/main/java/com/codejoust/main/dto/game/SubmissionMapper.java index 0996d686b..ea07ea296 100644 --- a/src/main/java/com/codejoust/main/dto/game/SubmissionMapper.java +++ b/src/main/java/com/codejoust/main/dto/game/SubmissionMapper.java @@ -1,7 +1,11 @@ package com.codejoust.main.dto.game; import com.codejoust.main.dto.problem.ProblemTestCaseDto; +import com.codejoust.main.game_object.PlayerCode; +import com.codejoust.main.game_object.Submission; import com.codejoust.main.game_object.SubmissionResult; +import com.codejoust.main.model.report.CodeLanguage; +import com.codejoust.main.model.report.SubmissionReport; import org.modelmapper.ModelMapper; @@ -29,4 +33,18 @@ public static SubmissionResult toSubmissionResult(TesterResult testerResult, Pro submissionResult.setInput(testCaseDto.getInput()); return submissionResult; } + + public static SubmissionReport toSubmissionReport(Submission submission) { + if (submission == null) { + return null; + } + + SubmissionReport submissionReport = mapper.map(submission, SubmissionReport.class); + + // Get and set the player code for this submission. + PlayerCode playerCode = submission.getPlayerCode(); + submissionReport.setCode(playerCode.getCode()); + submissionReport.setLanguage(CodeLanguage.fromString(submission.getPlayerCode().getLanguage().name())); + return submissionReport; + } } diff --git a/src/main/java/com/codejoust/main/model/Account.java b/src/main/java/com/codejoust/main/model/Account.java index 3c0f6413a..f28941fb8 100644 --- a/src/main/java/com/codejoust/main/model/Account.java +++ b/src/main/java/com/codejoust/main/model/Account.java @@ -60,4 +60,8 @@ public class Account { @Enumerated(EnumType.STRING) private AccountRole role = AccountRole.TEACHER; + + public void addGameReport(GameReport gameReport) { + gameReports.add(0, gameReport); + } } diff --git a/src/main/java/com/codejoust/main/model/User.java b/src/main/java/com/codejoust/main/model/User.java index 8cc447beb..235e287e4 100644 --- a/src/main/java/com/codejoust/main/model/User.java +++ b/src/main/java/com/codejoust/main/model/User.java @@ -64,4 +64,8 @@ public class User { @OneToMany(mappedBy = "user", fetch = FetchType.EAGER) @Setter(AccessLevel.PRIVATE) private List submissionGroupReports = new ArrayList<>(); + + public void addSubmissionGroupReport(SubmissionGroupReport submissionGroupReport) { + submissionGroupReports.add(submissionGroupReport); + } } diff --git a/src/main/java/com/codejoust/main/model/report/GameReport.java b/src/main/java/com/codejoust/main/model/report/GameReport.java index 374ac68a9..b88521bf8 100644 --- a/src/main/java/com/codejoust/main/model/report/GameReport.java +++ b/src/main/java/com/codejoust/main/model/report/GameReport.java @@ -3,6 +3,7 @@ import java.time.Instant; import java.util.ArrayList; import java.util.List; +import java.util.UUID; import javax.persistence.Entity; import javax.persistence.FetchType; @@ -32,13 +33,18 @@ public class GameReport { @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; - private String gameReportId; + @EqualsAndHashCode.Include + private String gameReportId = UUID.randomUUID().toString(); + // TODO: What if a problem is deleted? What if that happens during a game? @OneToMany(mappedBy = "gameReport", fetch = FetchType.EAGER) - @Setter(AccessLevel.PRIVATE) @Fetch(value = FetchMode.SUBSELECT) private List problems = new ArrayList<>(); + private Integer numProblems; + + private Integer numProblemTestCases; + @OneToMany(mappedBy = "gameReport", fetch = FetchType.EAGER) @Setter(AccessLevel.PRIVATE) @Fetch(value = FetchMode.SUBSELECT) @@ -52,4 +58,8 @@ public class GameReport { // How the game ended. private GameEndType gameEndType; + + public void addUser(User user) { + users.add(user); + } } diff --git a/src/main/java/com/codejoust/main/model/report/SubmissionGroupReport.java b/src/main/java/com/codejoust/main/model/report/SubmissionGroupReport.java index 43cfdca2d..21024b27b 100644 --- a/src/main/java/com/codejoust/main/model/report/SubmissionGroupReport.java +++ b/src/main/java/com/codejoust/main/model/report/SubmissionGroupReport.java @@ -31,6 +31,14 @@ public class SubmissionGroupReport { private String gameReportId; + private Integer numProblems; + + private Integer numTestCases; + + private Integer numProblemsSolved; + + private Integer numTestCasesPassed; + @OneToMany(mappedBy = "submissionGroupReport", fetch = FetchType.EAGER) @Setter(AccessLevel.PRIVATE) private List submissionReports = new ArrayList<>(); @@ -39,4 +47,8 @@ public class SubmissionGroupReport { @ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "user_table_id") private User user; + + public void addSubmissionReport(SubmissionReport submissionReport) { + submissionReports.add(submissionReport); + } } diff --git a/src/main/java/com/codejoust/main/service/GameManagementService.java b/src/main/java/com/codejoust/main/service/GameManagementService.java index 104ba9b68..dc4621647 100644 --- a/src/main/java/com/codejoust/main/service/GameManagementService.java +++ b/src/main/java/com/codejoust/main/service/GameManagementService.java @@ -13,6 +13,7 @@ import com.codejoust.main.dto.game.PlayAgainRequest; import com.codejoust.main.dto.game.StartGameRequest; import com.codejoust.main.dto.game.SubmissionDto; +import com.codejoust.main.dto.game.SubmissionMapper; import com.codejoust.main.dto.game.SubmissionRequest; import com.codejoust.main.dto.room.RoomDto; import com.codejoust.main.dto.room.RoomMapper; @@ -25,9 +26,13 @@ import com.codejoust.main.game_object.GameTimer; import com.codejoust.main.game_object.Player; import com.codejoust.main.game_object.PlayerCode; +import com.codejoust.main.game_object.Submission; +import com.codejoust.main.model.Account; import com.codejoust.main.model.Room; import com.codejoust.main.model.User; import com.codejoust.main.model.problem.Problem; +import com.codejoust.main.model.report.GameReport; +import com.codejoust.main.model.report.SubmissionGroupReport; import com.codejoust.main.util.EndGameTimerTask; import com.codejoust.main.util.Utility; @@ -294,11 +299,54 @@ public void handleEndGame(Game game) { // Create new game report based on all information // TODO: Wait for existing submission calls to complete...? - createGameReport(); + createGameReport(game); } - protected void createGameReport() { + protected void createGameReport(Game game) { + GameReport gameReport = new GameReport(); + gameReport.setProblems(game.getProblems()); + gameReport.setNumProblems(game.getProblems().size()); + + // TODO: Confirm that all the user information, particularly accounts, will always be up-to-date. + for (Player player : game.getPlayers().values()) { + // For each user, add the relevant submission info. + User user = player.getUser(); + + // Construct the new submission group report for each user. + SubmissionGroupReport submissionGroupReport = new SubmissionGroupReport(); + submissionGroupReport.setGameReportId(gameReport.getGameReportId()); + + // TODO: How should I indicate the top submissions / whether a problem was solved for each problem? + Integer numTestCases = 0; + Integer numTestCasesPassed = 0; + Integer numProblemsSolved = 0; + for (Submission submission : player.getSubmissions()) { + numTestCases += submission.getNumTestCases(); + numTestCasesPassed += submission.getNumCorrect(); + + // If the problem was solved, increment numProblemsSolved. + if (submission.getNumTestCases() == submission.getNumCorrect()) { + numProblemsSolved++; + } + submissionGroupReport.addSubmissionReport(SubmissionMapper.toSubmissionReport(submission)); + } + + // Set the problems and test cases statistics. + submissionGroupReport.setNumProblemsSolved(numProblemsSolved); + submissionGroupReport.setNumTestCases(numTestCases); + submissionGroupReport.setNumTestCasesPassed(numTestCasesPassed); + + // Add the submission group report and the user. + user.addSubmissionGroupReport(submissionGroupReport); + gameReport.addUser(user); + + // If account exists and game report is not added, add game report. + Account account = user.getAccount(); + if (account != null && !account.getGameReports().contains(gameReport)) { + account.addGameReport(gameReport); + } + } } protected boolean isGameOver(Game game) { From 5282463053a4c9ae57cdc2c587b9934288bd43f3 Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Thu, 24 Jun 2021 17:53:44 -0700 Subject: [PATCH 03/39] Add TODO and helper methods. --- .../com/codejoust/main/model/report/GameReport.java | 4 ++++ .../main/service/GameManagementService.java | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/main/java/com/codejoust/main/model/report/GameReport.java b/src/main/java/com/codejoust/main/model/report/GameReport.java index 8b78a9462..fc3b4c467 100644 --- a/src/main/java/com/codejoust/main/model/report/GameReport.java +++ b/src/main/java/com/codejoust/main/model/report/GameReport.java @@ -60,6 +60,10 @@ public class GameReport { // How the game ended. private GameEndType gameEndType; + public void addProblemContainer(ProblemContainer problemContainer) { + problemContainers.add(problemContainer); + } + public void addUser(User user) { users.add(user); } diff --git a/src/main/java/com/codejoust/main/service/GameManagementService.java b/src/main/java/com/codejoust/main/service/GameManagementService.java index 7d2c88b87..fcc936148 100644 --- a/src/main/java/com/codejoust/main/service/GameManagementService.java +++ b/src/main/java/com/codejoust/main/service/GameManagementService.java @@ -31,6 +31,7 @@ import com.codejoust.main.model.Room; import com.codejoust.main.model.User; import com.codejoust.main.model.problem.Problem; +import com.codejoust.main.model.problem.ProblemContainer; import com.codejoust.main.model.report.GameReport; import com.codejoust.main.model.report.SubmissionGroupReport; import com.codejoust.main.util.EndGameTimerTask; @@ -304,6 +305,8 @@ public void handleEndGame(Game game) { protected void createGameReport(Game game) { GameReport gameReport = new GameReport(); + // TODO: There is still an issue if problems are updated during the game. + // TODO: Create ProblemContainer class. // gameReport.setProblems(game.getProblems()); @@ -347,6 +350,15 @@ protected void createGameReport(Game game) { account.addGameReport(gameReport); } } + + // + game.getProblems().forEach((problem) -> { + ProblemContainer problemContainer = new ProblemContainer(); + problemContainer.setProblem(problem); + problemContainer.setTestCaseCount(problem.getTestCases().size()); + + // gameReport.addProblemContainer() + }); } protected boolean isGameOver(Game game) { From ddc34c15657b9fb4a9237d8da003523d9e6fb4ec Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Mon, 28 Jun 2021 19:44:33 -0700 Subject: [PATCH 04/39] Add logic to set data in submission report. --- .../main/game_object/Submission.java | 1 + .../model/report/SubmissionGroupReport.java | 7 +++- .../main/model/report/SubmissionReport.java | 2 ++ .../main/service/GameManagementService.java | 33 ++++++++++++++----- 4 files changed, 34 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/codejoust/main/game_object/Submission.java b/src/main/java/com/codejoust/main/game_object/Submission.java index fc7f4d8a9..a43cc5428 100644 --- a/src/main/java/com/codejoust/main/game_object/Submission.java +++ b/src/main/java/com/codejoust/main/game_object/Submission.java @@ -13,6 +13,7 @@ public class Submission { private PlayerCode playerCode; + private int problemIndex; private List results; diff --git a/src/main/java/com/codejoust/main/model/report/SubmissionGroupReport.java b/src/main/java/com/codejoust/main/model/report/SubmissionGroupReport.java index 05940a707..f8ff26004 100644 --- a/src/main/java/com/codejoust/main/model/report/SubmissionGroupReport.java +++ b/src/main/java/com/codejoust/main/model/report/SubmissionGroupReport.java @@ -33,7 +33,12 @@ public class SubmissionGroupReport { private Integer numTestCases; - private Integer numProblemsSolved; + /** + * String to represent problems solved. + * Each index represents the problem index. + * 0 = Not Solved, 1 = Solved. + */ + private String problemsSolved; private Integer numTestCasesPassed; diff --git a/src/main/java/com/codejoust/main/model/report/SubmissionReport.java b/src/main/java/com/codejoust/main/model/report/SubmissionReport.java index 34e022941..11c45ab85 100644 --- a/src/main/java/com/codejoust/main/model/report/SubmissionReport.java +++ b/src/main/java/com/codejoust/main/model/report/SubmissionReport.java @@ -25,6 +25,8 @@ public class SubmissionReport { private CodeLanguage language; + private int problemIndex; + // The time that the submission was received. private Instant startTime; diff --git a/src/main/java/com/codejoust/main/service/GameManagementService.java b/src/main/java/com/codejoust/main/service/GameManagementService.java index 4467fb981..9cb77ebb2 100644 --- a/src/main/java/com/codejoust/main/service/GameManagementService.java +++ b/src/main/java/com/codejoust/main/service/GameManagementService.java @@ -1,5 +1,6 @@ package com.codejoust.main.service; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -305,6 +306,7 @@ public void handleEndGame(Game game) { protected void createGameReport(Game game) { GameReport gameReport = new GameReport(); + int numProblems = game.getProblems().size(); // TODO: There is still an issue if problems are updated during the game. // TODO: Create ProblemContainer class. @@ -321,24 +323,27 @@ protected void createGameReport(Game game) { // TODO: How should I indicate the top submissions / whether a problem was solved for each problem? Integer numTestCases = 0; - Integer numTestCasesPassed = 0; - Integer numProblemsSolved = 0; + boolean[] problemsSolved = new boolean[numProblems]; + int[] testCasesPassed = new int[numProblems]; for (Submission submission : player.getSubmissions()) { + int problemIndex = submission.getProblemIndex(); numTestCases += submission.getNumTestCases(); - numTestCasesPassed += submission.getNumCorrect(); - // If the problem was solved, increment numProblemsSolved. - if (submission.getNumTestCases() == submission.getNumCorrect()) { - numProblemsSolved++; + // If the problem was solved, set boolean value to true. + if (submission.getNumTestCases() == submission.getNumCorrect() && !problemsSolved[problemIndex]) { + problemsSolved[problemIndex] = true; } + // Get the maximum number of test cases passed for each problem. + testCasesPassed[problemIndex] = Math.max(testCasesPassed[problemIndex], submission.getNumCorrect()); + submissionGroupReport.addSubmissionReport(SubmissionMapper.toSubmissionReport(submission)); } // Set the problems and test cases statistics. - submissionGroupReport.setNumProblemsSolved(numProblemsSolved); submissionGroupReport.setNumTestCases(numTestCases); - submissionGroupReport.setNumTestCasesPassed(numTestCasesPassed); + submissionGroupReport.setProblemsSolved(compactProblemsSolved(problemsSolved)); + submissionGroupReport.setNumTestCasesPassed(Arrays.stream(testCasesPassed).sum()); // Add the submission group report and the user. user.addSubmissionGroupReport(submissionGroupReport); @@ -361,6 +366,18 @@ protected void createGameReport(Game game) { }); } + private String compactProblemsSolved(boolean[] problemsSolved) { + StringBuilder builder = new StringBuilder(); + for (boolean problemSolved : problemsSolved) { + if (problemSolved) { + builder.append("1"); + } else { + builder.append("0"); + } + } + return builder.toString(); + } + protected boolean isGameOver(Game game) { return game.getGameEnded() || game.getAllSolved() || (game.getGameTimer() != null && game.getGameTimer().isTimeUp()); } From 54d81fe0a7cf26c0da5d2a501ac9937dd6e1bb10 Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Mon, 28 Jun 2021 22:44:58 -0700 Subject: [PATCH 05/39] Add problem container variable and remaining game report statistics. --- .../main/service/GameManagementService.java | 50 ++++++++++++++----- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/codejoust/main/service/GameManagementService.java b/src/main/java/com/codejoust/main/service/GameManagementService.java index 9cb77ebb2..3387430cc 100644 --- a/src/main/java/com/codejoust/main/service/GameManagementService.java +++ b/src/main/java/com/codejoust/main/service/GameManagementService.java @@ -1,5 +1,7 @@ package com.codejoust.main.service; +import java.time.Duration; +import java.time.Instant; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -33,6 +35,7 @@ import com.codejoust.main.model.User; import com.codejoust.main.model.problem.Problem; import com.codejoust.main.model.problem.ProblemContainer; +import com.codejoust.main.model.report.GameEndType; import com.codejoust.main.model.report.GameReport; import com.codejoust.main.model.report.SubmissionGroupReport; import com.codejoust.main.util.EndGameTimerTask; @@ -307,12 +310,12 @@ public void handleEndGame(Game game) { protected void createGameReport(Game game) { GameReport gameReport = new GameReport(); int numProblems = game.getProblems().size(); - // TODO: There is still an issue if problems are updated during the game. - - // TODO: Create ProblemContainer class. - // gameReport.setProblems(game.getProblems()); + int numPlayers = game.getPlayers().size(); - // TODO: Confirm that all the user information, particularly accounts, will always be up-to-date. + // Initialize the statistic variables for each problem. + int[] userSolved = new int[numProblems]; + double[] totalTestCasesPassed = new double[numProblems]; + double[] totalAttemptCount = new double[numProblems]; for (Player player : game.getPlayers().values()) { // For each user, add the relevant submission info. User user = player.getUser(); @@ -321,17 +324,19 @@ protected void createGameReport(Game game) { SubmissionGroupReport submissionGroupReport = new SubmissionGroupReport(); submissionGroupReport.setGameReportId(gameReport.getGameReportId()); - // TODO: How should I indicate the top submissions / whether a problem was solved for each problem? - Integer numTestCases = 0; + // Iterate through each submission and update group statistics. + int numTestCases = 0; boolean[] problemsSolved = new boolean[numProblems]; int[] testCasesPassed = new int[numProblems]; for (Submission submission : player.getSubmissions()) { int problemIndex = submission.getProblemIndex(); numTestCases += submission.getNumTestCases(); + totalAttemptCount[problemIndex]++; // If the problem was solved, set boolean value to true. if (submission.getNumTestCases() == submission.getNumCorrect() && !problemsSolved[problemIndex]) { problemsSolved[problemIndex] = true; + userSolved[problemIndex]++; } // Get the maximum number of test cases passed for each problem. @@ -339,6 +344,11 @@ protected void createGameReport(Game game) { submissionGroupReport.addSubmissionReport(SubmissionMapper.toSubmissionReport(submission)); } + + // Iterate through the test cases passed to add to the game total. + for (int i = 0; i < testCasesPassed.length; i++) { + totalTestCasesPassed[i] += testCasesPassed[i]; + } // Set the problems and test cases statistics. submissionGroupReport.setNumTestCases(numTestCases); @@ -356,14 +366,30 @@ protected void createGameReport(Game game) { } } - // - game.getProblems().forEach((problem) -> { + // Set problem container variables. + List problems = game.getProblems(); + for (int i = 0; i < numProblems; i++) { + Problem problem = problems.get(i); ProblemContainer problemContainer = new ProblemContainer(); problemContainer.setProblem(problem); + problemContainer.setUserSolvedCount(userSolved[i]); problemContainer.setTestCaseCount(problem.getTestCases().size()); - - // gameReport.addProblemContainer() - }); + problemContainer.setAverageTestCasesPassed(totalTestCasesPassed[i] / problem.getTestCases().size()); + problemContainer.setAverageAttemptCount(totalAttemptCount[i] / numPlayers); + gameReport.addProblemContainer(problemContainer); + } + + Instant startTime = game.getGameTimer().getStartTime(); + gameReport.setCreatedDateTime(startTime); + gameReport.setDuration(Duration.between(startTime, Instant.now()).getSeconds()); + + if (game.getGameEnded()) { + gameReport.setGameEndType(GameEndType.MANUAL_END); + } else if (game.getAllSolved()) { + gameReport.setGameEndType(GameEndType.ALL_SOLVED); + } else { + gameReport.setGameEndType(GameEndType.TIME_UP); + } } private String compactProblemsSolved(boolean[] problemsSolved) { From 7f5d22a2daa4022dbd376309d4f7ead1c744b17a Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Mon, 28 Jun 2021 23:07:39 -0700 Subject: [PATCH 06/39] Delay before creating the game report. --- .../codejoust/main/service/GameManagementService.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/codejoust/main/service/GameManagementService.java b/src/main/java/com/codejoust/main/service/GameManagementService.java index 3387430cc..9b8dcd7e0 100644 --- a/src/main/java/com/codejoust/main/service/GameManagementService.java +++ b/src/main/java/com/codejoust/main/service/GameManagementService.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Map; import java.util.Timer; +import java.util.concurrent.TimeUnit; import com.codejoust.main.dao.RoomRepository; import com.codejoust.main.dto.game.EndGameRequest; @@ -302,8 +303,12 @@ public void handleEndGame(Game game) { timer.cancel(); } - // Create new game report based on all information - // TODO: Wait for existing submission calls to complete...? + // After one minute (wait for existing submissions) create game report. + try { + TimeUnit.MINUTES.sleep(1); + } catch (InterruptedException e) { + log.info(e.toString()); + } createGameReport(game); } From 55301218735906a1ad2df0747d6224cf381d144b Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Mon, 28 Jun 2021 23:38:38 -0700 Subject: [PATCH 07/39] Add game report repository and save the object in the database. --- .../com/codejoust/main/dao/GameReportRepository.java | 10 ++++++++++ .../codejoust/main/service/GameManagementService.java | 10 +++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/codejoust/main/dao/GameReportRepository.java diff --git a/src/main/java/com/codejoust/main/dao/GameReportRepository.java b/src/main/java/com/codejoust/main/dao/GameReportRepository.java new file mode 100644 index 000000000..6127a7bd0 --- /dev/null +++ b/src/main/java/com/codejoust/main/dao/GameReportRepository.java @@ -0,0 +1,10 @@ +package com.codejoust.main.dao; + +import com.codejoust.main.model.report.GameReport; + +import org.springframework.data.repository.CrudRepository; + +public interface GameReportRepository extends CrudRepository { + + GameReport findGameReportByGameReportId(String gameReportId); +} diff --git a/src/main/java/com/codejoust/main/service/GameManagementService.java b/src/main/java/com/codejoust/main/service/GameManagementService.java index 9b8dcd7e0..2ec944c22 100644 --- a/src/main/java/com/codejoust/main/service/GameManagementService.java +++ b/src/main/java/com/codejoust/main/service/GameManagementService.java @@ -9,6 +9,7 @@ import java.util.Timer; import java.util.concurrent.TimeUnit; +import com.codejoust.main.dao.GameReportRepository; import com.codejoust.main.dao.RoomRepository; import com.codejoust.main.dto.game.EndGameRequest; import com.codejoust.main.dto.game.GameDto; @@ -51,6 +52,7 @@ public class GameManagementService { private final RoomRepository repository; + private final GameReportRepository gameReportRepository; private final SocketService socketService; private final LiveGameService liveGameService; private final NotificationService notificationService; @@ -59,10 +61,11 @@ public class GameManagementService { private final Map currentGameMap; @Autowired - protected GameManagementService(RoomRepository repository, SocketService socketService, + protected GameManagementService(RoomRepository repository, GameReportRepository gameReportRepository, SocketService socketService, LiveGameService liveGameService, NotificationService notificationService, SubmitService submitService, ProblemService problemService) { this.repository = repository; + this.gameReportRepository = gameReportRepository; this.socketService = socketService; this.liveGameService = liveGameService; this.notificationService = notificationService; @@ -363,7 +366,10 @@ protected void createGameReport(Game game) { // Add the submission group report and the user. user.addSubmissionGroupReport(submissionGroupReport); gameReport.addUser(user); + } + // Iterate through all room users, players and spectators included. + for (User user : game.getRoom().getUsers()) { // If account exists and game report is not added, add game report. Account account = user.getAccount(); if (account != null && !account.getGameReports().contains(gameReport)) { @@ -395,6 +401,8 @@ protected void createGameReport(Game game) { } else { gameReport.setGameEndType(GameEndType.TIME_UP); } + + gameReportRepository.save(gameReport); } private String compactProblemsSolved(boolean[] problemsSolved) { From 913cf382dd790af3d09cf40a27215df0cba01491 Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Tue, 29 Jun 2021 09:02:59 -0700 Subject: [PATCH 08/39] Update the saving functionality for users and accounts. --- .../com/codejoust/main/model/Account.java | 2 +- .../java/com/codejoust/main/model/User.java | 3 +- .../main/model/report/GameReport.java | 6 +- .../model/report/SubmissionGroupReport.java | 3 +- .../main/service/GameManagementService.java | 57 +++++++++++++------ 5 files changed, 48 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/codejoust/main/model/Account.java b/src/main/java/com/codejoust/main/model/Account.java index f28941fb8..300e717f7 100644 --- a/src/main/java/com/codejoust/main/model/Account.java +++ b/src/main/java/com/codejoust/main/model/Account.java @@ -52,7 +52,7 @@ public class Account { private List problemTags = new ArrayList<>(); // List of tags associated with this problem - @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH, CascadeType.DETACH}, fetch = FetchType.EAGER) + @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) @Setter(AccessLevel.PRIVATE) @JoinColumn(name = "game_report_id") @Fetch(value = FetchMode.SUBSELECT) diff --git a/src/main/java/com/codejoust/main/model/User.java b/src/main/java/com/codejoust/main/model/User.java index e57a45b38..a6f69ea4e 100644 --- a/src/main/java/com/codejoust/main/model/User.java +++ b/src/main/java/com/codejoust/main/model/User.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.List; +import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; @@ -58,7 +59,7 @@ public class User { private Room room; // Thie list holds the submission group associated with the current room - @OneToMany(fetch = FetchType.EAGER) + @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) @Setter(AccessLevel.PRIVATE) @Fetch(value = FetchMode.SUBSELECT) @JoinColumn(name = "submission_group_reports_table_id") diff --git a/src/main/java/com/codejoust/main/model/report/GameReport.java b/src/main/java/com/codejoust/main/model/report/GameReport.java index fc3b4c467..2fd9ceea0 100644 --- a/src/main/java/com/codejoust/main/model/report/GameReport.java +++ b/src/main/java/com/codejoust/main/model/report/GameReport.java @@ -39,16 +39,16 @@ public class GameReport { @EqualsAndHashCode.Include private String gameReportId = UUID.randomUUID().toString(); - @OneToMany(fetch = FetchType.EAGER) + @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) @Setter(AccessLevel.PRIVATE) @Fetch(value = FetchMode.SUBSELECT) @JoinColumn(name = "problem_containers_table_id") private List problemContainers = new ArrayList<>(); - @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH, CascadeType.DETACH}, fetch = FetchType.EAGER) + @ManyToMany(cascade = CascadeType.MERGE, fetch = FetchType.EAGER) @Setter(AccessLevel.PRIVATE) - @JoinColumn(name = "users_table_id") @Fetch(value = FetchMode.SUBSELECT) + @JoinColumn(name = "users_table_id") private List users = new ArrayList<>(); // The start time of the game diff --git a/src/main/java/com/codejoust/main/model/report/SubmissionGroupReport.java b/src/main/java/com/codejoust/main/model/report/SubmissionGroupReport.java index f8ff26004..36dd88a08 100644 --- a/src/main/java/com/codejoust/main/model/report/SubmissionGroupReport.java +++ b/src/main/java/com/codejoust/main/model/report/SubmissionGroupReport.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.List; +import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; @@ -42,7 +43,7 @@ public class SubmissionGroupReport { private Integer numTestCasesPassed; - @OneToMany(fetch = FetchType.EAGER) + @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) @Setter(AccessLevel.PRIVATE) @Fetch(value = FetchMode.SUBSELECT) @JoinColumn(name = "submission_reports_table_id") diff --git a/src/main/java/com/codejoust/main/service/GameManagementService.java b/src/main/java/com/codejoust/main/service/GameManagementService.java index 2ec944c22..27bc12090 100644 --- a/src/main/java/com/codejoust/main/service/GameManagementService.java +++ b/src/main/java/com/codejoust/main/service/GameManagementService.java @@ -4,13 +4,17 @@ import java.time.Instant; import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.Timer; import java.util.concurrent.TimeUnit; +import com.codejoust.main.dao.AccountRepository; import com.codejoust.main.dao.GameReportRepository; import com.codejoust.main.dao.RoomRepository; +import com.codejoust.main.dao.UserRepository; import com.codejoust.main.dto.game.EndGameRequest; import com.codejoust.main.dto.game.GameDto; import com.codejoust.main.dto.game.GameMapper; @@ -53,6 +57,8 @@ public class GameManagementService { private final RoomRepository repository; private final GameReportRepository gameReportRepository; + private final UserRepository userRepository; + private final AccountRepository accountRepository; private final SocketService socketService; private final LiveGameService liveGameService; private final NotificationService notificationService; @@ -61,11 +67,19 @@ public class GameManagementService { private final Map currentGameMap; @Autowired - protected GameManagementService(RoomRepository repository, GameReportRepository gameReportRepository, SocketService socketService, - LiveGameService liveGameService, NotificationService notificationService, - SubmitService submitService, ProblemService problemService) { + protected GameManagementService(RoomRepository repository, + GameReportRepository gameReportRepository, + UserRepository userRepository, + AccountRepository accountRepository, + SocketService socketService, + LiveGameService liveGameService, + NotificationService notificationService, + SubmitService submitService, + ProblemService problemService) { this.repository = repository; this.gameReportRepository = gameReportRepository; + this.userRepository = userRepository; + this.accountRepository = accountRepository; this.socketService = socketService; this.liveGameService = liveGameService; this.notificationService = notificationService; @@ -307,12 +321,15 @@ public void handleEndGame(Game game) { } // After one minute (wait for existing submissions) create game report. - try { - TimeUnit.MINUTES.sleep(1); - } catch (InterruptedException e) { - log.info(e.toString()); - } + log.info("One minute delay"); + // try { + // TimeUnit.MINUTES.sleep(1); + // } catch (InterruptedException e) { + // log.info(e.toString()); + // } + log.info("Create game report"); createGameReport(game); + log.info("Finished game report"); } protected void createGameReport(Game game) { @@ -368,15 +385,6 @@ protected void createGameReport(Game game) { gameReport.addUser(user); } - // Iterate through all room users, players and spectators included. - for (User user : game.getRoom().getUsers()) { - // If account exists and game report is not added, add game report. - Account account = user.getAccount(); - if (account != null && !account.getGameReports().contains(gameReport)) { - account.addGameReport(gameReport); - } - } - // Set problem container variables. List problems = game.getProblems(); for (int i = 0; i < numProblems; i++) { @@ -402,7 +410,22 @@ protected void createGameReport(Game game) { gameReport.setGameEndType(GameEndType.TIME_UP); } + log.info("Save game report"); gameReportRepository.save(gameReport); + + // Iterate through all room users, players and spectators included. + Set addedAccounts = new HashSet<>(); + for (User user : game.getRoom().getUsers()) { + // If account exists and game report is not added, add game report. + Account account = user.getAccount(); + if (account != null && !addedAccounts.contains(account)) { + account.addGameReport(gameReport); + addedAccounts.add(account); + accountRepository.save(account); + } else if (account == null) { + userRepository.save(user); + } + } } private String compactProblemsSolved(boolean[] problemsSolved) { From 15aa1edd3e6394b5286fb2c3f721d23f9f200fab Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Tue, 29 Jun 2021 18:18:06 -0700 Subject: [PATCH 09/39] Update column definitions to text. --- .../com/codejoust/main/model/problem/ProblemTestCase.java | 4 ++++ .../com/codejoust/main/model/report/SubmissionReport.java | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/main/java/com/codejoust/main/model/problem/ProblemTestCase.java b/src/main/java/com/codejoust/main/model/problem/ProblemTestCase.java index 0043ac49a..ae64ae830 100644 --- a/src/main/java/com/codejoust/main/model/problem/ProblemTestCase.java +++ b/src/main/java/com/codejoust/main/model/problem/ProblemTestCase.java @@ -4,6 +4,7 @@ import lombok.Getter; import lombok.Setter; +import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; @@ -24,9 +25,11 @@ public class ProblemTestCase { private Integer id; @EqualsAndHashCode.Include + @Column(columnDefinition = "TEXT") private String input; @EqualsAndHashCode.Include + @Column(columnDefinition = "TEXT") private String output; @EqualsAndHashCode.Include @@ -37,5 +40,6 @@ public class ProblemTestCase { private Problem problem; @EqualsAndHashCode.Include + @Column(columnDefinition = "TEXT") private String explanation; } diff --git a/src/main/java/com/codejoust/main/model/report/SubmissionReport.java b/src/main/java/com/codejoust/main/model/report/SubmissionReport.java index 11c45ab85..fac00139b 100644 --- a/src/main/java/com/codejoust/main/model/report/SubmissionReport.java +++ b/src/main/java/com/codejoust/main/model/report/SubmissionReport.java @@ -2,6 +2,7 @@ import java.time.Instant; +import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; @@ -21,6 +22,7 @@ public class SubmissionReport { @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; + @Column(columnDefinition = "TEXT") private String code; private CodeLanguage language; From cc3a0d484ce0aae079792f3e9be42224bfa8086d Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Tue, 29 Jun 2021 19:16:21 -0700 Subject: [PATCH 10/39] Move utility tasks to the task folder. --- .../java/com/codejoust/main/service/GameManagementService.java | 2 +- .../java/com/codejoust/main/service/NotificationService.java | 2 +- .../com/codejoust/main/{util => task}/EndGameTimerTask.java | 2 +- .../codejoust/main/{util => task}/NotificationTimerTask.java | 2 +- .../java/com/codejoust/main/task/EndGameTimerTaskTests.java | 2 -- .../com/codejoust/main/task/NotificationTimerTaskTests.java | 1 - 6 files changed, 4 insertions(+), 7 deletions(-) rename src/main/java/com/codejoust/main/{util => task}/EndGameTimerTask.java (97%) rename src/main/java/com/codejoust/main/{util => task}/NotificationTimerTask.java (97%) diff --git a/src/main/java/com/codejoust/main/service/GameManagementService.java b/src/main/java/com/codejoust/main/service/GameManagementService.java index 27bc12090..8915f9c87 100644 --- a/src/main/java/com/codejoust/main/service/GameManagementService.java +++ b/src/main/java/com/codejoust/main/service/GameManagementService.java @@ -44,7 +44,7 @@ import com.codejoust.main.model.report.GameEndType; import com.codejoust.main.model.report.GameReport; import com.codejoust.main.model.report.SubmissionGroupReport; -import com.codejoust.main.util.EndGameTimerTask; +import com.codejoust.main.task.EndGameTimerTask; import com.codejoust.main.util.Utility; import lombok.extern.log4j.Log4j2; diff --git a/src/main/java/com/codejoust/main/service/NotificationService.java b/src/main/java/com/codejoust/main/service/NotificationService.java index 4b9a5d8ac..5f24d3d9e 100644 --- a/src/main/java/com/codejoust/main/service/NotificationService.java +++ b/src/main/java/com/codejoust/main/service/NotificationService.java @@ -6,7 +6,7 @@ import com.codejoust.main.dto.game.GameNotificationDto; import com.codejoust.main.game_object.Game; import com.codejoust.main.game_object.GameTimer; -import com.codejoust.main.util.NotificationTimerTask; +import com.codejoust.main.task.NotificationTimerTask; import org.springframework.stereotype.Service; diff --git a/src/main/java/com/codejoust/main/util/EndGameTimerTask.java b/src/main/java/com/codejoust/main/task/EndGameTimerTask.java similarity index 97% rename from src/main/java/com/codejoust/main/util/EndGameTimerTask.java rename to src/main/java/com/codejoust/main/task/EndGameTimerTask.java index 438b3e337..143e77ffe 100644 --- a/src/main/java/com/codejoust/main/util/EndGameTimerTask.java +++ b/src/main/java/com/codejoust/main/task/EndGameTimerTask.java @@ -1,4 +1,4 @@ -package com.codejoust.main.util; +package com.codejoust.main.task; import java.util.TimerTask; diff --git a/src/main/java/com/codejoust/main/util/NotificationTimerTask.java b/src/main/java/com/codejoust/main/task/NotificationTimerTask.java similarity index 97% rename from src/main/java/com/codejoust/main/util/NotificationTimerTask.java rename to src/main/java/com/codejoust/main/task/NotificationTimerTask.java index cacb85136..210de4b8a 100644 --- a/src/main/java/com/codejoust/main/util/NotificationTimerTask.java +++ b/src/main/java/com/codejoust/main/task/NotificationTimerTask.java @@ -1,4 +1,4 @@ -package com.codejoust.main.util; +package com.codejoust.main.task; import java.time.Instant; import java.util.TimerTask; diff --git a/src/test/java/com/codejoust/main/task/EndGameTimerTaskTests.java b/src/test/java/com/codejoust/main/task/EndGameTimerTaskTests.java index d69b69250..859148315 100644 --- a/src/test/java/com/codejoust/main/task/EndGameTimerTaskTests.java +++ b/src/test/java/com/codejoust/main/task/EndGameTimerTaskTests.java @@ -16,8 +16,6 @@ import com.codejoust.main.model.problem.ProblemDifficulty; import com.codejoust.main.service.GameManagementService; import com.codejoust.main.service.SocketService; - -import com.codejoust.main.util.EndGameTimerTask; import com.codejoust.main.util.TestFields; import org.junit.Test; import org.junit.jupiter.api.BeforeEach; diff --git a/src/test/java/com/codejoust/main/task/NotificationTimerTaskTests.java b/src/test/java/com/codejoust/main/task/NotificationTimerTaskTests.java index ad85128ff..f2e7cc95b 100644 --- a/src/test/java/com/codejoust/main/task/NotificationTimerTaskTests.java +++ b/src/test/java/com/codejoust/main/task/NotificationTimerTaskTests.java @@ -13,7 +13,6 @@ import com.codejoust.main.game_object.NotificationType; import com.codejoust.main.service.SocketService; -import com.codejoust.main.util.NotificationTimerTask; import org.junit.Test; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.ExtendWith; From 56a73eceb6bb46f9eb197cc88aef9b097ba4f6d9 Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Tue, 29 Jun 2021 19:58:32 -0700 Subject: [PATCH 11/39] Add the timer task for creating a game report. --- .../codejoust/main/exception/TimerError.java | 2 +- .../main/service/GameManagementService.java | 21 ++++++------ .../main/task/CreateGameReportTask.java | 32 +++++++++++++++++++ 3 files changed, 42 insertions(+), 13 deletions(-) create mode 100644 src/main/java/com/codejoust/main/task/CreateGameReportTask.java diff --git a/src/main/java/com/codejoust/main/exception/TimerError.java b/src/main/java/com/codejoust/main/exception/TimerError.java index b36ab0854..150533f87 100644 --- a/src/main/java/com/codejoust/main/exception/TimerError.java +++ b/src/main/java/com/codejoust/main/exception/TimerError.java @@ -11,7 +11,7 @@ public enum TimerError implements ApiError { INVALID_DURATION(HttpStatus.BAD_REQUEST, "Please enter a valid duration between 1-60 minutes."), - NULL_SETTING(HttpStatus.BAD_REQUEST, "The game, associated game timer, room, room ID, and socket service must not be null."); + NULL_SETTING(HttpStatus.BAD_REQUEST, "The relevant game settings must not be null."); private final HttpStatus status; private final ApiErrorResponse response; diff --git a/src/main/java/com/codejoust/main/service/GameManagementService.java b/src/main/java/com/codejoust/main/service/GameManagementService.java index 8915f9c87..37a0c56db 100644 --- a/src/main/java/com/codejoust/main/service/GameManagementService.java +++ b/src/main/java/com/codejoust/main/service/GameManagementService.java @@ -9,7 +9,6 @@ import java.util.Map; import java.util.Set; import java.util.Timer; -import java.util.concurrent.TimeUnit; import com.codejoust.main.dao.AccountRepository; import com.codejoust.main.dao.GameReportRepository; @@ -44,6 +43,7 @@ import com.codejoust.main.model.report.GameEndType; import com.codejoust.main.model.report.GameReport; import com.codejoust.main.model.report.SubmissionGroupReport; +import com.codejoust.main.task.CreateGameReportTask; import com.codejoust.main.task.EndGameTimerTask; import com.codejoust.main.util.Utility; @@ -320,19 +320,15 @@ public void handleEndGame(Game game) { timer.cancel(); } - // After one minute (wait for existing submissions) create game report. - log.info("One minute delay"); - // try { - // TimeUnit.MINUTES.sleep(1); - // } catch (InterruptedException e) { - // log.info(e.toString()); - // } - log.info("Create game report"); - createGameReport(game); - log.info("Finished game report"); + log.info("Create game report timer"); + CreateGameReportTask createGameReportTask = new CreateGameReportTask(this, game); + Timer createGameReportTimer = new Timer(); + createGameReportTimer.schedule(createGameReportTask, GameTimer.DURATION_1 * 1000); + log.info("Passed game report timer"); } - protected void createGameReport(Game game) { + public void createGameReport(Game game) { + log.info("Started to create game report"); GameReport gameReport = new GameReport(); int numProblems = game.getProblems().size(); int numPlayers = game.getPlayers().size(); @@ -426,6 +422,7 @@ protected void createGameReport(Game game) { userRepository.save(user); } } + log.info("Create game report"); } private String compactProblemsSolved(boolean[] problemsSolved) { diff --git a/src/main/java/com/codejoust/main/task/CreateGameReportTask.java b/src/main/java/com/codejoust/main/task/CreateGameReportTask.java new file mode 100644 index 000000000..81d43873c --- /dev/null +++ b/src/main/java/com/codejoust/main/task/CreateGameReportTask.java @@ -0,0 +1,32 @@ +package com.codejoust.main.task; + +import java.util.TimerTask; + +import com.codejoust.main.exception.TimerError; +import com.codejoust.main.exception.api.ApiException; +import com.codejoust.main.game_object.Game; +import com.codejoust.main.service.GameManagementService; + +public class CreateGameReportTask extends TimerTask { + + private final Game game; + + private final GameManagementService gameManagementService; + + public CreateGameReportTask(GameManagementService gameManagementService, Game game) { + this.gameManagementService = gameManagementService; + this.game = game; + + // Handle potential errors for run(). + if (game == null) { + throw new ApiException(TimerError.NULL_SETTING); + } + } + + @Override + public void run() { + // Create the game report (is the game updated here?). + gameManagementService.createGameReport(game); + } + +} From 0fbd91992833eee1769f84c37bf2d4db50ca99e0 Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Tue, 29 Jun 2021 20:18:38 -0700 Subject: [PATCH 12/39] Fix average test cases passed and total number of test cases. --- .../java/com/codejoust/main/model/report/GameReport.java | 2 ++ .../main/model/report/SubmissionGroupReport.java | 2 -- .../codejoust/main/service/GameManagementService.java | 9 +++++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/codejoust/main/model/report/GameReport.java b/src/main/java/com/codejoust/main/model/report/GameReport.java index 2fd9ceea0..34923f7fe 100644 --- a/src/main/java/com/codejoust/main/model/report/GameReport.java +++ b/src/main/java/com/codejoust/main/model/report/GameReport.java @@ -51,6 +51,8 @@ public class GameReport { @JoinColumn(name = "users_table_id") private List users = new ArrayList<>(); + private int numTestCases; + // The start time of the game private Instant createdDateTime; diff --git a/src/main/java/com/codejoust/main/model/report/SubmissionGroupReport.java b/src/main/java/com/codejoust/main/model/report/SubmissionGroupReport.java index 36dd88a08..17f25ec7c 100644 --- a/src/main/java/com/codejoust/main/model/report/SubmissionGroupReport.java +++ b/src/main/java/com/codejoust/main/model/report/SubmissionGroupReport.java @@ -32,8 +32,6 @@ public class SubmissionGroupReport { private String gameReportId; - private Integer numTestCases; - /** * String to represent problems solved. * Each index represents the problem index. diff --git a/src/main/java/com/codejoust/main/service/GameManagementService.java b/src/main/java/com/codejoust/main/service/GameManagementService.java index 37a0c56db..0deccfd75 100644 --- a/src/main/java/com/codejoust/main/service/GameManagementService.java +++ b/src/main/java/com/codejoust/main/service/GameManagementService.java @@ -346,12 +346,10 @@ public void createGameReport(Game game) { submissionGroupReport.setGameReportId(gameReport.getGameReportId()); // Iterate through each submission and update group statistics. - int numTestCases = 0; boolean[] problemsSolved = new boolean[numProblems]; int[] testCasesPassed = new int[numProblems]; for (Submission submission : player.getSubmissions()) { int problemIndex = submission.getProblemIndex(); - numTestCases += submission.getNumTestCases(); totalAttemptCount[problemIndex]++; // If the problem was solved, set boolean value to true. @@ -372,7 +370,6 @@ public void createGameReport(Game game) { } // Set the problems and test cases statistics. - submissionGroupReport.setNumTestCases(numTestCases); submissionGroupReport.setProblemsSolved(compactProblemsSolved(problemsSolved)); submissionGroupReport.setNumTestCasesPassed(Arrays.stream(testCasesPassed).sum()); @@ -382,17 +379,21 @@ public void createGameReport(Game game) { } // Set problem container variables. + int numTestCases = 0; List problems = game.getProblems(); for (int i = 0; i < numProblems; i++) { Problem problem = problems.get(i); + numTestCases += problem.getTestCases().size(); + ProblemContainer problemContainer = new ProblemContainer(); problemContainer.setProblem(problem); problemContainer.setUserSolvedCount(userSolved[i]); problemContainer.setTestCaseCount(problem.getTestCases().size()); - problemContainer.setAverageTestCasesPassed(totalTestCasesPassed[i] / problem.getTestCases().size()); + problemContainer.setAverageTestCasesPassed(totalTestCasesPassed[i] / numPlayers); problemContainer.setAverageAttemptCount(totalAttemptCount[i] / numPlayers); gameReport.addProblemContainer(problemContainer); } + gameReport.setNumTestCases(numTestCases); Instant startTime = game.getGameTimer().getStartTime(); gameReport.setCreatedDateTime(startTime); From ed5c3da95fbdb910688b88023f19cb261db32f69 Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Tue, 29 Jun 2021 20:37:31 -0700 Subject: [PATCH 13/39] Remove old logs. --- .../com/codejoust/main/service/GameManagementService.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/codejoust/main/service/GameManagementService.java b/src/main/java/com/codejoust/main/service/GameManagementService.java index 0deccfd75..3cae4ef35 100644 --- a/src/main/java/com/codejoust/main/service/GameManagementService.java +++ b/src/main/java/com/codejoust/main/service/GameManagementService.java @@ -320,15 +320,12 @@ public void handleEndGame(Game game) { timer.cancel(); } - log.info("Create game report timer"); CreateGameReportTask createGameReportTask = new CreateGameReportTask(this, game); Timer createGameReportTimer = new Timer(); createGameReportTimer.schedule(createGameReportTask, GameTimer.DURATION_1 * 1000); - log.info("Passed game report timer"); } public void createGameReport(Game game) { - log.info("Started to create game report"); GameReport gameReport = new GameReport(); int numProblems = game.getProblems().size(); int numPlayers = game.getPlayers().size(); @@ -407,7 +404,6 @@ public void createGameReport(Game game) { gameReport.setGameEndType(GameEndType.TIME_UP); } - log.info("Save game report"); gameReportRepository.save(gameReport); // Iterate through all room users, players and spectators included. @@ -423,7 +419,9 @@ public void createGameReport(Game game) { userRepository.save(user); } } - log.info("Create game report"); + + // Log the completion of the latest game report. + log.info("Created game report for game with Room ID {}", game.getRoom().getRoomId()); } private String compactProblemsSolved(boolean[] problemsSolved) { From 22eff980b21eed4eaa48b60a35cdbc10e27d60da Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Sun, 4 Jul 2021 12:45:27 -0700 Subject: [PATCH 14/39] Add file for creating game report. --- .../main/task/CreateGameReportTaskTests.java | 147 ++++++++++++++++++ .../main/task/EndGameTimerTaskTests.java | 6 +- 2 files changed, 151 insertions(+), 2 deletions(-) create mode 100644 src/test/java/com/codejoust/main/task/CreateGameReportTaskTests.java diff --git a/src/test/java/com/codejoust/main/task/CreateGameReportTaskTests.java b/src/test/java/com/codejoust/main/task/CreateGameReportTaskTests.java new file mode 100644 index 000000000..8c0027637 --- /dev/null +++ b/src/test/java/com/codejoust/main/task/CreateGameReportTaskTests.java @@ -0,0 +1,147 @@ +package com.codejoust.main.task; + +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; + +import com.codejoust.main.dto.game.GameDto; +import com.codejoust.main.dto.game.GameMapper; +import com.codejoust.main.exception.api.ApiException; +import com.codejoust.main.game_object.Game; +import com.codejoust.main.game_object.GameTimer; +import com.codejoust.main.model.Room; +import com.codejoust.main.model.User; +import com.codejoust.main.model.problem.ProblemDifficulty; +import com.codejoust.main.service.GameManagementService; +import com.codejoust.main.service.SocketService; +import com.codejoust.main.util.TestFields; +import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class CreateGameReportTaskTests { + + @Mock + private GameManagementService gameManagementService; + + @Mock + private SocketService socketService; + + @BeforeEach + public void setup() throws Exception { + MockitoAnnotations.initMocks(this); + } + + @Test + public void createGameReportTaskSocketMessageNullGame() { + assertThrows(ApiException.class, () -> new CreateGameReportTask(gameManagementService, null)); + } + + @Test + public void createGameReportTaskSocketMessageNullSocketService() { + User user = new User(); + user.setNickname(TestFields.NICKNAME); + user.setUserId(TestFields.USER_ID); + user.setSessionId(TestFields.SESSION_ID); + + Room room = new Room(); + room.setRoomId(TestFields.ROOM_ID); + room.setDifficulty(ProblemDifficulty.MEDIUM); + room.setHost(user); + room.addUser(user); + + Game game = GameMapper.fromRoom(room); + GameTimer gameTimer = new GameTimer(10L); + game.setGameTimer(gameTimer); + + assertThrows(ApiException.class, () -> new CreateGameReportTask(gameManagementService, game)); + } + + @Test + public void createGameReportTaskSocketMessageNullGameTimer() { + User user = new User(); + user.setNickname(TestFields.NICKNAME); + user.setUserId(TestFields.USER_ID); + user.setSessionId(TestFields.SESSION_ID); + + Room room = new Room(); + room.setRoomId(TestFields.ROOM_ID); + room.setDifficulty(ProblemDifficulty.MEDIUM); + room.setHost(user); + room.addUser(user); + + Game game = GameMapper.fromRoom(room); + + assertThrows(ApiException.class, () -> new CreateGameReportTask(gameManagementService, game)); + } + + @Test + public void createGameReportTaskSocketMessageNullRoom() { + Game game = new Game(); + GameTimer gameTimer = new GameTimer(10L); + game.setGameTimer(gameTimer); + + assertThrows(ApiException.class, () -> new CreateGameReportTask(gameManagementService, game)); + } + + @Test + public void createGameReportTaskSocketMessageNullRoomId() { + User user = new User(); + user.setNickname(TestFields.NICKNAME); + user.setUserId(TestFields.USER_ID); + user.setSessionId(TestFields.SESSION_ID); + + Room room = new Room(); + room.setDifficulty(ProblemDifficulty.MEDIUM); + room.setHost(user); + room.addUser(user); + + Game game = GameMapper.fromRoom(room); + GameTimer gameTimer = new GameTimer(10L); + game.setGameTimer(gameTimer); + + assertThrows(ApiException.class, () -> new CreateGameReportTask(gameManagementService, game)); + } + + @Test + public void createGameReportTaskSocketMessage() { + User user = new User(); + user.setNickname(TestFields.NICKNAME); + user.setUserId(TestFields.USER_ID); + user.setSessionId(TestFields.SESSION_ID); + + Room room = new Room(); + room.setRoomId(TestFields.ROOM_ID); + room.setDifficulty(ProblemDifficulty.MEDIUM); + room.setHost(user); + room.addUser(user); + + Game game = GameMapper.fromRoom(room); + GameTimer gameTimer = new GameTimer(1L); + game.setGameTimer(gameTimer); + + // Make the Game DTO update that will occur on timer end. + GameDto gameDto = GameMapper.toDto(game); + gameDto.getGameTimer().setTimeUp(true); + + MockitoAnnotations.initMocks(this); + + CreateGameReportTask createGameReportTask = new CreateGameReportTask(gameManagementService, game); + gameTimer.getTimer().schedule(createGameReportTask, 1000L); + + /** + * Confirm that the socket update is not called immediately, + * but is called 1 second later (wait for timer task). + */ + + verify(socketService, never()).sendSocketUpdate(eq(gameDto)); + + verify(socketService, timeout(1200)).sendSocketUpdate(eq(gameDto)); + } +} diff --git a/src/test/java/com/codejoust/main/task/EndGameTimerTaskTests.java b/src/test/java/com/codejoust/main/task/EndGameTimerTaskTests.java index 859148315..75b62760e 100644 --- a/src/test/java/com/codejoust/main/task/EndGameTimerTaskTests.java +++ b/src/test/java/com/codejoust/main/task/EndGameTimerTaskTests.java @@ -140,8 +140,10 @@ public void endGameTimerTaskSocketMessage() { * but is called 1 second later (wait for timer task). */ - verify(socketService, never()).sendSocketUpdate(eq(gameDto)); + // verify(socketService, never()).sendSocketUpdate(eq(gameDto)); - verify(socketService, timeout(1200)).sendSocketUpdate(eq(gameDto)); + // verify(socketService, timeout(1200)).sendSocketUpdate(eq(gameDto)); + + verify(gameManagementService, timeout(10000)).handleEndGame(game); } } From 56c580a81567fbdc9d20182dd550588ecf147e7b Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Sun, 4 Jul 2021 13:34:25 -0700 Subject: [PATCH 15/39] Add mockito annotations calls to fix methods, remove unnecessary checks. --- .../main/task/CreateGameReportTask.java | 2 +- .../codejoust/main/task/EndGameTimerTask.java | 2 +- .../service/GameManagementServiceTests.java | 3 +- .../main/task/CreateGameReportTaskTests.java | 2 +- .../main/task/EndGameTimerTaskTests.java | 47 ++++--------------- 5 files changed, 15 insertions(+), 41 deletions(-) diff --git a/src/main/java/com/codejoust/main/task/CreateGameReportTask.java b/src/main/java/com/codejoust/main/task/CreateGameReportTask.java index 81d43873c..bdb47c036 100644 --- a/src/main/java/com/codejoust/main/task/CreateGameReportTask.java +++ b/src/main/java/com/codejoust/main/task/CreateGameReportTask.java @@ -18,7 +18,7 @@ public CreateGameReportTask(GameManagementService gameManagementService, Game ga this.game = game; // Handle potential errors for run(). - if (game == null) { + if (gameManagementService == null || game == null) { throw new ApiException(TimerError.NULL_SETTING); } } diff --git a/src/main/java/com/codejoust/main/task/EndGameTimerTask.java b/src/main/java/com/codejoust/main/task/EndGameTimerTask.java index 143e77ffe..738c59ef4 100644 --- a/src/main/java/com/codejoust/main/task/EndGameTimerTask.java +++ b/src/main/java/com/codejoust/main/task/EndGameTimerTask.java @@ -25,7 +25,7 @@ public EndGameTimerTask(GameManagementService gameManagementService, this.game = game; // Handle potential errors for run(). - if (game == null || game.getGameTimer() == null || game.getRoom() == null || game.getRoom().getRoomId() == null || socketService == null) { + if (game == null || socketService == null || gameManagementService == null) { throw new ApiException(TimerError.NULL_SETTING); } } diff --git a/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java b/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java index daaf259a9..de21e8709 100644 --- a/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java +++ b/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java @@ -986,7 +986,7 @@ public void isGameOverFunctionsCorrectly() { } @Test - public void endGameCancelsTimers() { + public void endGameCancelsTimersCreateReport() { Room room = new Room(); room.setRoomId(TestFields.ROOM_ID); room.setDuration(12L); @@ -1007,6 +1007,7 @@ public void endGameCancelsTimers() { // Neither the end game nor time left notifications are sent verify(socketService, after(13000).never()).sendSocketUpdate(Mockito.any(String.class), Mockito.any(GameNotificationDto.class)); verify(socketService, never()).sendSocketUpdate(Mockito.any(GameDto.class)); + verify(gameService, after(100000)).createGameReport(game); } @Test diff --git a/src/test/java/com/codejoust/main/task/CreateGameReportTaskTests.java b/src/test/java/com/codejoust/main/task/CreateGameReportTaskTests.java index 8c0027637..b226625cb 100644 --- a/src/test/java/com/codejoust/main/task/CreateGameReportTaskTests.java +++ b/src/test/java/com/codejoust/main/task/CreateGameReportTaskTests.java @@ -60,7 +60,7 @@ public void createGameReportTaskSocketMessageNullSocketService() { GameTimer gameTimer = new GameTimer(10L); game.setGameTimer(gameTimer); - assertThrows(ApiException.class, () -> new CreateGameReportTask(gameManagementService, game)); + assertThrows(ApiException.class, () -> new CreateGameReportTask(null, game)); } @Test diff --git a/src/test/java/com/codejoust/main/task/EndGameTimerTaskTests.java b/src/test/java/com/codejoust/main/task/EndGameTimerTaskTests.java index 75b62760e..8f7efee64 100644 --- a/src/test/java/com/codejoust/main/task/EndGameTimerTaskTests.java +++ b/src/test/java/com/codejoust/main/task/EndGameTimerTaskTests.java @@ -18,7 +18,6 @@ import com.codejoust.main.service.SocketService; import com.codejoust.main.util.TestFields; import org.junit.Test; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -33,11 +32,6 @@ public class EndGameTimerTaskTests { @Mock private SocketService socketService; - @BeforeEach - public void setup() throws Exception { - MockitoAnnotations.initMocks(this); - } - @Test public void endGameTimerTaskSocketMessageNullGame() { assertThrows(ApiException.class, () -> new EndGameTimerTask(gameManagementService, socketService, null)); @@ -60,11 +54,13 @@ public void endGameTimerTaskSocketMessageNullSocketService() { GameTimer gameTimer = new GameTimer(10L); game.setGameTimer(gameTimer); + MockitoAnnotations.initMocks(this); + assertThrows(ApiException.class, () -> new EndGameTimerTask(gameManagementService, null, game)); } @Test - public void endGameTimerTaskSocketMessageNullGameTimer() { + public void endGameTimerTaskSocketMessageNullGameManagementService() { User user = new User(); user.setNickname(TestFields.NICKNAME); user.setUserId(TestFields.USER_ID); @@ -77,36 +73,12 @@ public void endGameTimerTaskSocketMessageNullGameTimer() { room.addUser(user); Game game = GameMapper.fromRoom(room); - - assertThrows(ApiException.class, () -> new EndGameTimerTask(gameManagementService, socketService, game)); - } - - @Test - public void endGameTimerTaskSocketMessageNullRoom() { - Game game = new Game(); GameTimer gameTimer = new GameTimer(10L); game.setGameTimer(gameTimer); - assertThrows(ApiException.class, () -> new EndGameTimerTask(gameManagementService, socketService, game)); - } - - @Test - public void endGameTimerTaskSocketMessageNullRoomId() { - User user = new User(); - user.setNickname(TestFields.NICKNAME); - user.setUserId(TestFields.USER_ID); - user.setSessionId(TestFields.SESSION_ID); - - Room room = new Room(); - room.setDifficulty(ProblemDifficulty.MEDIUM); - room.setHost(user); - room.addUser(user); - - Game game = GameMapper.fromRoom(room); - GameTimer gameTimer = new GameTimer(10L); - game.setGameTimer(gameTimer); + MockitoAnnotations.initMocks(this); - assertThrows(ApiException.class, () -> new EndGameTimerTask(gameManagementService, socketService, game)); + assertThrows(ApiException.class, () -> new EndGameTimerTask(null, socketService, game)); } @Test @@ -137,13 +109,14 @@ public void endGameTimerTaskSocketMessage() { /** * Confirm that the socket update is not called immediately, - * but is called 1 second later (wait for timer task). + * but is called 1 second later (wait for timer task), + * along with the game management service. */ - // verify(socketService, never()).sendSocketUpdate(eq(gameDto)); + verify(socketService, never()).sendSocketUpdate(eq(gameDto)); - // verify(socketService, timeout(1200)).sendSocketUpdate(eq(gameDto)); + verify(socketService, timeout(1200)).sendSocketUpdate(eq(gameDto)); - verify(gameManagementService, timeout(10000)).handleEndGame(game); + verify(gameManagementService, timeout(1200)).handleEndGame(game); } } From 0ae57416ae9442ade87a38501bbee6c562fae7a9 Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Sun, 4 Jul 2021 14:31:19 -0700 Subject: [PATCH 16/39] Remove unnecessary tests and add check for create game report. --- .../service/GameManagementServiceTests.java | 9 ++- .../main/task/CreateGameReportTaskTests.java | 55 +------------------ .../main/task/EndGameTimerTaskTests.java | 1 + 3 files changed, 12 insertions(+), 53 deletions(-) diff --git a/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java b/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java index de21e8709..67ad76008 100644 --- a/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java +++ b/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java @@ -27,6 +27,7 @@ import java.util.Arrays; import java.util.Collections; +import com.codejoust.main.dao.GameReportRepository; import com.codejoust.main.dao.RoomRepository; import com.codejoust.main.dto.game.GameDto; import com.codejoust.main.dto.game.GameMapper; @@ -52,6 +53,7 @@ import com.codejoust.main.model.User; import com.codejoust.main.model.problem.Problem; import com.codejoust.main.model.problem.ProblemDifficulty; +import com.codejoust.main.model.report.GameReport; @ExtendWith(MockitoExtension.class) public class GameManagementServiceTests { @@ -59,6 +61,9 @@ public class GameManagementServiceTests { @Mock private RoomRepository repository; + @Mock + private GameReportRepository gameReportRepository; + @Mock private SocketService socketService; @@ -1007,7 +1012,9 @@ public void endGameCancelsTimersCreateReport() { // Neither the end game nor time left notifications are sent verify(socketService, after(13000).never()).sendSocketUpdate(Mockito.any(String.class), Mockito.any(GameNotificationDto.class)); verify(socketService, never()).sendSocketUpdate(Mockito.any(GameDto.class)); - verify(gameService, after(100000)).createGameReport(game); + + // Game report is saved after one minute past handleEndGame + verify(gameReportRepository, after(61300)).save(Mockito.any(GameReport.class)); } @Test diff --git a/src/test/java/com/codejoust/main/task/CreateGameReportTaskTests.java b/src/test/java/com/codejoust/main/task/CreateGameReportTaskTests.java index b226625cb..84f06533b 100644 --- a/src/test/java/com/codejoust/main/task/CreateGameReportTaskTests.java +++ b/src/test/java/com/codejoust/main/task/CreateGameReportTaskTests.java @@ -18,7 +18,6 @@ import com.codejoust.main.service.SocketService; import com.codejoust.main.util.TestFields; import org.junit.Test; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -33,13 +32,9 @@ public class CreateGameReportTaskTests { @Mock private SocketService socketService; - @BeforeEach - public void setup() throws Exception { - MockitoAnnotations.initMocks(this); - } - @Test public void createGameReportTaskSocketMessageNullGame() { + MockitoAnnotations.initMocks(this); assertThrows(ApiException.class, () -> new CreateGameReportTask(gameManagementService, null)); } @@ -60,53 +55,9 @@ public void createGameReportTaskSocketMessageNullSocketService() { GameTimer gameTimer = new GameTimer(10L); game.setGameTimer(gameTimer); - assertThrows(ApiException.class, () -> new CreateGameReportTask(null, game)); - } - - @Test - public void createGameReportTaskSocketMessageNullGameTimer() { - User user = new User(); - user.setNickname(TestFields.NICKNAME); - user.setUserId(TestFields.USER_ID); - user.setSessionId(TestFields.SESSION_ID); - - Room room = new Room(); - room.setRoomId(TestFields.ROOM_ID); - room.setDifficulty(ProblemDifficulty.MEDIUM); - room.setHost(user); - room.addUser(user); - - Game game = GameMapper.fromRoom(room); - - assertThrows(ApiException.class, () -> new CreateGameReportTask(gameManagementService, game)); - } - - @Test - public void createGameReportTaskSocketMessageNullRoom() { - Game game = new Game(); - GameTimer gameTimer = new GameTimer(10L); - game.setGameTimer(gameTimer); - - assertThrows(ApiException.class, () -> new CreateGameReportTask(gameManagementService, game)); - } - - @Test - public void createGameReportTaskSocketMessageNullRoomId() { - User user = new User(); - user.setNickname(TestFields.NICKNAME); - user.setUserId(TestFields.USER_ID); - user.setSessionId(TestFields.SESSION_ID); - - Room room = new Room(); - room.setDifficulty(ProblemDifficulty.MEDIUM); - room.setHost(user); - room.addUser(user); - - Game game = GameMapper.fromRoom(room); - GameTimer gameTimer = new GameTimer(10L); - game.setGameTimer(gameTimer); + MockitoAnnotations.initMocks(this); - assertThrows(ApiException.class, () -> new CreateGameReportTask(gameManagementService, game)); + assertThrows(ApiException.class, () -> new CreateGameReportTask(null, game)); } @Test diff --git a/src/test/java/com/codejoust/main/task/EndGameTimerTaskTests.java b/src/test/java/com/codejoust/main/task/EndGameTimerTaskTests.java index 8f7efee64..e130c9f15 100644 --- a/src/test/java/com/codejoust/main/task/EndGameTimerTaskTests.java +++ b/src/test/java/com/codejoust/main/task/EndGameTimerTaskTests.java @@ -34,6 +34,7 @@ public class EndGameTimerTaskTests { @Test public void endGameTimerTaskSocketMessageNullGame() { + MockitoAnnotations.initMocks(this); assertThrows(ApiException.class, () -> new EndGameTimerTask(gameManagementService, socketService, null)); } From ee959042f611228a804ffefbcdc227047017ce53 Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Mon, 5 Jul 2021 12:48:39 -0700 Subject: [PATCH 17/39] Update the game management service tests. --- .../service/GameManagementServiceTests.java | 128 ++++++++++++++++-- .../main/task/CreateGameReportTaskTests.java | 10 +- .../com/codejoust/main/util/TestFields.java | 7 +- 3 files changed, 124 insertions(+), 21 deletions(-) diff --git a/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java b/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java index 67ad76008..921233c0c 100644 --- a/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java +++ b/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java @@ -24,8 +24,10 @@ import static org.mockito.Mockito.verify; import java.time.Instant; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.List; import com.codejoust.main.dao.GameReportRepository; import com.codejoust.main.dao.RoomRepository; @@ -84,7 +86,7 @@ public class GameManagementServiceTests { private GameManagementService gameService; // Helper method to add a dummy submission to a Player object - private void addSubmissionHelper(Player player, int numCorrect) { + private void addSubmissionHelper(Player player, int problemIndex, int numCorrect) { Submission submission = new Submission(); submission.setNumCorrect(numCorrect); submission.setNumTestCases(TestFields.NUM_PROBLEMS); @@ -92,7 +94,9 @@ private void addSubmissionHelper(Player player, int numCorrect) { player.getSubmissions().add(submission); if (numCorrect == TestFields.NUM_PROBLEMS) { - player.setSolved(new boolean[]{true}); + boolean[] solved = player.getSolved(); + solved[problemIndex] = true; + player.setSolved(solved); } } @@ -365,7 +369,7 @@ public void submitSolutionSuccess() { submissionDto.setNumTestCases(TestFields.NUM_PROBLEMS); Mockito.doAnswer(new Answer() { public SubmissionDto answer(InvocationOnMock invocation) { - addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID), 10); + addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID), 0, 10); game.setAllSolved(true); return submissionDto; }}) @@ -406,8 +410,8 @@ public void sendAllSolvedSocketUpdate() { Game game = gameService.getGameFromRoomId(TestFields.ROOM_ID); // Add submissions for the first two users. - addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID), 10); - addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID_2), 10); + addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID), 0, 10); + addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID_2), 0, 10); SubmissionRequest request = new SubmissionRequest(); request.setLanguage(TestFields.PYTHON_LANGUAGE); @@ -420,7 +424,7 @@ public void sendAllSolvedSocketUpdate() { submissionDto.setNumTestCases(TestFields.NUM_PROBLEMS); Mockito.doAnswer(new Answer() { public SubmissionDto answer(InvocationOnMock invocation) { - addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID_3), 10); + addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID_3), 0, 10); game.setAllSolved(true); return submissionDto; }}) @@ -466,7 +470,7 @@ public void submitSolutionNotAllSolved() { submissionDto.setNumTestCases(TestFields.NUM_PROBLEMS); Mockito.doAnswer(new Answer() { public SubmissionDto answer(InvocationOnMock invocation) { - addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID), 10); + addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID), 0, 10); return submissionDto; }}) .when(submitService).submitSolution(game, request); @@ -912,12 +916,12 @@ public void updateCodeSuccess() { Mockito.doReturn(Collections.singletonList(new Problem())).when(problemService).getProblemsFromDifficulty(Mockito.any(), Mockito.any()); gameService.createAddGameFromRoom(room); Game game = gameService.getGameFromRoomId(TestFields.ROOM_ID); - gameService.updateCode(TestFields.ROOM_ID, TestFields.USER_ID, TestFields.PLAYER_CODE); + gameService.updateCode(TestFields.ROOM_ID, TestFields.USER_ID, TestFields.PLAYER_CODE_1); Player player = game.getPlayers().get(TestFields.USER_ID); // Confirm that the live game service method is called correctly. - verify(liveGameService).updateCode(eq(player), eq(TestFields.PLAYER_CODE)); + verify(liveGameService).updateCode(eq(player), eq(TestFields.PLAYER_CODE_1)); } @Test @@ -932,7 +936,7 @@ public void updateCodeInvalidRoomId() { Mockito.doReturn(Collections.singletonList(new Problem())).when(problemService).getProblemsFromDifficulty(Mockito.any(), Mockito.any()); gameService.createAddGameFromRoom(room); - ApiException exception = assertThrows(ApiException.class, () -> gameService.updateCode("999999", TestFields.USER_ID, TestFields.PLAYER_CODE)); + ApiException exception = assertThrows(ApiException.class, () -> gameService.updateCode("999999", TestFields.USER_ID, TestFields.PLAYER_CODE_1)); assertEquals(GameError.NOT_FOUND, exception.getError()); } @@ -948,7 +952,7 @@ public void updateCodeInvalidUserId() { Mockito.doReturn(Collections.singletonList(new Problem())).when(problemService).getProblemsFromDifficulty(Mockito.any(), Mockito.any()); gameService.createAddGameFromRoom(room); - ApiException exception = assertThrows(ApiException.class, () -> gameService.updateCode(TestFields.ROOM_ID, "999999", TestFields.PLAYER_CODE)); + ApiException exception = assertThrows(ApiException.class, () -> gameService.updateCode(TestFields.ROOM_ID, "999999", TestFields.PLAYER_CODE_1)); assertEquals(GameError.USER_NOT_IN_GAME, exception.getError()); } @@ -1017,6 +1021,108 @@ public void endGameCancelsTimersCreateReport() { verify(gameReportRepository, after(61300)).save(Mockito.any(GameReport.class)); } + @Test + public void createGameReportMultipleAttributes() { + /** + * Create a game report to test multiple different attributes: + * 1. Logged-in users, logged-out users, players, and spectators. + * 2. Correct and partially correct submissions. + * 3. Multiple problems, one of which is deleted before game end. + */ + + Room room = new Room(); + room.setRoomId(TestFields.ROOM_ID); + room.setDuration(120L); + User user1 = new User(); + user1.setNickname(TestFields.NICKNAME); + user1.setUserId(TestFields.USER_ID); + user1.setAccount(TestFields.account1()); + User user2 = new User(); + user2.setNickname(TestFields.NICKNAME_2); + user2.setUserId(TestFields.USER_ID_2); + user2.setAccount(TestFields.account1()); + User user3 = new User(); + user3.setNickname(TestFields.NICKNAME_3); + user3.setUserId(TestFields.USER_ID_3); + User user4 = new User(); + user4.setNickname(TestFields.NICKNAME_4); + user4.setUserId(TestFields.USER_ID_4); + user4.setSpectator(true); + room.addUser(user1); + room.addUser(user2); + room.addUser(user3); + room.addUser(user4); + room.setHost(user1); + + List problems = new ArrayList<>(); + problems.add(TestFields.problem1()); + problems.add(TestFields.problem2()); + room.setProblems(problems); + + gameService.createAddGameFromRoom(room); + Game game = gameService.getGameFromRoomId(room.getRoomId()); + + SubmissionRequest correctSubmission = new SubmissionRequest(); + correctSubmission.setLanguage(TestFields.PYTHON_LANGUAGE); + correctSubmission.setCode(TestFields.PYTHON_CODE); + correctSubmission.setInitiator(UserMapper.toDto(user1)); + + // First correct submission for problem 0 with user1. + SubmissionDto correctSubmissionDto = new SubmissionDto(); + correctSubmissionDto.setNumCorrect(TestFields.NUM_PROBLEMS); + correctSubmissionDto.setNumTestCases(TestFields.NUM_PROBLEMS); + Mockito.doAnswer(new Answer() { + public SubmissionDto answer(InvocationOnMock invocation) { + addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID), 0, 10); + game.setAllSolved(true); + return correctSubmissionDto; + }}) + .when(submitService).submitSolution(game, correctSubmission); + + gameService.submitSolution(TestFields.ROOM_ID, correctSubmission); + + // Second correct submission for problem 0 with user3. + correctSubmission.setInitiator(UserMapper.toDto(user3)); + correctSubmissionDto.setNumCorrect(TestFields.NUM_PROBLEMS); + correctSubmissionDto.setNumTestCases(TestFields.NUM_PROBLEMS); + Mockito.doAnswer(new Answer() { + public SubmissionDto answer(InvocationOnMock invocation) { + addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID_3), 0, 10); + game.setAllSolved(true); + return correctSubmissionDto; + }}) + .when(submitService).submitSolution(game, correctSubmission); + + gameService.submitSolution(TestFields.ROOM_ID, correctSubmission); + + // Partially correct submission for problem 1 with user3. + SubmissionRequest partialSubmission = new SubmissionRequest(); + partialSubmission.setLanguage(TestFields.PYTHON_LANGUAGE); + partialSubmission.setCode(TestFields.PYTHON_CODE); + partialSubmission.setInitiator(UserMapper.toDto(user3)); + + // First correct submission for problem 0 with user1. + SubmissionDto partialSubmissionDto = new SubmissionDto(); + partialSubmissionDto.setNumCorrect(TestFields.NUM_PROBLEMS); + partialSubmissionDto.setNumTestCases(TestFields.NUM_PROBLEMS); + Mockito.doAnswer(new Answer() { + public SubmissionDto answer(InvocationOnMock invocation) { + addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID_3), 1, 5); + game.setAllSolved(true); + return partialSubmissionDto; + }}) + .when(submitService).submitSolution(game, partialSubmission); + gameService.submitSolution(TestFields.ROOM_ID, partialSubmission); + + gameService.createGameReport(game); + + GameReport gameReport = new GameReport(); + + + // Game report is saved after one minute past handleEndGame + verify(gameReportRepository, after(61300)).save(eq(gameReport)); + } + @Test public void conditionallyUpdateSocketInfoSuccess() { Room room = new Room(); diff --git a/src/test/java/com/codejoust/main/task/CreateGameReportTaskTests.java b/src/test/java/com/codejoust/main/task/CreateGameReportTaskTests.java index 84f06533b..a5d82a9a6 100644 --- a/src/test/java/com/codejoust/main/task/CreateGameReportTaskTests.java +++ b/src/test/java/com/codejoust/main/task/CreateGameReportTaskTests.java @@ -2,7 +2,6 @@ import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; @@ -86,13 +85,6 @@ public void createGameReportTaskSocketMessage() { CreateGameReportTask createGameReportTask = new CreateGameReportTask(gameManagementService, game); gameTimer.getTimer().schedule(createGameReportTask, 1000L); - /** - * Confirm that the socket update is not called immediately, - * but is called 1 second later (wait for timer task). - */ - - verify(socketService, never()).sendSocketUpdate(eq(gameDto)); - - verify(socketService, timeout(1200)).sendSocketUpdate(eq(gameDto)); + verify(gameManagementService, timeout(1200)).createGameReport(eq(game)); } } diff --git a/src/test/java/com/codejoust/main/util/TestFields.java b/src/test/java/com/codejoust/main/util/TestFields.java index bd33f74c1..ec1d23143 100644 --- a/src/test/java/com/codejoust/main/util/TestFields.java +++ b/src/test/java/com/codejoust/main/util/TestFields.java @@ -30,6 +30,7 @@ public class TestFields { public static final String USER_ID = "012345"; public static final String USER_ID_2 = "678910"; public static final String USER_ID_3 = "024681"; + public static final String USER_ID_4 = "401905"; public static final String SESSION_ID = "abcde"; public static final String SESSION_ID_2 = "fghij"; public static final Integer ID = 1; @@ -65,7 +66,11 @@ public class TestFields { public static final String PYTHON_CODE = "print('hello')"; public static final CodeLanguage PYTHON_LANGUAGE = CodeLanguage.PYTHON; - public static final PlayerCode PLAYER_CODE = new PlayerCode(PYTHON_CODE, PYTHON_LANGUAGE); + public static final PlayerCode PLAYER_CODE_1 = new PlayerCode(PYTHON_CODE, PYTHON_LANGUAGE); + + public static final String JAVA_CODE = "System.out.println(\"hello\");"; + public static final CodeLanguage JAVA_LANGUAGE = CodeLanguage.JAVA; + public static final PlayerCode PLAYER_CODE_2 = new PlayerCode(JAVA_CODE, JAVA_LANGUAGE); public static final Integer NUM_PROBLEMS = 10; From 54795e578509c15913835545ba0b49ae2a1479de Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Mon, 5 Jul 2021 16:27:13 -0700 Subject: [PATCH 18/39] Remove unused variables. --- src/main/java/com/codejoust/main/service/SubmitService.java | 3 --- src/test/java/com/codejoust/main/api/GameTests.java | 1 - src/test/java/com/codejoust/main/mapper/GameMapperTests.java | 1 - .../java/com/codejoust/main/service/LiveGameServiceTests.java | 4 ++-- 4 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/codejoust/main/service/SubmitService.java b/src/main/java/com/codejoust/main/service/SubmitService.java index a2f367f65..9af74af13 100644 --- a/src/main/java/com/codejoust/main/service/SubmitService.java +++ b/src/main/java/com/codejoust/main/service/SubmitService.java @@ -91,9 +91,6 @@ private Submission getDummySubmission(TesterRequest request) { // Test the submission and send a socket update. public SubmissionDto runCode(Game game, SubmissionRequest request) { - String userId = request.getInitiator().getUserId(); - Player player = game.getPlayers().get(userId); - PlayerCode playerCode = new PlayerCode(); playerCode.setCode(request.getCode()); playerCode.setLanguage(request.getLanguage()); diff --git a/src/test/java/com/codejoust/main/api/GameTests.java b/src/test/java/com/codejoust/main/api/GameTests.java index e7e9eadfc..d12a52ab8 100644 --- a/src/test/java/com/codejoust/main/api/GameTests.java +++ b/src/test/java/com/codejoust/main/api/GameTests.java @@ -38,7 +38,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; diff --git a/src/test/java/com/codejoust/main/mapper/GameMapperTests.java b/src/test/java/com/codejoust/main/mapper/GameMapperTests.java index 3731e8c3d..e7e24d506 100644 --- a/src/test/java/com/codejoust/main/mapper/GameMapperTests.java +++ b/src/test/java/com/codejoust/main/mapper/GameMapperTests.java @@ -19,7 +19,6 @@ import com.codejoust.main.dto.game.SubmissionDto; import com.codejoust.main.dto.game.SubmissionResultDto; import com.codejoust.main.dto.problem.ProblemDto; -import com.codejoust.main.dto.problem.ProblemMapper; import com.codejoust.main.dto.problem.ProblemTestCaseDto; import com.codejoust.main.dto.room.RoomMapper; import com.codejoust.main.dto.user.UserMapper; diff --git a/src/test/java/com/codejoust/main/service/LiveGameServiceTests.java b/src/test/java/com/codejoust/main/service/LiveGameServiceTests.java index 0115573a7..0ca542cd4 100644 --- a/src/test/java/com/codejoust/main/service/LiveGameServiceTests.java +++ b/src/test/java/com/codejoust/main/service/LiveGameServiceTests.java @@ -33,8 +33,8 @@ public void updateCodeSuccess() { Game game = GameMapper.fromRoom(room); Player player = game.getPlayers().get(TestFields.USER_ID); - liveGameService.updateCode(player, TestFields.PLAYER_CODE); + liveGameService.updateCode(player, TestFields.PLAYER_CODE_1); - assertEquals(TestFields.PLAYER_CODE, player.getPlayerCode()); + assertEquals(TestFields.PLAYER_CODE_1, player.getPlayerCode()); } } From 343313844ba895e4346206196d2aef88dab5c9c2 Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Mon, 5 Jul 2021 17:41:56 -0700 Subject: [PATCH 19/39] Add verification of the relevant game report objects. --- .../main/service/GameManagementService.java | 3 +- .../service/GameManagementServiceTests.java | 50 +++++++++++++------ 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/codejoust/main/service/GameManagementService.java b/src/main/java/com/codejoust/main/service/GameManagementService.java index 3cae4ef35..30c797424 100644 --- a/src/main/java/com/codejoust/main/service/GameManagementService.java +++ b/src/main/java/com/codejoust/main/service/GameManagementService.java @@ -325,7 +325,7 @@ public void handleEndGame(Game game) { createGameReportTimer.schedule(createGameReportTask, GameTimer.DURATION_1 * 1000); } - public void createGameReport(Game game) { + public GameReport createGameReport(Game game) { GameReport gameReport = new GameReport(); int numProblems = game.getProblems().size(); int numPlayers = game.getPlayers().size(); @@ -422,6 +422,7 @@ public void createGameReport(Game game) { // Log the completion of the latest game report. log.info("Created game report for game with Room ID {}", game.getRoom().getRoomId()); + return gameReport; } private String compactProblemsSolved(boolean[] problemsSolved) { diff --git a/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java b/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java index 921233c0c..65cafd631 100644 --- a/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java +++ b/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java @@ -29,8 +29,10 @@ import java.util.Collections; import java.util.List; +import com.codejoust.main.dao.AccountRepository; import com.codejoust.main.dao.GameReportRepository; import com.codejoust.main.dao.RoomRepository; +import com.codejoust.main.dao.UserRepository; import com.codejoust.main.dto.game.GameDto; import com.codejoust.main.dto.game.GameMapper; import com.codejoust.main.dto.game.GameNotificationDto; @@ -50,11 +52,13 @@ import com.codejoust.main.game_object.GameTimer; import com.codejoust.main.game_object.NotificationType; import com.codejoust.main.game_object.Player; +import com.codejoust.main.game_object.PlayerCode; import com.codejoust.main.game_object.Submission; import com.codejoust.main.model.Room; import com.codejoust.main.model.User; import com.codejoust.main.model.problem.Problem; import com.codejoust.main.model.problem.ProblemDifficulty; +import com.codejoust.main.model.report.GameEndType; import com.codejoust.main.model.report.GameReport; @ExtendWith(MockitoExtension.class) @@ -65,6 +69,12 @@ public class GameManagementServiceTests { @Mock private GameReportRepository gameReportRepository; + + @Mock + private AccountRepository accountRepository; + + @Mock + private UserRepository userRepository; @Mock private SocketService socketService; @@ -86,11 +96,12 @@ public class GameManagementServiceTests { private GameManagementService gameService; // Helper method to add a dummy submission to a Player object - private void addSubmissionHelper(Player player, int problemIndex, int numCorrect) { + private void addSubmissionHelper(Player player, int problemIndex, PlayerCode playerCode, int numCorrect) { Submission submission = new Submission(); submission.setNumCorrect(numCorrect); submission.setNumTestCases(TestFields.NUM_PROBLEMS); submission.setStartTime(Instant.now()); + submission.setPlayerCode(playerCode); player.getSubmissions().add(submission); if (numCorrect == TestFields.NUM_PROBLEMS) { @@ -369,7 +380,7 @@ public void submitSolutionSuccess() { submissionDto.setNumTestCases(TestFields.NUM_PROBLEMS); Mockito.doAnswer(new Answer() { public SubmissionDto answer(InvocationOnMock invocation) { - addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID), 0, 10); + addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID), 0, TestFields.PLAYER_CODE_1, 10); game.setAllSolved(true); return submissionDto; }}) @@ -410,8 +421,8 @@ public void sendAllSolvedSocketUpdate() { Game game = gameService.getGameFromRoomId(TestFields.ROOM_ID); // Add submissions for the first two users. - addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID), 0, 10); - addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID_2), 0, 10); + addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID), 0, TestFields.PLAYER_CODE_1, 10); + addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID_2), 0, TestFields.PLAYER_CODE_1, 10); SubmissionRequest request = new SubmissionRequest(); request.setLanguage(TestFields.PYTHON_LANGUAGE); @@ -424,7 +435,7 @@ public void sendAllSolvedSocketUpdate() { submissionDto.setNumTestCases(TestFields.NUM_PROBLEMS); Mockito.doAnswer(new Answer() { public SubmissionDto answer(InvocationOnMock invocation) { - addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID_3), 0, 10); + addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID_3), 0,TestFields.PLAYER_CODE_1, 10); game.setAllSolved(true); return submissionDto; }}) @@ -470,7 +481,7 @@ public void submitSolutionNotAllSolved() { submissionDto.setNumTestCases(TestFields.NUM_PROBLEMS); Mockito.doAnswer(new Answer() { public SubmissionDto answer(InvocationOnMock invocation) { - addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID), 0, 10); + addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID), 0, TestFields.PLAYER_CODE_1, 10); return submissionDto; }}) .when(submitService).submitSolution(game, request); @@ -1027,12 +1038,12 @@ public void createGameReportMultipleAttributes() { * Create a game report to test multiple different attributes: * 1. Logged-in users, logged-out users, players, and spectators. * 2. Correct and partially correct submissions. - * 3. Multiple problems, one of which is deleted before game end. + * 3. Multiple problems, with submissions for each. */ Room room = new Room(); room.setRoomId(TestFields.ROOM_ID); - room.setDuration(120L); + room.setDuration(120000000L); User user1 = new User(); user1.setNickname(TestFields.NICKNAME); user1.setUserId(TestFields.USER_ID); @@ -1058,6 +1069,7 @@ public void createGameReportMultipleAttributes() { problems.add(TestFields.problem1()); problems.add(TestFields.problem2()); room.setProblems(problems); + room.setNumProblems(problems.size()); gameService.createAddGameFromRoom(room); Game game = gameService.getGameFromRoomId(room.getRoomId()); @@ -1073,7 +1085,7 @@ public void createGameReportMultipleAttributes() { correctSubmissionDto.setNumTestCases(TestFields.NUM_PROBLEMS); Mockito.doAnswer(new Answer() { public SubmissionDto answer(InvocationOnMock invocation) { - addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID), 0, 10); + addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID), 0, TestFields.PLAYER_CODE_1, 10); game.setAllSolved(true); return correctSubmissionDto; }}) @@ -1087,7 +1099,7 @@ public SubmissionDto answer(InvocationOnMock invocation) { correctSubmissionDto.setNumTestCases(TestFields.NUM_PROBLEMS); Mockito.doAnswer(new Answer() { public SubmissionDto answer(InvocationOnMock invocation) { - addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID_3), 0, 10); + addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID_3), 0, TestFields.PLAYER_CODE_1, 10); game.setAllSolved(true); return correctSubmissionDto; }}) @@ -1107,20 +1119,26 @@ public SubmissionDto answer(InvocationOnMock invocation) { partialSubmissionDto.setNumTestCases(TestFields.NUM_PROBLEMS); Mockito.doAnswer(new Answer() { public SubmissionDto answer(InvocationOnMock invocation) { - addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID_3), 1, 5); + addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID_3), 1, TestFields.PLAYER_CODE_2, 5); game.setAllSolved(true); return partialSubmissionDto; }}) .when(submitService).submitSolution(game, partialSubmission); gameService.submitSolution(TestFields.ROOM_ID, partialSubmission); - gameService.createGameReport(game); - - GameReport gameReport = new GameReport(); + // Set manually end game, and trigger game report creation directly. + EndGameRequest request = new EndGameRequest(); + request.setInitiator(UserMapper.toDto(user1)); + gameService.manuallyEndGame(TestFields.ROOM_ID, request); + GameReport gameReport = gameService.createGameReport(game); + // Confirm that the game report, account, and users are saved. + verify(gameReportRepository).save(Mockito.any(GameReport.class)); + verify(accountRepository).save(eq(user1.getAccount())); + verify(userRepository).save(eq(user3)); + verify(userRepository).save(eq(user4)); - // Game report is saved after one minute past handleEndGame - verify(gameReportRepository, after(61300)).save(eq(gameReport)); + } @Test From 9dcabee2b4ad098cc1cf432ee0f5ce795aac8148 Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Mon, 5 Jul 2021 18:17:12 -0700 Subject: [PATCH 20/39] Update the submission helper to have only one test case. --- .../service/GameManagementServiceTests.java | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java b/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java index 65cafd631..d805d252b 100644 --- a/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java +++ b/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java @@ -99,12 +99,12 @@ public class GameManagementServiceTests { private void addSubmissionHelper(Player player, int problemIndex, PlayerCode playerCode, int numCorrect) { Submission submission = new Submission(); submission.setNumCorrect(numCorrect); - submission.setNumTestCases(TestFields.NUM_PROBLEMS); + submission.setNumTestCases(1); submission.setStartTime(Instant.now()); submission.setPlayerCode(playerCode); player.getSubmissions().add(submission); - if (numCorrect == TestFields.NUM_PROBLEMS) { + if (numCorrect == 1) { boolean[] solved = player.getSolved(); solved[problemIndex] = true; player.setSolved(solved); @@ -380,7 +380,7 @@ public void submitSolutionSuccess() { submissionDto.setNumTestCases(TestFields.NUM_PROBLEMS); Mockito.doAnswer(new Answer() { public SubmissionDto answer(InvocationOnMock invocation) { - addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID), 0, TestFields.PLAYER_CODE_1, 10); + addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID), 0, TestFields.PLAYER_CODE_1, 1); game.setAllSolved(true); return submissionDto; }}) @@ -421,8 +421,8 @@ public void sendAllSolvedSocketUpdate() { Game game = gameService.getGameFromRoomId(TestFields.ROOM_ID); // Add submissions for the first two users. - addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID), 0, TestFields.PLAYER_CODE_1, 10); - addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID_2), 0, TestFields.PLAYER_CODE_1, 10); + addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID), 0, TestFields.PLAYER_CODE_1, 1); + addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID_2), 0, TestFields.PLAYER_CODE_1, 1); SubmissionRequest request = new SubmissionRequest(); request.setLanguage(TestFields.PYTHON_LANGUAGE); @@ -435,7 +435,7 @@ public void sendAllSolvedSocketUpdate() { submissionDto.setNumTestCases(TestFields.NUM_PROBLEMS); Mockito.doAnswer(new Answer() { public SubmissionDto answer(InvocationOnMock invocation) { - addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID_3), 0,TestFields.PLAYER_CODE_1, 10); + addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID_3), 0,TestFields.PLAYER_CODE_1, 1); game.setAllSolved(true); return submissionDto; }}) @@ -481,7 +481,7 @@ public void submitSolutionNotAllSolved() { submissionDto.setNumTestCases(TestFields.NUM_PROBLEMS); Mockito.doAnswer(new Answer() { public SubmissionDto answer(InvocationOnMock invocation) { - addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID), 0, TestFields.PLAYER_CODE_1, 10); + addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID), 0, TestFields.PLAYER_CODE_1, 1); return submissionDto; }}) .when(submitService).submitSolution(game, request); @@ -1041,6 +1041,8 @@ public void createGameReportMultipleAttributes() { * 3. Multiple problems, with submissions for each. */ + // User already includes many submission group reports. + Room room = new Room(); room.setRoomId(TestFields.ROOM_ID); room.setDuration(120000000L); @@ -1085,7 +1087,7 @@ public void createGameReportMultipleAttributes() { correctSubmissionDto.setNumTestCases(TestFields.NUM_PROBLEMS); Mockito.doAnswer(new Answer() { public SubmissionDto answer(InvocationOnMock invocation) { - addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID), 0, TestFields.PLAYER_CODE_1, 10); + addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID), 0, TestFields.PLAYER_CODE_1, 1); game.setAllSolved(true); return correctSubmissionDto; }}) @@ -1099,7 +1101,7 @@ public SubmissionDto answer(InvocationOnMock invocation) { correctSubmissionDto.setNumTestCases(TestFields.NUM_PROBLEMS); Mockito.doAnswer(new Answer() { public SubmissionDto answer(InvocationOnMock invocation) { - addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID_3), 0, TestFields.PLAYER_CODE_1, 10); + addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID_3), 0, TestFields.PLAYER_CODE_1, 1); game.setAllSolved(true); return correctSubmissionDto; }}) @@ -1113,13 +1115,13 @@ public SubmissionDto answer(InvocationOnMock invocation) { partialSubmission.setCode(TestFields.PYTHON_CODE); partialSubmission.setInitiator(UserMapper.toDto(user3)); - // First correct submission for problem 0 with user1. + // Incorrect submission for problem 0 with user1. SubmissionDto partialSubmissionDto = new SubmissionDto(); partialSubmissionDto.setNumCorrect(TestFields.NUM_PROBLEMS); partialSubmissionDto.setNumTestCases(TestFields.NUM_PROBLEMS); Mockito.doAnswer(new Answer() { public SubmissionDto answer(InvocationOnMock invocation) { - addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID_3), 1, TestFields.PLAYER_CODE_2, 5); + addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID_3), 1, TestFields.PLAYER_CODE_2, 0); game.setAllSolved(true); return partialSubmissionDto; }}) @@ -1127,9 +1129,7 @@ public SubmissionDto answer(InvocationOnMock invocation) { gameService.submitSolution(TestFields.ROOM_ID, partialSubmission); // Set manually end game, and trigger game report creation directly. - EndGameRequest request = new EndGameRequest(); - request.setInitiator(UserMapper.toDto(user1)); - gameService.manuallyEndGame(TestFields.ROOM_ID, request); + game.setGameEnded(true); GameReport gameReport = gameService.createGameReport(game); // Confirm that the game report, account, and users are saved. @@ -1138,7 +1138,7 @@ public SubmissionDto answer(InvocationOnMock invocation) { verify(userRepository).save(eq(user3)); verify(userRepository).save(eq(user4)); - + assertEquals(gameReport.getGameEndType(), GameEndType.MANUAL_END); } @Test From 4325c7954b0afa07634333ddb2373c28c52121dc Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Tue, 6 Jul 2021 08:35:20 -0700 Subject: [PATCH 21/39] Add the overall problems solved and test cases passed statistics. --- .../main/model/report/GameReport.java | 6 +++++ .../main/service/GameManagementService.java | 3 +++ .../service/GameManagementServiceTests.java | 24 +++++++++++-------- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/codejoust/main/model/report/GameReport.java b/src/main/java/com/codejoust/main/model/report/GameReport.java index 34923f7fe..4bc00c9ef 100644 --- a/src/main/java/com/codejoust/main/model/report/GameReport.java +++ b/src/main/java/com/codejoust/main/model/report/GameReport.java @@ -53,6 +53,12 @@ public class GameReport { private int numTestCases; + // The average (mean) number of test cases passed. + private Double averageTestCasesPassed; + + // The average (mean) number of problems solved. + private Double averageProblemsSolved; + // The start time of the game private Instant createdDateTime; diff --git a/src/main/java/com/codejoust/main/service/GameManagementService.java b/src/main/java/com/codejoust/main/service/GameManagementService.java index 30c797424..1c791ab37 100644 --- a/src/main/java/com/codejoust/main/service/GameManagementService.java +++ b/src/main/java/com/codejoust/main/service/GameManagementService.java @@ -404,6 +404,9 @@ public GameReport createGameReport(Game game) { gameReport.setGameEndType(GameEndType.TIME_UP); } + gameReport.setAverageTestCasesPassed(Arrays.stream(totalTestCasesPassed).sum() / numPlayers); + gameReport.setAverageProblemsSolved((double) Arrays.stream(userSolved).sum() / numPlayers); + gameReportRepository.save(gameReport); // Iterate through all room users, players and spectators included. diff --git a/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java b/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java index d805d252b..308c9245f 100644 --- a/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java +++ b/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java @@ -98,6 +98,7 @@ public class GameManagementServiceTests { // Helper method to add a dummy submission to a Player object private void addSubmissionHelper(Player player, int problemIndex, PlayerCode playerCode, int numCorrect) { Submission submission = new Submission(); + submission.setProblemIndex(problemIndex); submission.setNumCorrect(numCorrect); submission.setNumTestCases(1); submission.setStartTime(Instant.now()); @@ -1110,23 +1111,23 @@ public SubmissionDto answer(InvocationOnMock invocation) { gameService.submitSolution(TestFields.ROOM_ID, correctSubmission); // Partially correct submission for problem 1 with user3. - SubmissionRequest partialSubmission = new SubmissionRequest(); - partialSubmission.setLanguage(TestFields.PYTHON_LANGUAGE); - partialSubmission.setCode(TestFields.PYTHON_CODE); - partialSubmission.setInitiator(UserMapper.toDto(user3)); + SubmissionRequest incorrectSubmission = new SubmissionRequest(); + incorrectSubmission.setLanguage(TestFields.PYTHON_LANGUAGE); + incorrectSubmission.setCode(TestFields.PYTHON_CODE); + incorrectSubmission.setInitiator(UserMapper.toDto(user3)); // Incorrect submission for problem 0 with user1. - SubmissionDto partialSubmissionDto = new SubmissionDto(); - partialSubmissionDto.setNumCorrect(TestFields.NUM_PROBLEMS); - partialSubmissionDto.setNumTestCases(TestFields.NUM_PROBLEMS); + SubmissionDto incorrectSubmissionDto = new SubmissionDto(); + incorrectSubmissionDto.setNumCorrect(TestFields.NUM_PROBLEMS); + incorrectSubmissionDto.setNumTestCases(TestFields.NUM_PROBLEMS); Mockito.doAnswer(new Answer() { public SubmissionDto answer(InvocationOnMock invocation) { addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID_3), 1, TestFields.PLAYER_CODE_2, 0); game.setAllSolved(true); - return partialSubmissionDto; + return incorrectSubmissionDto; }}) - .when(submitService).submitSolution(game, partialSubmission); - gameService.submitSolution(TestFields.ROOM_ID, partialSubmission); + .when(submitService).submitSolution(game, incorrectSubmission); + gameService.submitSolution(TestFields.ROOM_ID, incorrectSubmission); // Set manually end game, and trigger game report creation directly. game.setGameEnded(true); @@ -1138,6 +1139,9 @@ public SubmissionDto answer(InvocationOnMock invocation) { verify(userRepository).save(eq(user3)); verify(userRepository).save(eq(user4)); + // Check assertions for top-level report variables. + assertEquals(gameReport.getCreatedDateTime(), game.getGameTimer().getStartTime()); + assertEquals(gameReport.getNumTestCases(), 2); assertEquals(gameReport.getGameEndType(), GameEndType.MANUAL_END); } From 71f715c908dfa97717cd043d4a12c96e70d2a6a5 Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Tue, 6 Jul 2021 09:20:22 -0700 Subject: [PATCH 22/39] Add a second incorrect attempt that factors only into attempt count. --- .../service/GameManagementServiceTests.java | 61 +++++++++++++------ 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java b/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java index 308c9245f..e322746b2 100644 --- a/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java +++ b/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java @@ -57,9 +57,11 @@ import com.codejoust.main.model.Room; import com.codejoust.main.model.User; import com.codejoust.main.model.problem.Problem; +import com.codejoust.main.model.problem.ProblemContainer; import com.codejoust.main.model.problem.ProblemDifficulty; import com.codejoust.main.model.report.GameEndType; import com.codejoust.main.model.report.GameReport; +import com.codejoust.main.model.report.SubmissionGroupReport; @ExtendWith(MockitoExtension.class) public class GameManagementServiceTests { @@ -1077,20 +1079,16 @@ public void createGameReportMultipleAttributes() { gameService.createAddGameFromRoom(room); Game game = gameService.getGameFromRoomId(room.getRoomId()); + // First correct submission for problem 0 with user1. SubmissionRequest correctSubmission = new SubmissionRequest(); correctSubmission.setLanguage(TestFields.PYTHON_LANGUAGE); correctSubmission.setCode(TestFields.PYTHON_CODE); correctSubmission.setInitiator(UserMapper.toDto(user1)); - - // First correct submission for problem 0 with user1. - SubmissionDto correctSubmissionDto = new SubmissionDto(); - correctSubmissionDto.setNumCorrect(TestFields.NUM_PROBLEMS); - correctSubmissionDto.setNumTestCases(TestFields.NUM_PROBLEMS); Mockito.doAnswer(new Answer() { public SubmissionDto answer(InvocationOnMock invocation) { addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID), 0, TestFields.PLAYER_CODE_1, 1); game.setAllSolved(true); - return correctSubmissionDto; + return new SubmissionDto(); }}) .when(submitService).submitSolution(game, correctSubmission); @@ -1098,33 +1096,37 @@ public SubmissionDto answer(InvocationOnMock invocation) { // Second correct submission for problem 0 with user3. correctSubmission.setInitiator(UserMapper.toDto(user3)); - correctSubmissionDto.setNumCorrect(TestFields.NUM_PROBLEMS); - correctSubmissionDto.setNumTestCases(TestFields.NUM_PROBLEMS); Mockito.doAnswer(new Answer() { public SubmissionDto answer(InvocationOnMock invocation) { addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID_3), 0, TestFields.PLAYER_CODE_1, 1); game.setAllSolved(true); - return correctSubmissionDto; + return new SubmissionDto(); }}) .when(submitService).submitSolution(game, correctSubmission); gameService.submitSolution(TestFields.ROOM_ID, correctSubmission); - // Partially correct submission for problem 1 with user3. + // Incorrect submission for problem 1 with user3. SubmissionRequest incorrectSubmission = new SubmissionRequest(); incorrectSubmission.setLanguage(TestFields.PYTHON_LANGUAGE); incorrectSubmission.setCode(TestFields.PYTHON_CODE); incorrectSubmission.setInitiator(UserMapper.toDto(user3)); + Mockito.doAnswer(new Answer() { + public SubmissionDto answer(InvocationOnMock invocation) { + addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID_3), 1, TestFields.PLAYER_CODE_2, 0); + game.setAllSolved(true); + return new SubmissionDto(); + }}) + .when(submitService).submitSolution(game, incorrectSubmission); + gameService.submitSolution(TestFields.ROOM_ID, incorrectSubmission); // Incorrect submission for problem 0 with user1. - SubmissionDto incorrectSubmissionDto = new SubmissionDto(); - incorrectSubmissionDto.setNumCorrect(TestFields.NUM_PROBLEMS); - incorrectSubmissionDto.setNumTestCases(TestFields.NUM_PROBLEMS); + incorrectSubmission.setInitiator(UserMapper.toDto(user1)); Mockito.doAnswer(new Answer() { public SubmissionDto answer(InvocationOnMock invocation) { - addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID_3), 1, TestFields.PLAYER_CODE_2, 0); + addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID), 0, TestFields.PLAYER_CODE_2, 0); game.setAllSolved(true); - return incorrectSubmissionDto; + return new SubmissionDto(); }}) .when(submitService).submitSolution(game, incorrectSubmission); gameService.submitSolution(TestFields.ROOM_ID, incorrectSubmission); @@ -1140,9 +1142,32 @@ public SubmissionDto answer(InvocationOnMock invocation) { verify(userRepository).save(eq(user4)); // Check assertions for top-level report variables. - assertEquals(gameReport.getCreatedDateTime(), game.getGameTimer().getStartTime()); - assertEquals(gameReport.getNumTestCases(), 2); - assertEquals(gameReport.getGameEndType(), GameEndType.MANUAL_END); + assertEquals(game.getGameTimer().getStartTime(), gameReport.getCreatedDateTime()); + assertEquals(2, gameReport.getNumTestCases()); + assertEquals((double) 2 / 3, gameReport.getAverageProblemsSolved()); + assertEquals((double) 2 / 3, gameReport.getAverageTestCasesPassed()); + assertEquals(GameEndType.MANUAL_END, gameReport.getGameEndType()); + + // Check assertions for individual problem containers. + assertEquals(2, gameReport.getProblemContainers().size()); + ProblemContainer problemContainer1 = gameReport.getProblemContainers().get(0); + assertEquals(1, problemContainer1.getAverageAttemptCount()); + assertEquals((double) 2 / 3, problemContainer1.getAverageTestCasesPassed()); + assertEquals(TestFields.problem1().getName(), problemContainer1.getProblem().getName()); + assertEquals(1, problemContainer1.getTestCaseCount()); + assertEquals(2, problemContainer1.getUserSolvedCount()); + ProblemContainer problemContainer2 = gameReport.getProblemContainers().get(1); + assertEquals((double) 1 / 3, problemContainer2.getAverageAttemptCount()); + assertEquals(0, problemContainer2.getAverageTestCasesPassed()); + assertEquals(TestFields.problem2().getName(), problemContainer2.getProblem().getName()); + assertEquals(1, problemContainer2.getTestCaseCount()); + assertEquals(0, problemContainer2.getUserSolvedCount()); + + // Check assertions for each user and submission group. + assertEquals(3, gameReport.getUsers().size()); + assertEquals(1, gameReport.getUsers().get(0).getSubmissionGroupReports().size()); + SubmissionGroupReport submissionGroupReport1 = gameReport.getUsers().get(0).getSubmissionGroupReports().get(0); + assertEquals(gameReport.getGameReportId(), submissionGroupReport1.getGameReportId()); } @Test From f43d8cedce16bb025343619d989a92bd90078acd Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Tue, 6 Jul 2021 09:36:41 -0700 Subject: [PATCH 23/39] Add final test statistics and add explanatory top-level comment. --- .../service/GameManagementServiceTests.java | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java b/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java index e322746b2..b17d8aef2 100644 --- a/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java +++ b/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java @@ -1042,10 +1042,21 @@ public void createGameReportMultipleAttributes() { * 1. Logged-in users, logged-out users, players, and spectators. * 2. Correct and partially correct submissions. * 3. Multiple problems, with submissions for each. + * + * The test goes through the following steps: + * 1. Create a room with four users: two with same account, one + * anonymous, and one anonymous spectator. + * 2. Add two different problems to the room, and start the game. + * 3. Add submissions: user1 submits correctly to the first problem, + * then a new incorrect submission to the first problem. user2 does + * not submit. user3 submits correctly to the first problem, then + * incorrectly to the second problem. + * 4. Check that the game and relevant account and user objects save. + * 5. Check assertions for the general game report statistics, + * each of the problem containers, and each of the users' submission + * group reports. */ - // User already includes many submission group reports. - Room room = new Room(); room.setRoomId(TestFields.ROOM_ID); room.setDuration(120000000L); @@ -1168,6 +1179,23 @@ public SubmissionDto answer(InvocationOnMock invocation) { assertEquals(1, gameReport.getUsers().get(0).getSubmissionGroupReports().size()); SubmissionGroupReport submissionGroupReport1 = gameReport.getUsers().get(0).getSubmissionGroupReports().get(0); assertEquals(gameReport.getGameReportId(), submissionGroupReport1.getGameReportId()); + assertEquals(1, submissionGroupReport1.getNumTestCasesPassed()); + assertEquals("10", submissionGroupReport1.getProblemsSolved()); + assertEquals(2, submissionGroupReport1.getSubmissionReports().size()); + + assertEquals(1, gameReport.getUsers().get(1).getSubmissionGroupReports().size()); + SubmissionGroupReport submissionGroupReport2 = gameReport.getUsers().get(1).getSubmissionGroupReports().get(0); + assertEquals(gameReport.getGameReportId(), submissionGroupReport2.getGameReportId()); + assertEquals(0, submissionGroupReport2.getNumTestCasesPassed()); + assertEquals("00", submissionGroupReport2.getProblemsSolved()); + assertEquals(0, submissionGroupReport2.getSubmissionReports().size()); + + assertEquals(1, gameReport.getUsers().get(2).getSubmissionGroupReports().size()); + SubmissionGroupReport submissionGroupReport3 = gameReport.getUsers().get(2).getSubmissionGroupReports().get(0); + assertEquals(gameReport.getGameReportId(), submissionGroupReport3.getGameReportId()); + assertEquals(1, submissionGroupReport3.getNumTestCasesPassed()); + assertEquals("10", submissionGroupReport3.getProblemsSolved()); + assertEquals(2, submissionGroupReport3.getSubmissionReports().size()); } @Test From b22927e37f765637997e4e57105e37ccf42276bf Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Tue, 6 Jul 2021 09:42:21 -0700 Subject: [PATCH 24/39] Add the create two game reports test. --- .../service/GameManagementServiceTests.java | 42 ++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java b/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java index b17d8aef2..606a113d8 100644 --- a/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java +++ b/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java @@ -1059,7 +1059,7 @@ public void createGameReportMultipleAttributes() { Room room = new Room(); room.setRoomId(TestFields.ROOM_ID); - room.setDuration(120000000L); + room.setDuration(120L); User user1 = new User(); user1.setNickname(TestFields.NICKNAME); user1.setUserId(TestFields.USER_ID); @@ -1198,6 +1198,46 @@ public SubmissionDto answer(InvocationOnMock invocation) { assertEquals(2, submissionGroupReport3.getSubmissionReports().size()); } + @Test + public void createTwoGameReports() { + /** + * Create two games to verify that two submission group reports are + * made with the same user. + * 1. Create room with one user and one problem. + * 2. Start and immediately end game, manually triggering the create + * game report. + * 3. Start and immediately end game again, and check that user has two + * submission group reports. + */ + + Room room = new Room(); + room.setRoomId(TestFields.ROOM_ID); + room.setDuration(120L); + User user1 = new User(); + user1.setNickname(TestFields.NICKNAME); + user1.setUserId(TestFields.USER_ID); + user1.setAccount(TestFields.account1()); + room.addUser(user1); + room.setHost(user1); + + List problems = new ArrayList<>(); + problems.add(TestFields.problem1()); + room.setProblems(problems); + room.setNumProblems(problems.size()); + + gameService.createAddGameFromRoom(room); + Game game = gameService.getGameFromRoomId(room.getRoomId()); + game.setGameEnded(true); + gameService.createGameReport(game); + + gameService.createAddGameFromRoom(room); + game = gameService.getGameFromRoomId(room.getRoomId()); + game.setGameEnded(true); + GameReport gameReport = gameService.createGameReport(game); + + assertEquals(2, gameReport.getUsers().get(0).getSubmissionGroupReports().size()); + } + @Test public void conditionallyUpdateSocketInfoSuccess() { Room room = new Room(); From 832341a720a581ad0eded33cd4c96addad2459ce Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Tue, 6 Jul 2021 10:06:28 -0700 Subject: [PATCH 25/39] Add final test for game end type. --- .../codejoust/main/service/GameManagementServiceTests.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java b/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java index 606a113d8..42a902d20 100644 --- a/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java +++ b/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java @@ -1207,7 +1207,7 @@ public void createTwoGameReports() { * 2. Start and immediately end game, manually triggering the create * game report. * 3. Start and immediately end game again, and check that user has two - * submission group reports. + * submission group reports, as well as the all solved game end type. */ Room room = new Room(); @@ -1232,9 +1232,10 @@ public void createTwoGameReports() { gameService.createAddGameFromRoom(room); game = gameService.getGameFromRoomId(room.getRoomId()); - game.setGameEnded(true); + game.setAllSolved(true); GameReport gameReport = gameService.createGameReport(game); + assertEquals(GameEndType.ALL_SOLVED, gameReport.getGameEndType()); assertEquals(2, gameReport.getUsers().get(0).getSubmissionGroupReports().size()); } From ed98efbe1b818856f2e1268b2ea9702157eec5fb Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Tue, 6 Jul 2021 15:34:26 -0700 Subject: [PATCH 26/39] Remove unnecessary user repository save. --- .../com/codejoust/main/service/GameManagementService.java | 6 ------ .../codejoust/main/service/GameManagementServiceTests.java | 2 -- 2 files changed, 8 deletions(-) diff --git a/src/main/java/com/codejoust/main/service/GameManagementService.java b/src/main/java/com/codejoust/main/service/GameManagementService.java index 1c791ab37..221904a12 100644 --- a/src/main/java/com/codejoust/main/service/GameManagementService.java +++ b/src/main/java/com/codejoust/main/service/GameManagementService.java @@ -13,7 +13,6 @@ import com.codejoust.main.dao.AccountRepository; import com.codejoust.main.dao.GameReportRepository; import com.codejoust.main.dao.RoomRepository; -import com.codejoust.main.dao.UserRepository; import com.codejoust.main.dto.game.EndGameRequest; import com.codejoust.main.dto.game.GameDto; import com.codejoust.main.dto.game.GameMapper; @@ -57,7 +56,6 @@ public class GameManagementService { private final RoomRepository repository; private final GameReportRepository gameReportRepository; - private final UserRepository userRepository; private final AccountRepository accountRepository; private final SocketService socketService; private final LiveGameService liveGameService; @@ -69,7 +67,6 @@ public class GameManagementService { @Autowired protected GameManagementService(RoomRepository repository, GameReportRepository gameReportRepository, - UserRepository userRepository, AccountRepository accountRepository, SocketService socketService, LiveGameService liveGameService, @@ -78,7 +75,6 @@ protected GameManagementService(RoomRepository repository, ProblemService problemService) { this.repository = repository; this.gameReportRepository = gameReportRepository; - this.userRepository = userRepository; this.accountRepository = accountRepository; this.socketService = socketService; this.liveGameService = liveGameService; @@ -418,8 +414,6 @@ public GameReport createGameReport(Game game) { account.addGameReport(gameReport); addedAccounts.add(account); accountRepository.save(account); - } else if (account == null) { - userRepository.save(user); } } diff --git a/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java b/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java index 42a902d20..1d18b1b59 100644 --- a/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java +++ b/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java @@ -1149,8 +1149,6 @@ public SubmissionDto answer(InvocationOnMock invocation) { // Confirm that the game report, account, and users are saved. verify(gameReportRepository).save(Mockito.any(GameReport.class)); verify(accountRepository).save(eq(user1.getAccount())); - verify(userRepository).save(eq(user3)); - verify(userRepository).save(eq(user4)); // Check assertions for top-level report variables. assertEquals(game.getGameTimer().getStartTime(), gameReport.getCreatedDateTime()); From 6cf7dd80ae1381cf4a7f6ff6c632265efe4aa190 Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Tue, 6 Jul 2021 23:41:16 -0700 Subject: [PATCH 27/39] Add problem service delete method. --- .../main/dao/ProblemContainerRepository.java | 14 +++++++++ .../codejoust/main/dao/RoomRepository.java | 4 +++ .../java/com/codejoust/main/model/Room.java | 4 +++ .../main/service/ProblemService.java | 30 +++++++++++++++++++ 4 files changed, 52 insertions(+) create mode 100644 src/main/java/com/codejoust/main/dao/ProblemContainerRepository.java diff --git a/src/main/java/com/codejoust/main/dao/ProblemContainerRepository.java b/src/main/java/com/codejoust/main/dao/ProblemContainerRepository.java new file mode 100644 index 000000000..8d48e42cd --- /dev/null +++ b/src/main/java/com/codejoust/main/dao/ProblemContainerRepository.java @@ -0,0 +1,14 @@ +package com.codejoust.main.dao; + +import org.springframework.data.repository.CrudRepository; + +import java.util.List; + +import com.codejoust.main.model.problem.Problem; +import com.codejoust.main.model.problem.ProblemContainer; + +// This will be AUTO IMPLEMENTED by Spring into a Bean called +// problemContainerRepository; CRUD refers Create, Read, Update, Delete +public interface ProblemContainerRepository extends CrudRepository { + List findAllByProblem(Problem problem); +} diff --git a/src/main/java/com/codejoust/main/dao/RoomRepository.java b/src/main/java/com/codejoust/main/dao/RoomRepository.java index b4132eb7d..89d469be2 100644 --- a/src/main/java/com/codejoust/main/dao/RoomRepository.java +++ b/src/main/java/com/codejoust/main/dao/RoomRepository.java @@ -1,5 +1,7 @@ package com.codejoust.main.dao; +import java.util.List; + import com.codejoust.main.model.Room; import org.springframework.data.repository.CrudRepository; @@ -8,4 +10,6 @@ public interface RoomRepository extends CrudRepository { // Auto generated by CrudRepository Room findRoomByRoomId(String roomId); + + List findByProblems_ProblemId(String problemId); } diff --git a/src/main/java/com/codejoust/main/model/Room.java b/src/main/java/com/codejoust/main/model/Room.java index 5323ac7df..8b9fd597e 100644 --- a/src/main/java/com/codejoust/main/model/Room.java +++ b/src/main/java/com/codejoust/main/model/Room.java @@ -148,4 +148,8 @@ public boolean isFull() { return users.size() >= size; } + + public boolean removeProblem(Problem problem) { + return problems.remove(problem); + } } diff --git a/src/main/java/com/codejoust/main/service/ProblemService.java b/src/main/java/com/codejoust/main/service/ProblemService.java index 0a31f1bd3..d38a091d3 100644 --- a/src/main/java/com/codejoust/main/service/ProblemService.java +++ b/src/main/java/com/codejoust/main/service/ProblemService.java @@ -1,8 +1,10 @@ package com.codejoust.main.service; import com.codejoust.main.dao.AccountRepository; +import com.codejoust.main.dao.ProblemContainerRepository; import com.codejoust.main.dao.ProblemRepository; import com.codejoust.main.dao.ProblemTagRepository; +import com.codejoust.main.dao.RoomRepository; import com.codejoust.main.dto.account.AccountRole; import com.codejoust.main.dto.problem.CreateProblemRequest; import com.codejoust.main.dto.problem.CreateProblemTagRequest; @@ -16,7 +18,9 @@ import com.codejoust.main.exception.ProblemError; import com.codejoust.main.exception.api.ApiException; import com.codejoust.main.model.Account; +import com.codejoust.main.model.Room; import com.codejoust.main.model.problem.Problem; +import com.codejoust.main.model.problem.ProblemContainer; import com.codejoust.main.model.problem.ProblemDifficulty; import com.codejoust.main.model.problem.ProblemIOType; import com.codejoust.main.model.problem.ProblemInput; @@ -47,7 +51,9 @@ public class ProblemService { private final FirebaseService service; private final ProblemRepository problemRepository; private final ProblemTagRepository problemTagRepository; + private final ProblemContainerRepository problemContainerRepository; private final AccountRepository accountRepository; + private final RoomRepository roomRepository; private final List defaultCodeGeneratorServiceList; private final Random random = new Random(); private final Gson gson = new Gson(); @@ -56,13 +62,17 @@ public class ProblemService { public ProblemService(FirebaseService service, ProblemRepository problemRepository, ProblemTagRepository problemTagRepository, + ProblemContainerRepository problemContainerRepository, AccountRepository accountRepository, + RoomRepository roomRepository, List defaultCodeGeneratorServiceList) { this.service = service; this.problemRepository = problemRepository; this.problemTagRepository = problemTagRepository; + this.problemContainerRepository = problemContainerRepository; this.accountRepository = accountRepository; + this.roomRepository = roomRepository; this.defaultCodeGeneratorServiceList = defaultCodeGeneratorServiceList; } @@ -243,6 +253,26 @@ public ProblemDto deleteProblem(String problemId, String token) { } service.verifyTokenMatchesUid(token, problem.getOwner().getUid()); + + /** + * Before the problem can be deleted, set all foreign key references + * to the Problem within all ProblemContainers to null. + * See https://stackoverflow.com/a/10030873/7517518. + */ + List problemContainers = problemContainerRepository.findAllByProblem(problem); + for (ProblemContainer problemContainer : problemContainers) { + problemContainer.setProblem(null); + problemContainerRepository.save(problemContainer); + } + + // Remove this problem from all the associated rooms. + List rooms = roomRepository.findByProblems_ProblemId(problemId); + for (Room room : rooms) { + room.removeProblem(problem); + roomRepository.save(room); + } + + // Also if it's currently in any room. toDto problemRepository.delete(problem); return ProblemMapper.toDto(problem); From bd6277807e1cd243ab38f15512fbcb38bee5fc80 Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Wed, 7 Jul 2021 21:07:07 -0700 Subject: [PATCH 28/39] Remove extraneous comment. --- src/main/java/com/codejoust/main/service/ProblemService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/codejoust/main/service/ProblemService.java b/src/main/java/com/codejoust/main/service/ProblemService.java index d38a091d3..afd0f6a33 100644 --- a/src/main/java/com/codejoust/main/service/ProblemService.java +++ b/src/main/java/com/codejoust/main/service/ProblemService.java @@ -272,7 +272,6 @@ public ProblemDto deleteProblem(String problemId, String token) { roomRepository.save(room); } - // Also if it's currently in any room. toDto problemRepository.delete(problem); return ProblemMapper.toDto(problem); From da88286751cbadabc0112e603de8c093206ced54 Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Tue, 20 Jul 2021 21:03:19 -0700 Subject: [PATCH 29/39] Create the new ReportService as well as modified the other corresponding tests. --- .../main/service/GameManagementService.java | 140 +-------- .../codejoust/main/service/ReportService.java | 154 ++++++++++ .../main/task/CreateGameReportTask.java | 12 +- .../service/GameManagementServiceTests.java | 237 +-------------- .../main/service/ReportServiceTests.java | 277 ++++++++++++++++++ .../main/task/CreateGameReportTaskTests.java | 10 +- .../main/util/UtilityTestMethods.java | 21 ++ 7 files changed, 474 insertions(+), 377 deletions(-) create mode 100644 src/main/java/com/codejoust/main/service/ReportService.java create mode 100644 src/test/java/com/codejoust/main/service/ReportServiceTests.java diff --git a/src/main/java/com/codejoust/main/service/GameManagementService.java b/src/main/java/com/codejoust/main/service/GameManagementService.java index 221904a12..d670ecf09 100644 --- a/src/main/java/com/codejoust/main/service/GameManagementService.java +++ b/src/main/java/com/codejoust/main/service/GameManagementService.java @@ -1,17 +1,10 @@ package com.codejoust.main.service; -import java.time.Duration; -import java.time.Instant; -import java.util.Arrays; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.Timer; -import com.codejoust.main.dao.AccountRepository; -import com.codejoust.main.dao.GameReportRepository; import com.codejoust.main.dao.RoomRepository; import com.codejoust.main.dto.game.EndGameRequest; import com.codejoust.main.dto.game.GameDto; @@ -20,7 +13,6 @@ import com.codejoust.main.dto.game.PlayAgainRequest; import com.codejoust.main.dto.game.StartGameRequest; import com.codejoust.main.dto.game.SubmissionDto; -import com.codejoust.main.dto.game.SubmissionMapper; import com.codejoust.main.dto.game.SubmissionRequest; import com.codejoust.main.dto.room.RoomDto; import com.codejoust.main.dto.room.RoomMapper; @@ -33,15 +25,9 @@ import com.codejoust.main.game_object.GameTimer; import com.codejoust.main.game_object.Player; import com.codejoust.main.game_object.PlayerCode; -import com.codejoust.main.game_object.Submission; -import com.codejoust.main.model.Account; import com.codejoust.main.model.Room; import com.codejoust.main.model.User; import com.codejoust.main.model.problem.Problem; -import com.codejoust.main.model.problem.ProblemContainer; -import com.codejoust.main.model.report.GameEndType; -import com.codejoust.main.model.report.GameReport; -import com.codejoust.main.model.report.SubmissionGroupReport; import com.codejoust.main.task.CreateGameReportTask; import com.codejoust.main.task.EndGameTimerTask; import com.codejoust.main.util.Utility; @@ -55,32 +41,29 @@ public class GameManagementService { private final RoomRepository repository; - private final GameReportRepository gameReportRepository; - private final AccountRepository accountRepository; private final SocketService socketService; private final LiveGameService liveGameService; private final NotificationService notificationService; private final SubmitService submitService; private final ProblemService problemService; + private final ReportService reportService; private final Map currentGameMap; @Autowired protected GameManagementService(RoomRepository repository, - GameReportRepository gameReportRepository, - AccountRepository accountRepository, SocketService socketService, LiveGameService liveGameService, NotificationService notificationService, SubmitService submitService, - ProblemService problemService) { + ProblemService problemService, + ReportService reportService) { this.repository = repository; - this.gameReportRepository = gameReportRepository; - this.accountRepository = accountRepository; this.socketService = socketService; this.liveGameService = liveGameService; this.notificationService = notificationService; this.submitService = submitService; this.problemService = problemService; + this.reportService = reportService; currentGameMap = new HashMap<>(); } @@ -316,124 +299,11 @@ public void handleEndGame(Game game) { timer.cancel(); } - CreateGameReportTask createGameReportTask = new CreateGameReportTask(this, game); + CreateGameReportTask createGameReportTask = new CreateGameReportTask(reportService, game); Timer createGameReportTimer = new Timer(); createGameReportTimer.schedule(createGameReportTask, GameTimer.DURATION_1 * 1000); } - public GameReport createGameReport(Game game) { - GameReport gameReport = new GameReport(); - int numProblems = game.getProblems().size(); - int numPlayers = game.getPlayers().size(); - - // Initialize the statistic variables for each problem. - int[] userSolved = new int[numProblems]; - double[] totalTestCasesPassed = new double[numProblems]; - double[] totalAttemptCount = new double[numProblems]; - for (Player player : game.getPlayers().values()) { - // For each user, add the relevant submission info. - User user = player.getUser(); - - // Construct the new submission group report for each user. - SubmissionGroupReport submissionGroupReport = new SubmissionGroupReport(); - submissionGroupReport.setGameReportId(gameReport.getGameReportId()); - - // Iterate through each submission and update group statistics. - boolean[] problemsSolved = new boolean[numProblems]; - int[] testCasesPassed = new int[numProblems]; - for (Submission submission : player.getSubmissions()) { - int problemIndex = submission.getProblemIndex(); - totalAttemptCount[problemIndex]++; - - // If the problem was solved, set boolean value to true. - if (submission.getNumTestCases() == submission.getNumCorrect() && !problemsSolved[problemIndex]) { - problemsSolved[problemIndex] = true; - userSolved[problemIndex]++; - } - - // Get the maximum number of test cases passed for each problem. - testCasesPassed[problemIndex] = Math.max(testCasesPassed[problemIndex], submission.getNumCorrect()); - - submissionGroupReport.addSubmissionReport(SubmissionMapper.toSubmissionReport(submission)); - } - - // Iterate through the test cases passed to add to the game total. - for (int i = 0; i < testCasesPassed.length; i++) { - totalTestCasesPassed[i] += testCasesPassed[i]; - } - - // Set the problems and test cases statistics. - submissionGroupReport.setProblemsSolved(compactProblemsSolved(problemsSolved)); - submissionGroupReport.setNumTestCasesPassed(Arrays.stream(testCasesPassed).sum()); - - // Add the submission group report and the user. - user.addSubmissionGroupReport(submissionGroupReport); - gameReport.addUser(user); - } - - // Set problem container variables. - int numTestCases = 0; - List problems = game.getProblems(); - for (int i = 0; i < numProblems; i++) { - Problem problem = problems.get(i); - numTestCases += problem.getTestCases().size(); - - ProblemContainer problemContainer = new ProblemContainer(); - problemContainer.setProblem(problem); - problemContainer.setUserSolvedCount(userSolved[i]); - problemContainer.setTestCaseCount(problem.getTestCases().size()); - problemContainer.setAverageTestCasesPassed(totalTestCasesPassed[i] / numPlayers); - problemContainer.setAverageAttemptCount(totalAttemptCount[i] / numPlayers); - gameReport.addProblemContainer(problemContainer); - } - gameReport.setNumTestCases(numTestCases); - - Instant startTime = game.getGameTimer().getStartTime(); - gameReport.setCreatedDateTime(startTime); - gameReport.setDuration(Duration.between(startTime, Instant.now()).getSeconds()); - - if (game.getGameEnded()) { - gameReport.setGameEndType(GameEndType.MANUAL_END); - } else if (game.getAllSolved()) { - gameReport.setGameEndType(GameEndType.ALL_SOLVED); - } else { - gameReport.setGameEndType(GameEndType.TIME_UP); - } - - gameReport.setAverageTestCasesPassed(Arrays.stream(totalTestCasesPassed).sum() / numPlayers); - gameReport.setAverageProblemsSolved((double) Arrays.stream(userSolved).sum() / numPlayers); - - gameReportRepository.save(gameReport); - - // Iterate through all room users, players and spectators included. - Set addedAccounts = new HashSet<>(); - for (User user : game.getRoom().getUsers()) { - // If account exists and game report is not added, add game report. - Account account = user.getAccount(); - if (account != null && !addedAccounts.contains(account)) { - account.addGameReport(gameReport); - addedAccounts.add(account); - accountRepository.save(account); - } - } - - // Log the completion of the latest game report. - log.info("Created game report for game with Room ID {}", game.getRoom().getRoomId()); - return gameReport; - } - - private String compactProblemsSolved(boolean[] problemsSolved) { - StringBuilder builder = new StringBuilder(); - for (boolean problemSolved : problemsSolved) { - if (problemSolved) { - builder.append("1"); - } else { - builder.append("0"); - } - } - return builder.toString(); - } - protected boolean isGameOver(Game game) { return game.getGameEnded() || game.getAllSolved() || (game.getGameTimer() != null && game.getGameTimer().isTimeUp()); } diff --git a/src/main/java/com/codejoust/main/service/ReportService.java b/src/main/java/com/codejoust/main/service/ReportService.java new file mode 100644 index 000000000..0f7420e28 --- /dev/null +++ b/src/main/java/com/codejoust/main/service/ReportService.java @@ -0,0 +1,154 @@ +package com.codejoust.main.service; + +import java.time.Duration; +import java.time.Instant; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.codejoust.main.dao.AccountRepository; +import com.codejoust.main.dao.GameReportRepository; +import com.codejoust.main.dto.game.SubmissionMapper; +import com.codejoust.main.game_object.Game; +import com.codejoust.main.game_object.Player; +import com.codejoust.main.game_object.Submission; +import com.codejoust.main.model.Account; +import com.codejoust.main.model.User; +import com.codejoust.main.model.problem.Problem; +import com.codejoust.main.model.problem.ProblemContainer; +import com.codejoust.main.model.report.GameEndType; +import com.codejoust.main.model.report.GameReport; +import com.codejoust.main.model.report.SubmissionGroupReport; + +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Log4j2 +@Service +public class ReportService { + + private final GameReportRepository gameReportRepository; + private final AccountRepository accountRepository; + + @Autowired + protected ReportService(GameReportRepository gameReportRepository, + AccountRepository accountRepository) { + this.gameReportRepository = gameReportRepository; + this.accountRepository = accountRepository; + } + + public GameReport createGameReport(Game game) { + GameReport gameReport = new GameReport(); + int numProblems = game.getProblems().size(); + int numPlayers = game.getPlayers().size(); + + // Initialize the statistic variables for each problem. + int[] userSolved = new int[numProblems]; + double[] totalTestCasesPassed = new double[numProblems]; + double[] totalAttemptCount = new double[numProblems]; + for (Player player : game.getPlayers().values()) { + // For each user, add the relevant submission info. + User user = player.getUser(); + + // Construct the new submission group report for each user. + SubmissionGroupReport submissionGroupReport = new SubmissionGroupReport(); + submissionGroupReport.setGameReportId(gameReport.getGameReportId()); + + // Iterate through each submission and update group statistics. + boolean[] problemsSolved = new boolean[numProblems]; + int[] testCasesPassed = new int[numProblems]; + for (Submission submission : player.getSubmissions()) { + int problemIndex = submission.getProblemIndex(); + totalAttemptCount[problemIndex]++; + + // If the problem was solved, set boolean value to true. + if (submission.getNumTestCases() == submission.getNumCorrect() && !problemsSolved[problemIndex]) { + problemsSolved[problemIndex] = true; + userSolved[problemIndex]++; + } + + // Get the maximum number of test cases passed for each problem. + testCasesPassed[problemIndex] = Math.max(testCasesPassed[problemIndex], submission.getNumCorrect()); + + submissionGroupReport.addSubmissionReport(SubmissionMapper.toSubmissionReport(submission)); + } + + // Iterate through the test cases passed to add to the game total. + for (int i = 0; i < testCasesPassed.length; i++) { + totalTestCasesPassed[i] += testCasesPassed[i]; + } + + // Set the problems and test cases statistics. + submissionGroupReport.setProblemsSolved(compactProblemsSolved(problemsSolved)); + submissionGroupReport.setNumTestCasesPassed(Arrays.stream(testCasesPassed).sum()); + + // Add the submission group report and the user. + user.addSubmissionGroupReport(submissionGroupReport); + gameReport.addUser(user); + } + + // Set problem container variables. + int numTestCases = 0; + List problems = game.getProblems(); + for (int i = 0; i < numProblems; i++) { + Problem problem = problems.get(i); + numTestCases += problem.getTestCases().size(); + + ProblemContainer problemContainer = new ProblemContainer(); + problemContainer.setProblem(problem); + problemContainer.setUserSolvedCount(userSolved[i]); + problemContainer.setTestCaseCount(problem.getTestCases().size()); + problemContainer.setAverageTestCasesPassed(totalTestCasesPassed[i] / numPlayers); + problemContainer.setAverageAttemptCount(totalAttemptCount[i] / numPlayers); + gameReport.addProblemContainer(problemContainer); + } + gameReport.setNumTestCases(numTestCases); + + Instant startTime = game.getGameTimer().getStartTime(); + gameReport.setCreatedDateTime(startTime); + gameReport.setDuration(Duration.between(startTime, Instant.now()).getSeconds()); + + if (game.getGameEnded()) { + gameReport.setGameEndType(GameEndType.MANUAL_END); + } else if (game.getAllSolved()) { + gameReport.setGameEndType(GameEndType.ALL_SOLVED); + } else { + gameReport.setGameEndType(GameEndType.TIME_UP); + } + + gameReport.setAverageTestCasesPassed(Arrays.stream(totalTestCasesPassed).sum() / numPlayers); + gameReport.setAverageProblemsSolved((double) Arrays.stream(userSolved).sum() / numPlayers); + + gameReportRepository.save(gameReport); + + // Iterate through all room users, players and spectators included. + Set addedAccounts = new HashSet<>(); + for (User user : game.getRoom().getUsers()) { + // If account exists and game report is not added, add game report. + Account account = user.getAccount(); + if (account != null && !addedAccounts.contains(account)) { + account.addGameReport(gameReport); + addedAccounts.add(account); + accountRepository.save(account); + } + } + + // Log the completion of the latest game report. + log.info("Created game report for game with Room ID {}", game.getRoom().getRoomId()); + return gameReport; + } + + private String compactProblemsSolved(boolean[] problemsSolved) { + StringBuilder builder = new StringBuilder(); + for (boolean problemSolved : problemsSolved) { + if (problemSolved) { + builder.append("1"); + } else { + builder.append("0"); + } + } + return builder.toString(); + } +} diff --git a/src/main/java/com/codejoust/main/task/CreateGameReportTask.java b/src/main/java/com/codejoust/main/task/CreateGameReportTask.java index bdb47c036..a20373559 100644 --- a/src/main/java/com/codejoust/main/task/CreateGameReportTask.java +++ b/src/main/java/com/codejoust/main/task/CreateGameReportTask.java @@ -5,20 +5,20 @@ import com.codejoust.main.exception.TimerError; import com.codejoust.main.exception.api.ApiException; import com.codejoust.main.game_object.Game; -import com.codejoust.main.service.GameManagementService; +import com.codejoust.main.service.ReportService; public class CreateGameReportTask extends TimerTask { private final Game game; - private final GameManagementService gameManagementService; + private final ReportService reportService; - public CreateGameReportTask(GameManagementService gameManagementService, Game game) { - this.gameManagementService = gameManagementService; + public CreateGameReportTask(ReportService reportService, Game game) { + this.reportService = reportService; this.game = game; // Handle potential errors for run(). - if (gameManagementService == null || game == null) { + if (reportService == null || game == null) { throw new ApiException(TimerError.NULL_SETTING); } } @@ -26,7 +26,7 @@ public CreateGameReportTask(GameManagementService gameManagementService, Game ga @Override public void run() { // Create the game report (is the game updated here?). - gameManagementService.createGameReport(game); + reportService.createGameReport(game); } } diff --git a/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java b/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java index 1d18b1b59..032045abd 100644 --- a/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java +++ b/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java @@ -24,10 +24,8 @@ import static org.mockito.Mockito.verify; import java.time.Instant; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.List; import com.codejoust.main.dao.AccountRepository; import com.codejoust.main.dao.GameReportRepository; @@ -52,16 +50,12 @@ import com.codejoust.main.game_object.GameTimer; import com.codejoust.main.game_object.NotificationType; import com.codejoust.main.game_object.Player; -import com.codejoust.main.game_object.PlayerCode; -import com.codejoust.main.game_object.Submission; import com.codejoust.main.model.Room; import com.codejoust.main.model.User; import com.codejoust.main.model.problem.Problem; -import com.codejoust.main.model.problem.ProblemContainer; import com.codejoust.main.model.problem.ProblemDifficulty; -import com.codejoust.main.model.report.GameEndType; import com.codejoust.main.model.report.GameReport; -import com.codejoust.main.model.report.SubmissionGroupReport; +import com.codejoust.main.util.UtilityTestMethods; @ExtendWith(MockitoExtension.class) public class GameManagementServiceTests { @@ -97,23 +91,6 @@ public class GameManagementServiceTests { @InjectMocks private GameManagementService gameService; - // Helper method to add a dummy submission to a Player object - private void addSubmissionHelper(Player player, int problemIndex, PlayerCode playerCode, int numCorrect) { - Submission submission = new Submission(); - submission.setProblemIndex(problemIndex); - submission.setNumCorrect(numCorrect); - submission.setNumTestCases(1); - submission.setStartTime(Instant.now()); - submission.setPlayerCode(playerCode); - - player.getSubmissions().add(submission); - if (numCorrect == 1) { - boolean[] solved = player.getSolved(); - solved[problemIndex] = true; - player.setSolved(solved); - } - } - @Test public void addGetAndRemoveGame() { // Initially, room doesn't exist @@ -383,7 +360,7 @@ public void submitSolutionSuccess() { submissionDto.setNumTestCases(TestFields.NUM_PROBLEMS); Mockito.doAnswer(new Answer() { public SubmissionDto answer(InvocationOnMock invocation) { - addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID), 0, TestFields.PLAYER_CODE_1, 1); + UtilityTestMethods.addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID), 0, TestFields.PLAYER_CODE_1, 1); game.setAllSolved(true); return submissionDto; }}) @@ -424,8 +401,8 @@ public void sendAllSolvedSocketUpdate() { Game game = gameService.getGameFromRoomId(TestFields.ROOM_ID); // Add submissions for the first two users. - addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID), 0, TestFields.PLAYER_CODE_1, 1); - addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID_2), 0, TestFields.PLAYER_CODE_1, 1); + UtilityTestMethods.addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID), 0, TestFields.PLAYER_CODE_1, 1); + UtilityTestMethods.addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID_2), 0, TestFields.PLAYER_CODE_1, 1); SubmissionRequest request = new SubmissionRequest(); request.setLanguage(TestFields.PYTHON_LANGUAGE); @@ -438,7 +415,7 @@ public void sendAllSolvedSocketUpdate() { submissionDto.setNumTestCases(TestFields.NUM_PROBLEMS); Mockito.doAnswer(new Answer() { public SubmissionDto answer(InvocationOnMock invocation) { - addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID_3), 0,TestFields.PLAYER_CODE_1, 1); + UtilityTestMethods.addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID_3), 0,TestFields.PLAYER_CODE_1, 1); game.setAllSolved(true); return submissionDto; }}) @@ -484,7 +461,7 @@ public void submitSolutionNotAllSolved() { submissionDto.setNumTestCases(TestFields.NUM_PROBLEMS); Mockito.doAnswer(new Answer() { public SubmissionDto answer(InvocationOnMock invocation) { - addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID), 0, TestFields.PLAYER_CODE_1, 1); + UtilityTestMethods.addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID), 0, TestFields.PLAYER_CODE_1, 1); return submissionDto; }}) .when(submitService).submitSolution(game, request); @@ -1035,208 +1012,6 @@ public void endGameCancelsTimersCreateReport() { verify(gameReportRepository, after(61300)).save(Mockito.any(GameReport.class)); } - @Test - public void createGameReportMultipleAttributes() { - /** - * Create a game report to test multiple different attributes: - * 1. Logged-in users, logged-out users, players, and spectators. - * 2. Correct and partially correct submissions. - * 3. Multiple problems, with submissions for each. - * - * The test goes through the following steps: - * 1. Create a room with four users: two with same account, one - * anonymous, and one anonymous spectator. - * 2. Add two different problems to the room, and start the game. - * 3. Add submissions: user1 submits correctly to the first problem, - * then a new incorrect submission to the first problem. user2 does - * not submit. user3 submits correctly to the first problem, then - * incorrectly to the second problem. - * 4. Check that the game and relevant account and user objects save. - * 5. Check assertions for the general game report statistics, - * each of the problem containers, and each of the users' submission - * group reports. - */ - - Room room = new Room(); - room.setRoomId(TestFields.ROOM_ID); - room.setDuration(120L); - User user1 = new User(); - user1.setNickname(TestFields.NICKNAME); - user1.setUserId(TestFields.USER_ID); - user1.setAccount(TestFields.account1()); - User user2 = new User(); - user2.setNickname(TestFields.NICKNAME_2); - user2.setUserId(TestFields.USER_ID_2); - user2.setAccount(TestFields.account1()); - User user3 = new User(); - user3.setNickname(TestFields.NICKNAME_3); - user3.setUserId(TestFields.USER_ID_3); - User user4 = new User(); - user4.setNickname(TestFields.NICKNAME_4); - user4.setUserId(TestFields.USER_ID_4); - user4.setSpectator(true); - room.addUser(user1); - room.addUser(user2); - room.addUser(user3); - room.addUser(user4); - room.setHost(user1); - - List problems = new ArrayList<>(); - problems.add(TestFields.problem1()); - problems.add(TestFields.problem2()); - room.setProblems(problems); - room.setNumProblems(problems.size()); - - gameService.createAddGameFromRoom(room); - Game game = gameService.getGameFromRoomId(room.getRoomId()); - - // First correct submission for problem 0 with user1. - SubmissionRequest correctSubmission = new SubmissionRequest(); - correctSubmission.setLanguage(TestFields.PYTHON_LANGUAGE); - correctSubmission.setCode(TestFields.PYTHON_CODE); - correctSubmission.setInitiator(UserMapper.toDto(user1)); - Mockito.doAnswer(new Answer() { - public SubmissionDto answer(InvocationOnMock invocation) { - addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID), 0, TestFields.PLAYER_CODE_1, 1); - game.setAllSolved(true); - return new SubmissionDto(); - }}) - .when(submitService).submitSolution(game, correctSubmission); - - gameService.submitSolution(TestFields.ROOM_ID, correctSubmission); - - // Second correct submission for problem 0 with user3. - correctSubmission.setInitiator(UserMapper.toDto(user3)); - Mockito.doAnswer(new Answer() { - public SubmissionDto answer(InvocationOnMock invocation) { - addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID_3), 0, TestFields.PLAYER_CODE_1, 1); - game.setAllSolved(true); - return new SubmissionDto(); - }}) - .when(submitService).submitSolution(game, correctSubmission); - - gameService.submitSolution(TestFields.ROOM_ID, correctSubmission); - - // Incorrect submission for problem 1 with user3. - SubmissionRequest incorrectSubmission = new SubmissionRequest(); - incorrectSubmission.setLanguage(TestFields.PYTHON_LANGUAGE); - incorrectSubmission.setCode(TestFields.PYTHON_CODE); - incorrectSubmission.setInitiator(UserMapper.toDto(user3)); - Mockito.doAnswer(new Answer() { - public SubmissionDto answer(InvocationOnMock invocation) { - addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID_3), 1, TestFields.PLAYER_CODE_2, 0); - game.setAllSolved(true); - return new SubmissionDto(); - }}) - .when(submitService).submitSolution(game, incorrectSubmission); - gameService.submitSolution(TestFields.ROOM_ID, incorrectSubmission); - - // Incorrect submission for problem 0 with user1. - incorrectSubmission.setInitiator(UserMapper.toDto(user1)); - Mockito.doAnswer(new Answer() { - public SubmissionDto answer(InvocationOnMock invocation) { - addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID), 0, TestFields.PLAYER_CODE_2, 0); - game.setAllSolved(true); - return new SubmissionDto(); - }}) - .when(submitService).submitSolution(game, incorrectSubmission); - gameService.submitSolution(TestFields.ROOM_ID, incorrectSubmission); - - // Set manually end game, and trigger game report creation directly. - game.setGameEnded(true); - GameReport gameReport = gameService.createGameReport(game); - - // Confirm that the game report, account, and users are saved. - verify(gameReportRepository).save(Mockito.any(GameReport.class)); - verify(accountRepository).save(eq(user1.getAccount())); - - // Check assertions for top-level report variables. - assertEquals(game.getGameTimer().getStartTime(), gameReport.getCreatedDateTime()); - assertEquals(2, gameReport.getNumTestCases()); - assertEquals((double) 2 / 3, gameReport.getAverageProblemsSolved()); - assertEquals((double) 2 / 3, gameReport.getAverageTestCasesPassed()); - assertEquals(GameEndType.MANUAL_END, gameReport.getGameEndType()); - - // Check assertions for individual problem containers. - assertEquals(2, gameReport.getProblemContainers().size()); - ProblemContainer problemContainer1 = gameReport.getProblemContainers().get(0); - assertEquals(1, problemContainer1.getAverageAttemptCount()); - assertEquals((double) 2 / 3, problemContainer1.getAverageTestCasesPassed()); - assertEquals(TestFields.problem1().getName(), problemContainer1.getProblem().getName()); - assertEquals(1, problemContainer1.getTestCaseCount()); - assertEquals(2, problemContainer1.getUserSolvedCount()); - ProblemContainer problemContainer2 = gameReport.getProblemContainers().get(1); - assertEquals((double) 1 / 3, problemContainer2.getAverageAttemptCount()); - assertEquals(0, problemContainer2.getAverageTestCasesPassed()); - assertEquals(TestFields.problem2().getName(), problemContainer2.getProblem().getName()); - assertEquals(1, problemContainer2.getTestCaseCount()); - assertEquals(0, problemContainer2.getUserSolvedCount()); - - // Check assertions for each user and submission group. - assertEquals(3, gameReport.getUsers().size()); - assertEquals(1, gameReport.getUsers().get(0).getSubmissionGroupReports().size()); - SubmissionGroupReport submissionGroupReport1 = gameReport.getUsers().get(0).getSubmissionGroupReports().get(0); - assertEquals(gameReport.getGameReportId(), submissionGroupReport1.getGameReportId()); - assertEquals(1, submissionGroupReport1.getNumTestCasesPassed()); - assertEquals("10", submissionGroupReport1.getProblemsSolved()); - assertEquals(2, submissionGroupReport1.getSubmissionReports().size()); - - assertEquals(1, gameReport.getUsers().get(1).getSubmissionGroupReports().size()); - SubmissionGroupReport submissionGroupReport2 = gameReport.getUsers().get(1).getSubmissionGroupReports().get(0); - assertEquals(gameReport.getGameReportId(), submissionGroupReport2.getGameReportId()); - assertEquals(0, submissionGroupReport2.getNumTestCasesPassed()); - assertEquals("00", submissionGroupReport2.getProblemsSolved()); - assertEquals(0, submissionGroupReport2.getSubmissionReports().size()); - - assertEquals(1, gameReport.getUsers().get(2).getSubmissionGroupReports().size()); - SubmissionGroupReport submissionGroupReport3 = gameReport.getUsers().get(2).getSubmissionGroupReports().get(0); - assertEquals(gameReport.getGameReportId(), submissionGroupReport3.getGameReportId()); - assertEquals(1, submissionGroupReport3.getNumTestCasesPassed()); - assertEquals("10", submissionGroupReport3.getProblemsSolved()); - assertEquals(2, submissionGroupReport3.getSubmissionReports().size()); - } - - @Test - public void createTwoGameReports() { - /** - * Create two games to verify that two submission group reports are - * made with the same user. - * 1. Create room with one user and one problem. - * 2. Start and immediately end game, manually triggering the create - * game report. - * 3. Start and immediately end game again, and check that user has two - * submission group reports, as well as the all solved game end type. - */ - - Room room = new Room(); - room.setRoomId(TestFields.ROOM_ID); - room.setDuration(120L); - User user1 = new User(); - user1.setNickname(TestFields.NICKNAME); - user1.setUserId(TestFields.USER_ID); - user1.setAccount(TestFields.account1()); - room.addUser(user1); - room.setHost(user1); - - List problems = new ArrayList<>(); - problems.add(TestFields.problem1()); - room.setProblems(problems); - room.setNumProblems(problems.size()); - - gameService.createAddGameFromRoom(room); - Game game = gameService.getGameFromRoomId(room.getRoomId()); - game.setGameEnded(true); - gameService.createGameReport(game); - - gameService.createAddGameFromRoom(room); - game = gameService.getGameFromRoomId(room.getRoomId()); - game.setAllSolved(true); - GameReport gameReport = gameService.createGameReport(game); - - assertEquals(GameEndType.ALL_SOLVED, gameReport.getGameEndType()); - assertEquals(2, gameReport.getUsers().get(0).getSubmissionGroupReports().size()); - } - @Test public void conditionallyUpdateSocketInfoSuccess() { Room room = new Room(); diff --git a/src/test/java/com/codejoust/main/service/ReportServiceTests.java b/src/test/java/com/codejoust/main/service/ReportServiceTests.java new file mode 100644 index 000000000..327da4dbd --- /dev/null +++ b/src/test/java/com/codejoust/main/service/ReportServiceTests.java @@ -0,0 +1,277 @@ +package com.codejoust.main.service; + +import com.codejoust.main.util.TestFields; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.stubbing.Answer; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; + +import java.util.ArrayList; +import java.util.List; + +import com.codejoust.main.dao.AccountRepository; +import com.codejoust.main.dao.GameReportRepository; +import com.codejoust.main.dao.RoomRepository; +import com.codejoust.main.dao.UserRepository; +import com.codejoust.main.dto.game.SubmissionDto; +import com.codejoust.main.dto.game.SubmissionRequest; +import com.codejoust.main.dto.user.UserMapper; +import com.codejoust.main.game_object.Game; +import com.codejoust.main.model.Room; +import com.codejoust.main.model.User; +import com.codejoust.main.model.problem.Problem; +import com.codejoust.main.model.problem.ProblemContainer; +import com.codejoust.main.model.report.GameEndType; +import com.codejoust.main.model.report.GameReport; +import com.codejoust.main.model.report.SubmissionGroupReport; +import com.codejoust.main.util.UtilityTestMethods; + +@ExtendWith(MockitoExtension.class) +public class ReportServiceTests { + + @Mock + private RoomRepository repository; + + @Mock + private GameReportRepository gameReportRepository; + + @Mock + private AccountRepository accountRepository; + + @Mock + private UserRepository userRepository; + + @Mock + private SocketService socketService; + + @Mock + private SubmitService submitService; + + @Mock + private ProblemService problemService; + + @Mock + private NotificationService notificationService; + + @Mock + private LiveGameService liveGameService; + + @Spy + @InjectMocks + private GameManagementService gameManagementService; + + @Spy + @InjectMocks + private ReportService reportService; + + @Test + public void createGameReportMultipleAttributes() { + /** + * Create a game report to test multiple different attributes: + * 1. Logged-in users, logged-out users, players, and spectators. + * 2. Correct and partially correct submissions. + * 3. Multiple problems, with submissions for each. + * + * The test goes through the following steps: + * 1. Create a room with four users: two with same account, one + * anonymous, and one anonymous spectator. + * 2. Add two different problems to the room, and start the game. + * 3. Add submissions: user1 submits correctly to the first problem, + * then a new incorrect submission to the first problem. user2 does + * not submit. user3 submits correctly to the first problem, then + * incorrectly to the second problem. + * 4. Check that the game and relevant account and user objects save. + * 5. Check assertions for the general game report statistics, + * each of the problem containers, and each of the users' submission + * group reports. + */ + + Room room = new Room(); + room.setRoomId(TestFields.ROOM_ID); + room.setDuration(120L); + User user1 = new User(); + user1.setNickname(TestFields.NICKNAME); + user1.setUserId(TestFields.USER_ID); + user1.setAccount(TestFields.account1()); + User user2 = new User(); + user2.setNickname(TestFields.NICKNAME_2); + user2.setUserId(TestFields.USER_ID_2); + user2.setAccount(TestFields.account1()); + User user3 = new User(); + user3.setNickname(TestFields.NICKNAME_3); + user3.setUserId(TestFields.USER_ID_3); + User user4 = new User(); + user4.setNickname(TestFields.NICKNAME_4); + user4.setUserId(TestFields.USER_ID_4); + user4.setSpectator(true); + room.addUser(user1); + room.addUser(user2); + room.addUser(user3); + room.addUser(user4); + room.setHost(user1); + + List problems = new ArrayList<>(); + problems.add(TestFields.problem1()); + problems.add(TestFields.problem2()); + room.setProblems(problems); + room.setNumProblems(problems.size()); + + gameManagementService.createAddGameFromRoom(room); + Game game = gameManagementService.getGameFromRoomId(room.getRoomId()); + + // First correct submission for problem 0 with user1. + SubmissionRequest correctSubmission = new SubmissionRequest(); + correctSubmission.setLanguage(TestFields.PYTHON_LANGUAGE); + correctSubmission.setCode(TestFields.PYTHON_CODE); + correctSubmission.setInitiator(UserMapper.toDto(user1)); + Mockito.doAnswer(new Answer() { + public SubmissionDto answer(InvocationOnMock invocation) { + UtilityTestMethods.addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID), 0, TestFields.PLAYER_CODE_1, 1); + game.setAllSolved(true); + return new SubmissionDto(); + }}) + .when(submitService).submitSolution(game, correctSubmission); + + gameManagementService.submitSolution(TestFields.ROOM_ID, correctSubmission); + + // Second correct submission for problem 0 with user3. + correctSubmission.setInitiator(UserMapper.toDto(user3)); + Mockito.doAnswer(new Answer() { + public SubmissionDto answer(InvocationOnMock invocation) { + UtilityTestMethods.addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID_3), 0, TestFields.PLAYER_CODE_1, 1); + game.setAllSolved(true); + return new SubmissionDto(); + }}) + .when(submitService).submitSolution(game, correctSubmission); + + gameManagementService.submitSolution(TestFields.ROOM_ID, correctSubmission); + + // Incorrect submission for problem 1 with user3. + SubmissionRequest incorrectSubmission = new SubmissionRequest(); + incorrectSubmission.setLanguage(TestFields.PYTHON_LANGUAGE); + incorrectSubmission.setCode(TestFields.PYTHON_CODE); + incorrectSubmission.setInitiator(UserMapper.toDto(user3)); + Mockito.doAnswer(new Answer() { + public SubmissionDto answer(InvocationOnMock invocation) { + UtilityTestMethods.addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID_3), 1, TestFields.PLAYER_CODE_2, 0); + game.setAllSolved(true); + return new SubmissionDto(); + }}) + .when(submitService).submitSolution(game, incorrectSubmission); + gameManagementService.submitSolution(TestFields.ROOM_ID, incorrectSubmission); + + // Incorrect submission for problem 0 with user1. + incorrectSubmission.setInitiator(UserMapper.toDto(user1)); + Mockito.doAnswer(new Answer() { + public SubmissionDto answer(InvocationOnMock invocation) { + UtilityTestMethods.addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID), 0, TestFields.PLAYER_CODE_2, 0); + game.setAllSolved(true); + return new SubmissionDto(); + }}) + .when(submitService).submitSolution(game, incorrectSubmission); + gameManagementService.submitSolution(TestFields.ROOM_ID, incorrectSubmission); + + // Set manually end game, and trigger game report creation directly. + game.setGameEnded(true); + GameReport gameReport = reportService.createGameReport(game); + + // Confirm that the game report, account, and users are saved. + verify(gameReportRepository).save(Mockito.any(GameReport.class)); + verify(accountRepository).save(eq(user1.getAccount())); + + // Check assertions for top-level report variables. + assertEquals(game.getGameTimer().getStartTime(), gameReport.getCreatedDateTime()); + assertEquals(2, gameReport.getNumTestCases()); + assertEquals((double) 2 / 3, gameReport.getAverageProblemsSolved()); + assertEquals((double) 2 / 3, gameReport.getAverageTestCasesPassed()); + assertEquals(GameEndType.MANUAL_END, gameReport.getGameEndType()); + + // Check assertions for individual problem containers. + assertEquals(2, gameReport.getProblemContainers().size()); + ProblemContainer problemContainer1 = gameReport.getProblemContainers().get(0); + assertEquals(1, problemContainer1.getAverageAttemptCount()); + assertEquals((double) 2 / 3, problemContainer1.getAverageTestCasesPassed()); + assertEquals(TestFields.problem1().getName(), problemContainer1.getProblem().getName()); + assertEquals(1, problemContainer1.getTestCaseCount()); + assertEquals(2, problemContainer1.getUserSolvedCount()); + ProblemContainer problemContainer2 = gameReport.getProblemContainers().get(1); + assertEquals((double) 1 / 3, problemContainer2.getAverageAttemptCount()); + assertEquals(0, problemContainer2.getAverageTestCasesPassed()); + assertEquals(TestFields.problem2().getName(), problemContainer2.getProblem().getName()); + assertEquals(1, problemContainer2.getTestCaseCount()); + assertEquals(0, problemContainer2.getUserSolvedCount()); + + // Check assertions for each user and submission group. + assertEquals(3, gameReport.getUsers().size()); + assertEquals(1, gameReport.getUsers().get(0).getSubmissionGroupReports().size()); + SubmissionGroupReport submissionGroupReport1 = gameReport.getUsers().get(0).getSubmissionGroupReports().get(0); + assertEquals(gameReport.getGameReportId(), submissionGroupReport1.getGameReportId()); + assertEquals(1, submissionGroupReport1.getNumTestCasesPassed()); + assertEquals("10", submissionGroupReport1.getProblemsSolved()); + assertEquals(2, submissionGroupReport1.getSubmissionReports().size()); + + assertEquals(1, gameReport.getUsers().get(1).getSubmissionGroupReports().size()); + SubmissionGroupReport submissionGroupReport2 = gameReport.getUsers().get(1).getSubmissionGroupReports().get(0); + assertEquals(gameReport.getGameReportId(), submissionGroupReport2.getGameReportId()); + assertEquals(0, submissionGroupReport2.getNumTestCasesPassed()); + assertEquals("00", submissionGroupReport2.getProblemsSolved()); + assertEquals(0, submissionGroupReport2.getSubmissionReports().size()); + + assertEquals(1, gameReport.getUsers().get(2).getSubmissionGroupReports().size()); + SubmissionGroupReport submissionGroupReport3 = gameReport.getUsers().get(2).getSubmissionGroupReports().get(0); + assertEquals(gameReport.getGameReportId(), submissionGroupReport3.getGameReportId()); + assertEquals(1, submissionGroupReport3.getNumTestCasesPassed()); + assertEquals("10", submissionGroupReport3.getProblemsSolved()); + assertEquals(2, submissionGroupReport3.getSubmissionReports().size()); + } + + @Test + public void createTwoGameReports() { + /** + * Create two games to verify that two submission group reports are + * made with the same user. + * 1. Create room with one user and one problem. + * 2. Start and immediately end game, manually triggering the create + * game report. + * 3. Start and immediately end game again, and check that user has two + * submission group reports, as well as the all solved game end type. + */ + + Room room = new Room(); + room.setRoomId(TestFields.ROOM_ID); + room.setDuration(120L); + User user1 = new User(); + user1.setNickname(TestFields.NICKNAME); + user1.setUserId(TestFields.USER_ID); + user1.setAccount(TestFields.account1()); + room.addUser(user1); + room.setHost(user1); + + List problems = new ArrayList<>(); + problems.add(TestFields.problem1()); + room.setProblems(problems); + room.setNumProblems(problems.size()); + + gameManagementService.createAddGameFromRoom(room); + Game game = gameManagementService.getGameFromRoomId(room.getRoomId()); + game.setGameEnded(true); + reportService.createGameReport(game); + + gameManagementService.createAddGameFromRoom(room); + game = gameManagementService.getGameFromRoomId(room.getRoomId()); + game.setAllSolved(true); + GameReport gameReport = reportService.createGameReport(game); + + assertEquals(GameEndType.ALL_SOLVED, gameReport.getGameEndType()); + assertEquals(2, gameReport.getUsers().get(0).getSubmissionGroupReports().size()); + } +} diff --git a/src/test/java/com/codejoust/main/task/CreateGameReportTaskTests.java b/src/test/java/com/codejoust/main/task/CreateGameReportTaskTests.java index a5d82a9a6..bcbb167c2 100644 --- a/src/test/java/com/codejoust/main/task/CreateGameReportTaskTests.java +++ b/src/test/java/com/codejoust/main/task/CreateGameReportTaskTests.java @@ -13,7 +13,7 @@ import com.codejoust.main.model.Room; import com.codejoust.main.model.User; import com.codejoust.main.model.problem.ProblemDifficulty; -import com.codejoust.main.service.GameManagementService; +import com.codejoust.main.service.ReportService; import com.codejoust.main.service.SocketService; import com.codejoust.main.util.TestFields; import org.junit.Test; @@ -26,7 +26,7 @@ public class CreateGameReportTaskTests { @Mock - private GameManagementService gameManagementService; + private ReportService reportService; @Mock private SocketService socketService; @@ -34,7 +34,7 @@ public class CreateGameReportTaskTests { @Test public void createGameReportTaskSocketMessageNullGame() { MockitoAnnotations.initMocks(this); - assertThrows(ApiException.class, () -> new CreateGameReportTask(gameManagementService, null)); + assertThrows(ApiException.class, () -> new CreateGameReportTask(reportService, null)); } @Test @@ -82,9 +82,9 @@ public void createGameReportTaskSocketMessage() { MockitoAnnotations.initMocks(this); - CreateGameReportTask createGameReportTask = new CreateGameReportTask(gameManagementService, game); + CreateGameReportTask createGameReportTask = new CreateGameReportTask(reportService, game); gameTimer.getTimer().schedule(createGameReportTask, 1000L); - verify(gameManagementService, timeout(1200)).createGameReport(eq(game)); + verify(reportService, timeout(1200)).createGameReport(eq(game)); } } diff --git a/src/test/java/com/codejoust/main/util/UtilityTestMethods.java b/src/test/java/com/codejoust/main/util/UtilityTestMethods.java index 4e831b5e5..63bc75270 100644 --- a/src/test/java/com/codejoust/main/util/UtilityTestMethods.java +++ b/src/test/java/com/codejoust/main/util/UtilityTestMethods.java @@ -7,6 +7,10 @@ import com.google.gson.GsonBuilder; import com.google.gson.JsonDeserializer; +import com.codejoust.main.game_object.Player; +import com.codejoust.main.game_object.PlayerCode; +import com.codejoust.main.game_object.Submission; + public class UtilityTestMethods { public static String convertObjectToJsonString(Object o) { @@ -27,4 +31,21 @@ public static T toObjectType(String json, Type type) { Gson gson = new Gson(); return gson.fromJson(json, type); } + + // Helper method to add a dummy submission to a Player object + public static void addSubmissionHelper(Player player, int problemIndex, PlayerCode playerCode, int numCorrect) { + Submission submission = new Submission(); + submission.setProblemIndex(problemIndex); + submission.setNumCorrect(numCorrect); + submission.setNumTestCases(1); + submission.setStartTime(Instant.now()); + submission.setPlayerCode(playerCode); + + player.getSubmissions().add(submission); + if (numCorrect == 1) { + boolean[] solved = player.getSolved(); + solved[problemIndex] = true; + player.setSolved(solved); + } + } } From dabe1424831261899294b3cf405622f17afb2347 Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Wed, 21 Jul 2021 08:53:15 -0700 Subject: [PATCH 30/39] Add problem repository to the report service to take care of deleted problems during game. --- .../com/codejoust/main/service/ReportService.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/codejoust/main/service/ReportService.java b/src/main/java/com/codejoust/main/service/ReportService.java index 0f7420e28..78a040a86 100644 --- a/src/main/java/com/codejoust/main/service/ReportService.java +++ b/src/main/java/com/codejoust/main/service/ReportService.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Set; +import com.codejoust.main.dao.ProblemRepository; import com.codejoust.main.dao.AccountRepository; import com.codejoust.main.dao.GameReportRepository; import com.codejoust.main.dto.game.SubmissionMapper; @@ -29,12 +30,15 @@ @Service public class ReportService { + private final ProblemRepository problemRepository; private final GameReportRepository gameReportRepository; private final AccountRepository accountRepository; @Autowired - protected ReportService(GameReportRepository gameReportRepository, + protected ReportService(ProblemRepository problemRepository, + GameReportRepository gameReportRepository, AccountRepository accountRepository) { + this.problemRepository = problemRepository; this.gameReportRepository = gameReportRepository; this.accountRepository = accountRepository; } @@ -121,6 +125,12 @@ public GameReport createGameReport(Game game) { gameReport.setAverageTestCasesPassed(Arrays.stream(totalTestCasesPassed).sum() / numPlayers); gameReport.setAverageProblemsSolved((double) Arrays.stream(userSolved).sum() / numPlayers); + // Set the Problem fields with the updated database problem. + for (ProblemContainer problemContainer : gameReport.getProblemContainers()) { + Problem problem = problemRepository.findProblemByProblemId(problemContainer.getProblem().getProblemId()); + problemContainer.setProblem(problem); + } + gameReportRepository.save(gameReport); // Iterate through all room users, players and spectators included. From 8ad55e7b8af205a422a2e48fd8916e98fb513ef5 Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Mon, 26 Jul 2021 00:33:53 -0700 Subject: [PATCH 31/39] Update submit service functionality to prevent submissions from occuring after the game ends. --- .../main/service/GameManagementService.java | 8 +--- .../codejoust/main/service/ReportService.java | 1 + .../codejoust/main/service/SubmitService.java | 40 +++++++++++-------- .../java/com/codejoust/main/util/Utility.java | 11 +++++ .../service/GameManagementServiceTests.java | 23 ----------- .../com/codejoust/main/util/UtilityTests.java | 24 +++++++++++ 6 files changed, 61 insertions(+), 46 deletions(-) diff --git a/src/main/java/com/codejoust/main/service/GameManagementService.java b/src/main/java/com/codejoust/main/service/GameManagementService.java index d670ecf09..85d615c49 100644 --- a/src/main/java/com/codejoust/main/service/GameManagementService.java +++ b/src/main/java/com/codejoust/main/service/GameManagementService.java @@ -113,7 +113,7 @@ public RoomDto startGame(String roomId, StartGameRequest request) { public RoomDto playAgain(String roomId, PlayAgainRequest request) { Game game = getGameFromRoomId(roomId); - if (!isGameOver(game)) { + if (!Utility.isGameOver(game)) { throw new ApiException(GameError.GAME_NOT_OVER); } @@ -216,7 +216,7 @@ public SubmissionDto submitSolution(String roomId, SubmissionRequest request) { SubmissionDto submissionDto = submitService.submitSolution(game, request); - if (isGameOver(game)) { + if (Utility.isGameOver(game)) { handleEndGame(game); } @@ -304,10 +304,6 @@ public void handleEndGame(Game game) { createGameReportTimer.schedule(createGameReportTask, GameTimer.DURATION_1 * 1000); } - protected boolean isGameOver(Game game) { - return game.getGameEnded() || game.getAllSolved() || (game.getGameTimer() != null && game.getGameTimer().isTimeUp()); - } - // Update people's socket active status public void conditionallyUpdateSocketInfo(Room room, User user) { Game game = currentGameMap.get(room.getRoomId()); diff --git a/src/main/java/com/codejoust/main/service/ReportService.java b/src/main/java/com/codejoust/main/service/ReportService.java index 78a040a86..a1733e617 100644 --- a/src/main/java/com/codejoust/main/service/ReportService.java +++ b/src/main/java/com/codejoust/main/service/ReportService.java @@ -126,6 +126,7 @@ public GameReport createGameReport(Game game) { gameReport.setAverageProblemsSolved((double) Arrays.stream(userSolved).sum() / numPlayers); // Set the Problem fields with the updated database problem. + // TODO: Take care of this in the test suite. for (ProblemContainer problemContainer : gameReport.getProblemContainers()) { Problem problem = problemRepository.findProblemByProblemId(problemContainer.getProblem().getProblemId()); problemContainer.setProblem(problem); diff --git a/src/main/java/com/codejoust/main/service/SubmitService.java b/src/main/java/com/codejoust/main/service/SubmitService.java index 9af74af13..05a83cb84 100644 --- a/src/main/java/com/codejoust/main/service/SubmitService.java +++ b/src/main/java/com/codejoust/main/service/SubmitService.java @@ -25,6 +25,7 @@ import com.codejoust.main.game_object.Submission; import com.codejoust.main.game_object.SubmissionResult; import com.codejoust.main.model.problem.Problem; +import com.codejoust.main.util.Utility; import com.google.gson.Gson; import lombok.extern.log4j.Log4j2; @@ -144,30 +145,35 @@ public SubmissionDto submitSolution(Game game, SubmissionRequest request) { testerRequest.setProblem(problemDto); Submission submission = getSubmission(testerRequest); - submission.setProblemIndex(request.getProblemIndex()); - player.getSubmissions().add(submission); - if (submission.getNumCorrect().equals(submission.getNumTestCases())) { - player.getSolved()[request.getProblemIndex()] = true; - } + // Add submission score if game is not over. + if (Utility.isGameOver(game)) { + submission.setProblemIndex(request.getProblemIndex()); + player.getSubmissions().add(submission); + + if (submission.getNumCorrect().equals(submission.getNumTestCases())) { + player.getSolved()[request.getProblemIndex()] = true; + } - // Variable to indicate whether all players have solved the problem. - boolean allSolved = true; - for (Player p : game.getPlayers().values()) { - for (Boolean b : p.getSolved()) { - if (b == null || !b) { - allSolved = false; - break; + // Variable to indicate whether all players have solved the problem. + boolean allSolved = true; + for (Player p : game.getPlayers().values()) { + for (Boolean b : p.getSolved()) { + if (b == null || !b) { + allSolved = false; + break; + } } } - } - // If the users have all completed the problem, set all solved to true. - if (allSolved) { - game.setAllSolved(true); + // If the users have all completed the problem, set all solved to true. + if (allSolved) { + game.setAllSolved(true); + } } - return GameMapper.submissionToDto(submission); + // TODO: Will this be an issue if the submission is not actually counted? Should I return null in that case? + return GameMapper.submissionToDto(submission); } // Get submission (either through tester or using a dummy response) diff --git a/src/main/java/com/codejoust/main/util/Utility.java b/src/main/java/com/codejoust/main/util/Utility.java index c3ef7e3b1..784247528 100644 --- a/src/main/java/com/codejoust/main/util/Utility.java +++ b/src/main/java/com/codejoust/main/util/Utility.java @@ -12,6 +12,7 @@ import com.codejoust.main.dao.RoomRepository; import com.codejoust.main.dao.UserRepository; +import com.codejoust.main.game_object.Game; import com.codejoust.main.game_object.NotificationType; import org.springframework.beans.factory.annotation.Autowired; @@ -131,4 +132,14 @@ && isLetter(identifier.charAt(0)) private static boolean isLetter(Character c) { return Character.toUpperCase(c) != Character.toLowerCase(c); } + + /** + * Check if the game is over. + * + * @param game the game in question + * @return a boolean verifying whether the game is over + */ + public static boolean isGameOver(Game game) { + return game.getGameEnded() || game.getAllSolved() || (game.getGameTimer() != null && game.getGameTimer().isTimeUp()); + } } diff --git a/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java b/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java index 032045abd..e72425398 100644 --- a/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java +++ b/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java @@ -47,7 +47,6 @@ import com.codejoust.main.exception.RoomError; import com.codejoust.main.exception.api.ApiException; import com.codejoust.main.game_object.Game; -import com.codejoust.main.game_object.GameTimer; import com.codejoust.main.game_object.NotificationType; import com.codejoust.main.game_object.Player; import com.codejoust.main.model.Room; @@ -963,28 +962,6 @@ public void updateCodeEmptyPlayerCode() { assertEquals(GameError.EMPTY_FIELD, exception.getError()); } - @Test - public void isGameOverFunctionsCorrectly() { - Game game = new Game(); - game.setGameTimer(new GameTimer(TestFields.DURATION)); - - game.setAllSolved(false); - game.getGameTimer().setTimeUp(false); - assertFalse(gameService.isGameOver(game)); - - game.setAllSolved(true); - game.getGameTimer().setTimeUp(false); - assertTrue(gameService.isGameOver(game)); - - game.setAllSolved(false); - game.getGameTimer().setTimeUp(true); - assertTrue(gameService.isGameOver(game)); - - game.setAllSolved(false); - game.setGameTimer(null); - assertFalse(gameService.isGameOver(game)); - } - @Test public void endGameCancelsTimersCreateReport() { Room room = new Room(); diff --git a/src/test/java/com/codejoust/main/util/UtilityTests.java b/src/test/java/com/codejoust/main/util/UtilityTests.java index 1de62762c..df1693e3b 100644 --- a/src/test/java/com/codejoust/main/util/UtilityTests.java +++ b/src/test/java/com/codejoust/main/util/UtilityTests.java @@ -19,6 +19,8 @@ import com.codejoust.main.dao.RoomRepository; import com.codejoust.main.dao.UserRepository; +import com.codejoust.main.game_object.Game; +import com.codejoust.main.game_object.GameTimer; import com.codejoust.main.model.User; import com.codejoust.main.service.RoomService; import com.codejoust.main.service.UserService; @@ -99,4 +101,26 @@ public void validateIdentifierFalse(String inputName) { public void validateIdentifierTrue(String inputName) { assertTrue(Utility.validateIdentifier(inputName)); } + + @Test + public void isGameOverFunctionsCorrectly() { + Game game = new Game(); + game.setGameTimer(new GameTimer(TestFields.DURATION)); + + game.setAllSolved(false); + game.getGameTimer().setTimeUp(false); + assertFalse(Utility.isGameOver(game)); + + game.setAllSolved(true); + game.getGameTimer().setTimeUp(false); + assertTrue(Utility.isGameOver(game)); + + game.setAllSolved(false); + game.getGameTimer().setTimeUp(true); + assertTrue(Utility.isGameOver(game)); + + game.setAllSolved(false); + game.setGameTimer(null); + assertFalse(Utility.isGameOver(game)); + } } From 7f52b257791acddcdb7513da966d77ecde2d03ce Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Tue, 27 Jul 2021 19:25:04 -0700 Subject: [PATCH 32/39] Remove CreateGameReportTask class and call createGameReport directly. --- .../main/service/GameManagementService.java | 5 +- .../codejoust/main/service/SubmitService.java | 2 +- .../main/task/CreateGameReportTask.java | 32 ------- .../main/task/CreateGameReportTaskTests.java | 90 ------------------- 4 files changed, 2 insertions(+), 127 deletions(-) delete mode 100644 src/main/java/com/codejoust/main/task/CreateGameReportTask.java delete mode 100644 src/test/java/com/codejoust/main/task/CreateGameReportTaskTests.java diff --git a/src/main/java/com/codejoust/main/service/GameManagementService.java b/src/main/java/com/codejoust/main/service/GameManagementService.java index 85d615c49..6139de70b 100644 --- a/src/main/java/com/codejoust/main/service/GameManagementService.java +++ b/src/main/java/com/codejoust/main/service/GameManagementService.java @@ -28,7 +28,6 @@ import com.codejoust.main.model.Room; import com.codejoust.main.model.User; import com.codejoust.main.model.problem.Problem; -import com.codejoust.main.task.CreateGameReportTask; import com.codejoust.main.task.EndGameTimerTask; import com.codejoust.main.util.Utility; @@ -299,9 +298,7 @@ public void handleEndGame(Game game) { timer.cancel(); } - CreateGameReportTask createGameReportTask = new CreateGameReportTask(reportService, game); - Timer createGameReportTimer = new Timer(); - createGameReportTimer.schedule(createGameReportTask, GameTimer.DURATION_1 * 1000); + reportService.createGameReport(game); } // Update people's socket active status diff --git a/src/main/java/com/codejoust/main/service/SubmitService.java b/src/main/java/com/codejoust/main/service/SubmitService.java index 05a83cb84..292600296 100644 --- a/src/main/java/com/codejoust/main/service/SubmitService.java +++ b/src/main/java/com/codejoust/main/service/SubmitService.java @@ -173,7 +173,7 @@ public SubmissionDto submitSolution(Game game, SubmissionRequest request) { } // TODO: Will this be an issue if the submission is not actually counted? Should I return null in that case? - return GameMapper.submissionToDto(submission); + return GameMapper.submissionToDto(submission); } // Get submission (either through tester or using a dummy response) diff --git a/src/main/java/com/codejoust/main/task/CreateGameReportTask.java b/src/main/java/com/codejoust/main/task/CreateGameReportTask.java deleted file mode 100644 index a20373559..000000000 --- a/src/main/java/com/codejoust/main/task/CreateGameReportTask.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.codejoust.main.task; - -import java.util.TimerTask; - -import com.codejoust.main.exception.TimerError; -import com.codejoust.main.exception.api.ApiException; -import com.codejoust.main.game_object.Game; -import com.codejoust.main.service.ReportService; - -public class CreateGameReportTask extends TimerTask { - - private final Game game; - - private final ReportService reportService; - - public CreateGameReportTask(ReportService reportService, Game game) { - this.reportService = reportService; - this.game = game; - - // Handle potential errors for run(). - if (reportService == null || game == null) { - throw new ApiException(TimerError.NULL_SETTING); - } - } - - @Override - public void run() { - // Create the game report (is the game updated here?). - reportService.createGameReport(game); - } - -} diff --git a/src/test/java/com/codejoust/main/task/CreateGameReportTaskTests.java b/src/test/java/com/codejoust/main/task/CreateGameReportTaskTests.java deleted file mode 100644 index bcbb167c2..000000000 --- a/src/test/java/com/codejoust/main/task/CreateGameReportTaskTests.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.codejoust.main.task; - -import static org.junit.Assert.assertThrows; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.timeout; -import static org.mockito.Mockito.verify; - -import com.codejoust.main.dto.game.GameDto; -import com.codejoust.main.dto.game.GameMapper; -import com.codejoust.main.exception.api.ApiException; -import com.codejoust.main.game_object.Game; -import com.codejoust.main.game_object.GameTimer; -import com.codejoust.main.model.Room; -import com.codejoust.main.model.User; -import com.codejoust.main.model.problem.ProblemDifficulty; -import com.codejoust.main.service.ReportService; -import com.codejoust.main.service.SocketService; -import com.codejoust.main.util.TestFields; -import org.junit.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.junit.jupiter.MockitoExtension; - -@ExtendWith(MockitoExtension.class) -public class CreateGameReportTaskTests { - - @Mock - private ReportService reportService; - - @Mock - private SocketService socketService; - - @Test - public void createGameReportTaskSocketMessageNullGame() { - MockitoAnnotations.initMocks(this); - assertThrows(ApiException.class, () -> new CreateGameReportTask(reportService, null)); - } - - @Test - public void createGameReportTaskSocketMessageNullSocketService() { - User user = new User(); - user.setNickname(TestFields.NICKNAME); - user.setUserId(TestFields.USER_ID); - user.setSessionId(TestFields.SESSION_ID); - - Room room = new Room(); - room.setRoomId(TestFields.ROOM_ID); - room.setDifficulty(ProblemDifficulty.MEDIUM); - room.setHost(user); - room.addUser(user); - - Game game = GameMapper.fromRoom(room); - GameTimer gameTimer = new GameTimer(10L); - game.setGameTimer(gameTimer); - - MockitoAnnotations.initMocks(this); - - assertThrows(ApiException.class, () -> new CreateGameReportTask(null, game)); - } - - @Test - public void createGameReportTaskSocketMessage() { - User user = new User(); - user.setNickname(TestFields.NICKNAME); - user.setUserId(TestFields.USER_ID); - user.setSessionId(TestFields.SESSION_ID); - - Room room = new Room(); - room.setRoomId(TestFields.ROOM_ID); - room.setDifficulty(ProblemDifficulty.MEDIUM); - room.setHost(user); - room.addUser(user); - - Game game = GameMapper.fromRoom(room); - GameTimer gameTimer = new GameTimer(1L); - game.setGameTimer(gameTimer); - - // Make the Game DTO update that will occur on timer end. - GameDto gameDto = GameMapper.toDto(game); - gameDto.getGameTimer().setTimeUp(true); - - MockitoAnnotations.initMocks(this); - - CreateGameReportTask createGameReportTask = new CreateGameReportTask(reportService, game); - gameTimer.getTimer().schedule(createGameReportTask, 1000L); - - verify(reportService, timeout(1200)).createGameReport(eq(game)); - } -} From 056dd0751d661ac1855560ec4719aecb122567e4 Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Tue, 27 Jul 2021 19:54:59 -0700 Subject: [PATCH 33/39] Fix reportService tests. --- .../codejoust/main/service/SubmitService.java | 2 +- .../service/GameManagementServiceTests.java | 8 +++--- .../main/service/ReportServiceTests.java | 25 +++++++++++-------- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/codejoust/main/service/SubmitService.java b/src/main/java/com/codejoust/main/service/SubmitService.java index 292600296..987da15f5 100644 --- a/src/main/java/com/codejoust/main/service/SubmitService.java +++ b/src/main/java/com/codejoust/main/service/SubmitService.java @@ -147,7 +147,7 @@ public SubmissionDto submitSolution(Game game, SubmissionRequest request) { Submission submission = getSubmission(testerRequest); // Add submission score if game is not over. - if (Utility.isGameOver(game)) { + if (!Utility.isGameOver(game)) { submission.setProblemIndex(request.getProblemIndex()); player.getSubmissions().add(submission); diff --git a/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java b/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java index e72425398..4f28ad4cd 100644 --- a/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java +++ b/src/test/java/com/codejoust/main/service/GameManagementServiceTests.java @@ -53,7 +53,6 @@ import com.codejoust.main.model.User; import com.codejoust.main.model.problem.Problem; import com.codejoust.main.model.problem.ProblemDifficulty; -import com.codejoust.main.model.report.GameReport; import com.codejoust.main.util.UtilityTestMethods; @ExtendWith(MockitoExtension.class) @@ -86,6 +85,9 @@ public class GameManagementServiceTests { @Mock private LiveGameService liveGameService; + @Mock + private ReportService reportService; + @Spy @InjectMocks private GameManagementService gameService; @@ -985,8 +987,8 @@ public void endGameCancelsTimersCreateReport() { verify(socketService, after(13000).never()).sendSocketUpdate(Mockito.any(String.class), Mockito.any(GameNotificationDto.class)); verify(socketService, never()).sendSocketUpdate(Mockito.any(GameDto.class)); - // Game report is saved after one minute past handleEndGame - verify(gameReportRepository, after(61300)).save(Mockito.any(GameReport.class)); + // The game report is created upon end game + verify(reportService).createGameReport(eq(game)); } @Test diff --git a/src/test/java/com/codejoust/main/service/ReportServiceTests.java b/src/test/java/com/codejoust/main/service/ReportServiceTests.java index 327da4dbd..ffba0db6e 100644 --- a/src/test/java/com/codejoust/main/service/ReportServiceTests.java +++ b/src/test/java/com/codejoust/main/service/ReportServiceTests.java @@ -20,6 +20,7 @@ import com.codejoust.main.dao.AccountRepository; import com.codejoust.main.dao.GameReportRepository; +import com.codejoust.main.dao.ProblemRepository; import com.codejoust.main.dao.RoomRepository; import com.codejoust.main.dao.UserRepository; import com.codejoust.main.dto.game.SubmissionDto; @@ -50,6 +51,9 @@ public class ReportServiceTests { @Mock private UserRepository userRepository; + @Mock + private ProblemRepository problemRepository; + @Mock private SocketService socketService; @@ -118,10 +122,12 @@ public void createGameReportMultipleAttributes() { room.addUser(user3); room.addUser(user4); room.setHost(user1); + Problem problem1 = TestFields.problem1(); + Problem problem2 = TestFields.problem2(); List problems = new ArrayList<>(); - problems.add(TestFields.problem1()); - problems.add(TestFields.problem2()); + problems.add(problem1); + problems.add(problem2); room.setProblems(problems); room.setNumProblems(problems.size()); @@ -136,23 +142,19 @@ public void createGameReportMultipleAttributes() { Mockito.doAnswer(new Answer() { public SubmissionDto answer(InvocationOnMock invocation) { UtilityTestMethods.addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID), 0, TestFields.PLAYER_CODE_1, 1); - game.setAllSolved(true); return new SubmissionDto(); }}) .when(submitService).submitSolution(game, correctSubmission); - - gameManagementService.submitSolution(TestFields.ROOM_ID, correctSubmission); + gameManagementService.submitSolution(TestFields.ROOM_ID, correctSubmission); // Second correct submission for problem 0 with user3. correctSubmission.setInitiator(UserMapper.toDto(user3)); Mockito.doAnswer(new Answer() { public SubmissionDto answer(InvocationOnMock invocation) { UtilityTestMethods.addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID_3), 0, TestFields.PLAYER_CODE_1, 1); - game.setAllSolved(true); return new SubmissionDto(); }}) .when(submitService).submitSolution(game, correctSubmission); - gameManagementService.submitSolution(TestFields.ROOM_ID, correctSubmission); // Incorrect submission for problem 1 with user3. @@ -163,7 +165,6 @@ public SubmissionDto answer(InvocationOnMock invocation) { Mockito.doAnswer(new Answer() { public SubmissionDto answer(InvocationOnMock invocation) { UtilityTestMethods.addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID_3), 1, TestFields.PLAYER_CODE_2, 0); - game.setAllSolved(true); return new SubmissionDto(); }}) .when(submitService).submitSolution(game, incorrectSubmission); @@ -174,7 +175,6 @@ public SubmissionDto answer(InvocationOnMock invocation) { Mockito.doAnswer(new Answer() { public SubmissionDto answer(InvocationOnMock invocation) { UtilityTestMethods.addSubmissionHelper(game.getPlayers().get(TestFields.USER_ID), 0, TestFields.PLAYER_CODE_2, 0); - game.setAllSolved(true); return new SubmissionDto(); }}) .when(submitService).submitSolution(game, incorrectSubmission); @@ -182,6 +182,8 @@ public SubmissionDto answer(InvocationOnMock invocation) { // Set manually end game, and trigger game report creation directly. game.setGameEnded(true); + Mockito.doReturn(problem1).when(problemRepository).findProblemByProblemId(problem1.getProblemId()); + Mockito.doReturn(problem2).when(problemRepository).findProblemByProblemId(problem2.getProblemId()); GameReport gameReport = reportService.createGameReport(game); // Confirm that the game report, account, and users are saved. @@ -255,15 +257,18 @@ public void createTwoGameReports() { user1.setAccount(TestFields.account1()); room.addUser(user1); room.setHost(user1); + Problem problem1 = TestFields.problem1(); List problems = new ArrayList<>(); - problems.add(TestFields.problem1()); + problems.add(problem1); room.setProblems(problems); room.setNumProblems(problems.size()); gameManagementService.createAddGameFromRoom(room); Game game = gameManagementService.getGameFromRoomId(room.getRoomId()); game.setGameEnded(true); + + Mockito.doReturn(problem1).when(problemRepository).findProblemByProblemId(problem1.getProblemId()); reportService.createGameReport(game); gameManagementService.createAddGameFromRoom(room); From f3dc868f61cd3ee14658d456b972a02b03649ce8 Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Tue, 27 Jul 2021 22:48:22 -0700 Subject: [PATCH 34/39] Update the removeProblem test API. --- .../java/com/codejoust/main/model/Room.java | 4 +++ .../main/service/ProblemServiceTests.java | 29 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/main/java/com/codejoust/main/model/Room.java b/src/main/java/com/codejoust/main/model/Room.java index 8b9fd597e..1af7690ab 100644 --- a/src/main/java/com/codejoust/main/model/Room.java +++ b/src/main/java/com/codejoust/main/model/Room.java @@ -149,6 +149,10 @@ public boolean isFull() { return users.size() >= size; } + public boolean addProblem(Problem problem) { + return problems.add(problem); + } + public boolean removeProblem(Problem problem) { return problems.remove(problem); } diff --git a/src/test/java/com/codejoust/main/service/ProblemServiceTests.java b/src/test/java/com/codejoust/main/service/ProblemServiceTests.java index 946a78da9..da9717a94 100644 --- a/src/test/java/com/codejoust/main/service/ProblemServiceTests.java +++ b/src/test/java/com/codejoust/main/service/ProblemServiceTests.java @@ -1,6 +1,7 @@ package com.codejoust.main.service; import com.codejoust.main.dao.AccountRepository; +import com.codejoust.main.dao.ProblemContainerRepository; import com.codejoust.main.exception.AccountError; import com.codejoust.main.exception.api.ApiError; import com.codejoust.main.util.TestFields; @@ -20,6 +21,7 @@ import com.codejoust.main.dao.ProblemRepository; import com.codejoust.main.dao.ProblemTagRepository; +import com.codejoust.main.dao.RoomRepository; import com.codejoust.main.dto.problem.CreateProblemRequest; import com.codejoust.main.dto.problem.CreateProblemTagRequest; import com.codejoust.main.dto.problem.CreateTestCaseRequest; @@ -30,7 +32,9 @@ import com.codejoust.main.dto.problem.ProblemTestCaseDto; import com.codejoust.main.exception.ProblemError; import com.codejoust.main.exception.api.ApiException; +import com.codejoust.main.model.Room; import com.codejoust.main.model.problem.Problem; +import com.codejoust.main.model.problem.ProblemContainer; import com.codejoust.main.model.problem.ProblemDifficulty; import com.codejoust.main.model.problem.ProblemIOType; import com.codejoust.main.model.problem.ProblemInput; @@ -57,6 +61,12 @@ public class ProblemServiceTests { @Mock private AccountRepository accountRepository; + @Mock + private ProblemContainerRepository problemContainerRepository; + + @Mock + private RoomRepository roomRepository; + @Mock private FirebaseService firebaseService; @@ -656,6 +666,12 @@ public void editProblemNewProblemInputInvalidatesTestCases() { @Test public void deleteProblemSuccess() { + /** + * 1. Create the problem and mock the return of findProblemByProblemId. + * 2. Create an associated ProblemContainer and mock its return. + * 3. Create an associated Room and mock its return. + * 4. Delete the problem and verify the calls and assertions. + */ Problem problem = new Problem(); problem.setName(TestFields.NAME); problem.setDescription(TestFields.DESCRIPTION); @@ -664,9 +680,22 @@ public void deleteProblemSuccess() { Mockito.doReturn(problem).when(repository).findProblemByProblemId(problem.getProblemId()); + ProblemContainer problemContainer = new ProblemContainer(); + problemContainer.setProblem(problem); + problemContainer.setTestCaseCount(problem.getTestCases().size()); + Mockito.doReturn(Collections.singletonList(problemContainer)).when(problemContainerRepository).findAllByProblem(problem); + problemContainer.setProblem(null); + + Room room = new Room(); + room.addProblem(problem); + Mockito.doReturn(Collections.singletonList(room)).when(roomRepository).findByProblems_ProblemId(problem.getProblemId()); + room.removeProblem(problem); + ProblemDto response = problemService.deleteProblem(problem.getProblemId(), TestFields.TOKEN); verify(repository).delete(problem); + verify(problemContainerRepository).save(problemContainer); + verify(roomRepository).save(room); assertEquals(problem.getName(), response.getName()); assertEquals(problem.getDescription(), response.getDescription()); From fe0bd955bd4fac86a4677a50d1276286c376fade Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Tue, 27 Jul 2021 23:37:32 -0700 Subject: [PATCH 35/39] Attempt initial implementation of createGameReportStarted. --- src/main/java/com/codejoust/main/game_object/Game.java | 3 +++ src/main/java/com/codejoust/main/service/ReportService.java | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/src/main/java/com/codejoust/main/game_object/Game.java b/src/main/java/com/codejoust/main/game_object/Game.java index 3df57e7a4..b6e51a7d1 100644 --- a/src/main/java/com/codejoust/main/game_object/Game.java +++ b/src/main/java/com/codejoust/main/game_object/Game.java @@ -31,4 +31,7 @@ public class Game { // Boolean to hold whether the host ended the game early private Boolean gameEnded = false; + + // Boolean to hold whether the process of creating the game report started + private Boolean createGameReportStarted = false; } diff --git a/src/main/java/com/codejoust/main/service/ReportService.java b/src/main/java/com/codejoust/main/service/ReportService.java index a1733e617..2008519a7 100644 --- a/src/main/java/com/codejoust/main/service/ReportService.java +++ b/src/main/java/com/codejoust/main/service/ReportService.java @@ -44,6 +44,12 @@ protected ReportService(ProblemRepository problemRepository, } public GameReport createGameReport(Game game) { + // Check if game report has already been created (or attempted). + if (game.getCreateGameReportStarted()) { + return null; + } + game.setCreateGameReportStarted(true); + GameReport gameReport = new GameReport(); int numProblems = game.getProblems().size(); int numPlayers = game.getPlayers().size(); From 72dd0a6c0b3a7e2b03e8f052f8e37b1e8e6ccd34 Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Sun, 1 Aug 2021 13:37:43 -0700 Subject: [PATCH 36/39] Test problem deletion in the ReportServiceTests class. --- .../java/com/codejoust/main/service/ReportService.java | 1 - .../com/codejoust/main/service/ReportServiceTests.java | 9 +++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/codejoust/main/service/ReportService.java b/src/main/java/com/codejoust/main/service/ReportService.java index 2008519a7..1e8a5e4df 100644 --- a/src/main/java/com/codejoust/main/service/ReportService.java +++ b/src/main/java/com/codejoust/main/service/ReportService.java @@ -132,7 +132,6 @@ public GameReport createGameReport(Game game) { gameReport.setAverageProblemsSolved((double) Arrays.stream(userSolved).sum() / numPlayers); // Set the Problem fields with the updated database problem. - // TODO: Take care of this in the test suite. for (ProblemContainer problemContainer : gameReport.getProblemContainers()) { Problem problem = problemRepository.findProblemByProblemId(problemContainer.getProblem().getProblemId()); problemContainer.setProblem(problem); diff --git a/src/test/java/com/codejoust/main/service/ReportServiceTests.java b/src/test/java/com/codejoust/main/service/ReportServiceTests.java index ffba0db6e..afabae4d7 100644 --- a/src/test/java/com/codejoust/main/service/ReportServiceTests.java +++ b/src/test/java/com/codejoust/main/service/ReportServiceTests.java @@ -12,6 +12,7 @@ import org.mockito.stubbing.Answer; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; @@ -180,10 +181,10 @@ public SubmissionDto answer(InvocationOnMock invocation) { .when(submitService).submitSolution(game, incorrectSubmission); gameManagementService.submitSolution(TestFields.ROOM_ID, incorrectSubmission); - // Set manually end game, and trigger game report creation directly. + // Set end game, second problem deleted, and trigger game report. game.setGameEnded(true); Mockito.doReturn(problem1).when(problemRepository).findProblemByProblemId(problem1.getProblemId()); - Mockito.doReturn(problem2).when(problemRepository).findProblemByProblemId(problem2.getProblemId()); + Mockito.doReturn(null).when(problemRepository).findProblemByProblemId(problem2.getProblemId()); GameReport gameReport = reportService.createGameReport(game); // Confirm that the game report, account, and users are saved. @@ -200,15 +201,15 @@ public SubmissionDto answer(InvocationOnMock invocation) { // Check assertions for individual problem containers. assertEquals(2, gameReport.getProblemContainers().size()); ProblemContainer problemContainer1 = gameReport.getProblemContainers().get(0); + assertEquals(problem1, problemContainer1.getProblem()); assertEquals(1, problemContainer1.getAverageAttemptCount()); assertEquals((double) 2 / 3, problemContainer1.getAverageTestCasesPassed()); - assertEquals(TestFields.problem1().getName(), problemContainer1.getProblem().getName()); assertEquals(1, problemContainer1.getTestCaseCount()); assertEquals(2, problemContainer1.getUserSolvedCount()); ProblemContainer problemContainer2 = gameReport.getProblemContainers().get(1); + assertNull(problemContainer2.getProblem()); assertEquals((double) 1 / 3, problemContainer2.getAverageAttemptCount()); assertEquals(0, problemContainer2.getAverageTestCasesPassed()); - assertEquals(TestFields.problem2().getName(), problemContainer2.getProblem().getName()); assertEquals(1, problemContainer2.getTestCaseCount()); assertEquals(0, problemContainer2.getUserSolvedCount()); From 87d39b1edae6cad658b9e04384f497038e1f9718 Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Sun, 1 Aug 2021 18:01:01 -0700 Subject: [PATCH 37/39] Split problem container and account additions to separate methods. --- .../codejoust/main/service/ReportService.java | 89 +++++++++++++------ 1 file changed, 62 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/codejoust/main/service/ReportService.java b/src/main/java/com/codejoust/main/service/ReportService.java index 1e8a5e4df..4eb546644 100644 --- a/src/main/java/com/codejoust/main/service/ReportService.java +++ b/src/main/java/com/codejoust/main/service/ReportService.java @@ -99,23 +99,6 @@ public GameReport createGameReport(Game game) { gameReport.addUser(user); } - // Set problem container variables. - int numTestCases = 0; - List problems = game.getProblems(); - for (int i = 0; i < numProblems; i++) { - Problem problem = problems.get(i); - numTestCases += problem.getTestCases().size(); - - ProblemContainer problemContainer = new ProblemContainer(); - problemContainer.setProblem(problem); - problemContainer.setUserSolvedCount(userSolved[i]); - problemContainer.setTestCaseCount(problem.getTestCases().size()); - problemContainer.setAverageTestCasesPassed(totalTestCasesPassed[i] / numPlayers); - problemContainer.setAverageAttemptCount(totalAttemptCount[i] / numPlayers); - gameReport.addProblemContainer(problemContainer); - } - gameReport.setNumTestCases(numTestCases); - Instant startTime = game.getGameTimer().getStartTime(); gameReport.setCreatedDateTime(startTime); gameReport.setDuration(Duration.between(startTime, Instant.now()).getSeconds()); @@ -131,14 +114,61 @@ public GameReport createGameReport(Game game) { gameReport.setAverageTestCasesPassed(Arrays.stream(totalTestCasesPassed).sum() / numPlayers); gameReport.setAverageProblemsSolved((double) Arrays.stream(userSolved).sum() / numPlayers); - // Set the Problem fields with the updated database problem. - for (ProblemContainer problemContainer : gameReport.getProblemContainers()) { - Problem problem = problemRepository.findProblemByProblemId(problemContainer.getProblem().getProblemId()); - problemContainer.setProblem(problem); - } - + createProblemContainers(gameReport, game, numProblems, numPlayers, userSolved, totalTestCasesPassed, totalAttemptCount); gameReportRepository.save(gameReport); + addGameReportAccounts(gameReport, game); + + // Log the completion of the latest game report. + log.info("Created game report for game with Room ID {}", game.getRoom().getRoomId()); + return gameReport; + } + + /** + * Create and add the problem containers to the game report, as well + * as iterating through and setting the number of test cases. + * + * @param gameReport The game report in progress. + * @param game The game that has just concluded. + * @param numProblems The number of problems in the game. + * @param numPlayers The number of players (non-spectators) in the game. + * @param userSolved The number of users that solved each problem. + * @param totalTestCasesPassed The total test cases passed across all + * users for each problem. + * @param totalAttemptCount The total attempt count across all users for + * each problem. + */ + public void createProblemContainers(GameReport gameReport, Game game, + int numProblems, int numPlayers, int[] userSolved, + double[] totalTestCasesPassed, double[] totalAttemptCount) { + + // Set problem container variables. + int numTestCases = 0; + List problems = game.getProblems(); + for (int i = 0; i < numProblems; i++) { + Problem problem = problems.get(i); + numTestCases += problem.getTestCases().size(); + + ProblemContainer problemContainer = new ProblemContainer(); + problemContainer.setUserSolvedCount(userSolved[i]); + problemContainer.setTestCaseCount(problem.getTestCases().size()); + problemContainer.setAverageTestCasesPassed(totalTestCasesPassed[i] / numPlayers); + problemContainer.setAverageAttemptCount(totalAttemptCount[i] / numPlayers); + + // Set the Problem fields with the updated database problem. + problemContainer.setProblem(problemRepository.findProblemByProblemId(problem.getProblemId())); + gameReport.addProblemContainer(problemContainer); + } + gameReport.setNumTestCases(numTestCases); + } + + /** + * Add the game report to the associated accounts (players and spectators). + * + * @param gameReport The game report in progress. + * @param game The game that has just concluded. + */ + public void addGameReportAccounts(GameReport gameReport, Game game) { // Iterate through all room users, players and spectators included. Set addedAccounts = new HashSet<>(); for (User user : game.getRoom().getUsers()) { @@ -150,12 +180,17 @@ public GameReport createGameReport(Game game) { accountRepository.save(account); } } - - // Log the completion of the latest game report. - log.info("Created game report for game with Room ID {}", game.getRoom().getRoomId()); - return gameReport; } + /** + * The String that represents the different problems solved by a user, + * where the index of the String represents a specific problem and a + * 1 = solved, 0 = not solved. + * + * @param problemsSolved The boolean array representing the problems + * solved by the user. + * @return A String representing the problems solved by the user. + */ private String compactProblemsSolved(boolean[] problemsSolved) { StringBuilder builder = new StringBuilder(); for (boolean problemSolved : problemsSolved) { From c743cfa2959b4cade955c10518b4a46b468495be Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Sun, 1 Aug 2021 18:16:01 -0700 Subject: [PATCH 38/39] Further split up the createGameReport method into different methods. --- .../codejoust/main/service/ReportService.java | 62 ++++++++++++++++--- 1 file changed, 52 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/codejoust/main/service/ReportService.java b/src/main/java/com/codejoust/main/service/ReportService.java index 4eb546644..f485b6fc9 100644 --- a/src/main/java/com/codejoust/main/service/ReportService.java +++ b/src/main/java/com/codejoust/main/service/ReportService.java @@ -54,10 +54,46 @@ public GameReport createGameReport(Game game) { int numProblems = game.getProblems().size(); int numPlayers = game.getPlayers().size(); - // Initialize the statistic variables for each problem. + // Compute the statistic variables per problem, add submission reports. int[] userSolved = new int[numProblems]; double[] totalTestCasesPassed = new double[numProblems]; double[] totalAttemptCount = new double[numProblems]; + computeGameStatisticsAndAddSubmissions(gameReport, game, numProblems, + numPlayers, userSolved, totalTestCasesPassed, totalAttemptCount); + + setGameReportStatistics(gameReport, game, numPlayers, userSolved, + totalTestCasesPassed); + + createProblemContainers(gameReport, game, numProblems, numPlayers, + userSolved, totalTestCasesPassed, totalAttemptCount); + gameReportRepository.save(gameReport); + + addGameReportAccounts(gameReport, game); + + // Log the completion of the latest game report. + log.info("Created game report for game with Room ID {}", game.getRoom().getRoomId()); + return gameReport; + } + + /** + * Compute the game statistics userSolved, totalTestCasesPassed, and + * totalAttemptCount. Submission reports and submission group reports + * were also added. + * + * @param gameReport The game report in progress. + * @param game The game that has just concluded. + * @param numProblems The number of problems in the game. + * @param numPlayers The number of players (non-spectators) in the game. + * @param userSolved The number of users that solved each problem. + * @param totalTestCasesPassed The total test cases passed across all + * users for each problem. + * @param totalAttemptCount The total attempt count across all users for + * each problem. + */ + public void computeGameStatisticsAndAddSubmissions(GameReport gameReport, + Game game, int numProblems, int numPlayers, int[] userSolved, + double[] totalTestCasesPassed, double[] totalAttemptCount) { + for (Player player : game.getPlayers().values()) { // For each user, add the relevant submission info. User user = player.getUser(); @@ -98,6 +134,21 @@ public GameReport createGameReport(Game game) { user.addSubmissionGroupReport(submissionGroupReport); gameReport.addUser(user); } + } + + /** + * Set the game report statistics, including the timing information, + * the game end type, and test case and solve data. + * + * @param gameReport The game report in progress. + * @param game The game that has just concluded. + * @param numPlayers The number of players (non-spectators) in the game. + * @param userSolved The number of users that solved each problem. + * @param totalTestCasesPassed The total test cases passed across all + * users for each problem. + */ + public void setGameReportStatistics(GameReport gameReport, Game game, + int numPlayers, int[] userSolved, double[] totalTestCasesPassed) { Instant startTime = game.getGameTimer().getStartTime(); gameReport.setCreatedDateTime(startTime); @@ -113,15 +164,6 @@ public GameReport createGameReport(Game game) { gameReport.setAverageTestCasesPassed(Arrays.stream(totalTestCasesPassed).sum() / numPlayers); gameReport.setAverageProblemsSolved((double) Arrays.stream(userSolved).sum() / numPlayers); - - createProblemContainers(gameReport, game, numProblems, numPlayers, userSolved, totalTestCasesPassed, totalAttemptCount); - gameReportRepository.save(gameReport); - - addGameReportAccounts(gameReport, game); - - // Log the completion of the latest game report. - log.info("Created game report for game with Room ID {}", game.getRoom().getRoomId()); - return gameReport; } /** From 2705ac52d010bfd50972e77293e24e0fd678b405 Mon Sep 17 00:00:00 2001 From: Chris Elliott Date: Sun, 1 Aug 2021 18:21:12 -0700 Subject: [PATCH 39/39] Remove blank lines in createGameReport method. --- src/main/java/com/codejoust/main/service/ReportService.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/com/codejoust/main/service/ReportService.java b/src/main/java/com/codejoust/main/service/ReportService.java index f485b6fc9..582c3440a 100644 --- a/src/main/java/com/codejoust/main/service/ReportService.java +++ b/src/main/java/com/codejoust/main/service/ReportService.java @@ -60,14 +60,11 @@ public GameReport createGameReport(Game game) { double[] totalAttemptCount = new double[numProblems]; computeGameStatisticsAndAddSubmissions(gameReport, game, numProblems, numPlayers, userSolved, totalTestCasesPassed, totalAttemptCount); - setGameReportStatistics(gameReport, game, numPlayers, userSolved, totalTestCasesPassed); - createProblemContainers(gameReport, game, numProblems, numPlayers, userSolved, totalTestCasesPassed, totalAttemptCount); gameReportRepository.save(gameReport); - addGameReportAccounts(gameReport, game); // Log the completion of the latest game report.