diff --git a/src/test/java/de/tum/in/www1/artemis/quiz/QuizExerciseIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/quiz/QuizExerciseIntegrationTest.java index bf787ccc5c12..bb13c9e33430 100644 --- a/src/test/java/de/tum/in/www1/artemis/quiz/QuizExerciseIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/quiz/QuizExerciseIntegrationTest.java @@ -7,6 +7,7 @@ import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; import java.util.*; +import java.util.concurrent.ScheduledThreadPoolExecutor; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -31,12 +32,12 @@ import de.tum.in.www1.artemis.domain.quiz.*; import de.tum.in.www1.artemis.repository.*; import de.tum.in.www1.artemis.security.SecurityUtils; -import de.tum.in.www1.artemis.service.ExerciseService; import de.tum.in.www1.artemis.service.QuizExerciseService; import de.tum.in.www1.artemis.util.ExerciseIntegrationTestUtils; import de.tum.in.www1.artemis.util.ModelFactory; import de.tum.in.www1.artemis.util.QuizUtilService; import de.tum.in.www1.artemis.web.rest.dto.QuizBatchJoinDTO; +import de.tum.in.www1.artemis.web.rest.dto.SearchResultPageDTO; import de.tum.in.www1.artemis.web.websocket.QuizSubmissionWebsocketService; class QuizExerciseIntegrationTest extends AbstractSpringIntegrationBambooBitbucketJiraTest { @@ -48,6 +49,9 @@ class QuizExerciseIntegrationTest extends AbstractSpringIntegrationBambooBitbuck @Autowired private QuizExerciseService quizExerciseService; + @Autowired + private QuizSubmissionWebsocketService quizSubmissionWebsocketService; + @Autowired private StudentParticipationRepository studentParticipationRepository; @@ -57,9 +61,6 @@ class QuizExerciseIntegrationTest extends AbstractSpringIntegrationBambooBitbuck @Autowired private QuizExerciseRepository quizExerciseRepository; - @Autowired - private QuizSubmissionWebsocketService quizSubmissionWebsocketService; - @Autowired private QuizSubmissionRepository quizSubmissionRepository; @@ -69,20 +70,12 @@ class QuizExerciseIntegrationTest extends AbstractSpringIntegrationBambooBitbuck @Autowired private QuizUtilService quizUtilService; - @Autowired - private QuizBatchRepository quizBatchRepository; - @Autowired private TeamRepository teamRepository; @Autowired private ExerciseIntegrationTestUtils exerciseIntegrationTestUtils; - @Autowired - private ExerciseService exerciseService; - - private QuizExercise quizExercise; - // helper attributes for shorter code in assert statements private final PointCounter pc01 = pc(0, 1); @@ -110,156 +103,32 @@ class QuizExerciseIntegrationTest extends AbstractSpringIntegrationBambooBitbuck @BeforeEach void init() { + quizScheduleService.stopSchedule(); database.addUsers(TEST_PREFIX, 1, 1, 1, 1); - quizScheduleService.startSchedule(5 * 1000); } @ParameterizedTest(name = "{displayName} [{index}] {argumentsWithNames}") @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") @EnumSource(QuizMode.class) void testCreateQuizExercise(QuizMode quizMode) throws Exception { - quizExercise = createQuizOnServer(ZonedDateTime.now().plusHours(5), null, quizMode); - - // General assertions - assertThat(quizExercise.getQuizQuestions()).as("Quiz questions were saved").hasSize(3); - assertThat(quizExercise.getDuration()).as("Quiz duration was correctly set").isEqualTo(3600); - assertThat(quizExercise.getDifficulty()).as("Quiz difficulty was correctly set").isEqualTo(DifficultyLevel.MEDIUM); - - // Quiz type specific assertions - for (QuizQuestion question : quizExercise.getQuizQuestions()) { - if (question instanceof MultipleChoiceQuestion multipleChoiceQuestion) { - assertThat(multipleChoiceQuestion.getAnswerOptions()).as("Multiple choice question answer options were saved").hasSize(2); - assertThat(multipleChoiceQuestion.getTitle()).as("Multiple choice question title is correct").isEqualTo("MC"); - assertThat(multipleChoiceQuestion.getText()).as("Multiple choice question text is correct").isEqualTo("Q1"); - assertThat(multipleChoiceQuestion.getPoints()).as("Multiple choice question score is correct").isEqualTo(4); - - List answerOptions = multipleChoiceQuestion.getAnswerOptions(); - assertThat(answerOptions.get(0).getText()).as("Text for answer option is correct").isEqualTo("A"); - assertThat(answerOptions.get(0).getHint()).as("Hint for answer option is correct").isEqualTo("H1"); - assertThat(answerOptions.get(0).getExplanation()).as("Explanation for answer option is correct").isEqualTo("E1"); - assertThat(answerOptions.get(0).isIsCorrect()).as("Is correct for answer option is correct").isTrue(); - assertThat(answerOptions.get(1).getText()).as("Text for answer option is correct").isEqualTo("B"); - assertThat(answerOptions.get(1).getHint()).as("Hint for answer option is correct").isEqualTo("H2"); - assertThat(answerOptions.get(1).getExplanation()).as("Explanation for answer option is correct").isEqualTo("E2"); - assertThat(answerOptions.get(1).isIsCorrect()).as("Is correct for answer option is correct").isFalse(); - } - else if (question instanceof DragAndDropQuestion dragAndDropQuestion) { - assertThat(dragAndDropQuestion.getDropLocations()).as("Drag and drop question drop locations were saved").hasSize(3); - assertThat(dragAndDropQuestion.getDragItems()).as("Drag and drop question drag items were saved").hasSize(3); - assertThat(dragAndDropQuestion.getTitle()).as("Drag and drop question title is correct").isEqualTo("DnD"); - assertThat(dragAndDropQuestion.getText()).as("Drag and drop question text is correct").isEqualTo("Q2"); - assertThat(dragAndDropQuestion.getPoints()).as("Drag and drop question score is correct").isEqualTo(3); - - List dropLocations = dragAndDropQuestion.getDropLocations(); - assertThat(dropLocations.get(0).getPosX()).as("Pos X for drop location is correct").isEqualTo(10); - assertThat(dropLocations.get(0).getPosY()).as("Pos Y for drop location is correct").isEqualTo(10); - assertThat(dropLocations.get(0).getWidth()).as("Width for drop location is correct").isEqualTo(10); - assertThat(dropLocations.get(0).getHeight()).as("Height for drop location is correct").isEqualTo(10); - assertThat(dropLocations.get(0).getQuestion()).isNotNull(); - assertThat(dropLocations.get(1).getPosX()).as("Pos X for drop location is correct").isEqualTo(20); - assertThat(dropLocations.get(1).getPosY()).as("Pos Y for drop location is correct").isEqualTo(20); - assertThat(dropLocations.get(1).getWidth()).as("Width for drop location is correct").isEqualTo(10); - assertThat(dropLocations.get(1).getHeight()).as("Height for drop location is correct").isEqualTo(10); - - List dragItems = dragAndDropQuestion.getDragItems(); - assertThat(dragItems.get(0).getText()).as("Text for drag item is correct").isEqualTo("D1"); - assertThat(dragItems.get(1).getText()).as("Text for drag item is correct").isEqualTo("D2"); - } - else if (question instanceof ShortAnswerQuestion shortAnswerQuestion) { - assertThat(shortAnswerQuestion.getSpots()).as("Short answer question spots were saved").hasSize(2); - assertThat(shortAnswerQuestion.getSolutions()).as("Short answer question solutions were saved").hasSize(2); - assertThat(shortAnswerQuestion.getTitle()).as("Short answer question title is correct").isEqualTo("SA"); - assertThat(shortAnswerQuestion.getText()).as("Short answer question text is correct").isEqualTo("This is a long answer text"); - assertThat(shortAnswerQuestion.getPoints()).as("Short answer question score is correct").isEqualTo(2); - - List spots = shortAnswerQuestion.getSpots(); - assertThat(spots.get(0).getSpotNr()).as("Spot nr for spot is correct").isZero(); - assertThat(spots.get(0).getWidth()).as("Width for spot is correct").isEqualTo(1); - assertThat(spots.get(1).getSpotNr()).as("Spot nr for spot is correct").isEqualTo(2); - assertThat(spots.get(1).getWidth()).as("Width for spot is correct").isEqualTo(2); - - List solutions = shortAnswerQuestion.getSolutions(); - assertThat(solutions.get(0).getText()).as("Text for solution is correct").isEqualTo("is"); - assertThat(solutions.get(1).getText()).as("Text for solution is correct").isEqualTo("long"); - } - } + QuizExercise quizExercise = createQuizOnServer(ZonedDateTime.now().plusHours(5), null, quizMode); + createdQuizAssert(quizExercise); } @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void testCreateQuizExerciseForExam() throws Exception { - quizExercise = createQuizOnServerForExam(); - - // General assertions - assertThat(quizExercise.getQuizQuestions()).as("Quiz questions were saved").hasSize(3); - assertThat(quizExercise.getDuration()).as("Quiz duration was correctly set").isEqualTo(3600); - assertThat(quizExercise.getDifficulty()).as("Quiz difficulty was correctly set").isEqualTo(DifficultyLevel.MEDIUM); - - // Quiz type specific assertions - for (QuizQuestion question : quizExercise.getQuizQuestions()) { - if (question instanceof MultipleChoiceQuestion multipleChoiceQuestion) { - assertThat(multipleChoiceQuestion.getAnswerOptions()).as("Multiple choice question answer options were saved").hasSize(2); - assertThat(multipleChoiceQuestion.getTitle()).as("Multiple choice question title is correct").isEqualTo("MC"); - assertThat(multipleChoiceQuestion.getText()).as("Multiple choice question text is correct").isEqualTo("Q1"); - assertThat(multipleChoiceQuestion.getPoints()).as("Multiple choice question score is correct").isEqualTo(4); - - List answerOptions = multipleChoiceQuestion.getAnswerOptions(); - assertThat(answerOptions.get(0).getText()).as("Text for answer option is correct").isEqualTo("A"); - assertThat(answerOptions.get(0).getHint()).as("Hint for answer option is correct").isEqualTo("H1"); - assertThat(answerOptions.get(0).getExplanation()).as("Explanation for answer option is correct").isEqualTo("E1"); - assertThat(answerOptions.get(0).isIsCorrect()).as("Is correct for answer option is correct").isTrue(); - assertThat(answerOptions.get(1).getText()).as("Text for answer option is correct").isEqualTo("B"); - assertThat(answerOptions.get(1).getHint()).as("Hint for answer option is correct").isEqualTo("H2"); - assertThat(answerOptions.get(1).getExplanation()).as("Explanation for answer option is correct").isEqualTo("E2"); - assertThat(answerOptions.get(1).isIsCorrect()).as("Is correct for answer option is correct").isFalse(); - } - else if (question instanceof DragAndDropQuestion dragAndDropQuestion) { - assertThat(dragAndDropQuestion.getDropLocations()).as("Drag and drop question drop locations were saved").hasSize(3); - assertThat(dragAndDropQuestion.getDragItems()).as("Drag and drop question drag items were saved").hasSize(3); - assertThat(dragAndDropQuestion.getTitle()).as("Drag and drop question title is correct").isEqualTo("DnD"); - assertThat(dragAndDropQuestion.getText()).as("Drag and drop question text is correct").isEqualTo("Q2"); - assertThat(dragAndDropQuestion.getPoints()).as("Drag and drop question score is correct").isEqualTo(3); - - List dropLocations = dragAndDropQuestion.getDropLocations(); - assertThat(dropLocations.get(0).getPosX()).as("Pos X for drop location is correct").isEqualTo(10); - assertThat(dropLocations.get(0).getPosY()).as("Pos Y for drop location is correct").isEqualTo(10); - assertThat(dropLocations.get(0).getWidth()).as("Width for drop location is correct").isEqualTo(10); - assertThat(dropLocations.get(0).getHeight()).as("Height for drop location is correct").isEqualTo(10); - assertThat(dropLocations.get(1).getPosX()).as("Pos X for drop location is correct").isEqualTo(20); - assertThat(dropLocations.get(1).getPosY()).as("Pos Y for drop location is correct").isEqualTo(20); - assertThat(dropLocations.get(1).getWidth()).as("Width for drop location is correct").isEqualTo(10); - assertThat(dropLocations.get(1).getHeight()).as("Height for drop location is correct").isEqualTo(10); - - List dragItems = dragAndDropQuestion.getDragItems(); - assertThat(dragItems.get(0).getText()).as("Text for drag item is correct").isEqualTo("D1"); - assertThat(dragItems.get(1).getText()).as("Text for drag item is correct").isEqualTo("D2"); - } - else if (question instanceof ShortAnswerQuestion shortAnswerQuestion) { - assertThat(shortAnswerQuestion.getSpots()).as("Short answer question spots were saved").hasSize(2); - assertThat(shortAnswerQuestion.getSolutions()).as("Short answer question solutions were saved").hasSize(2); - assertThat(shortAnswerQuestion.getTitle()).as("Short answer question title is correct").isEqualTo("SA"); - assertThat(shortAnswerQuestion.getText()).as("Short answer question text is correct").isEqualTo("This is a long answer text"); - assertThat(shortAnswerQuestion.getPoints()).as("Short answer question score is correct").isEqualTo(2); - - List spots = shortAnswerQuestion.getSpots(); - assertThat(spots.get(0).getSpotNr()).as("Spot nr for spot is correct").isZero(); - assertThat(spots.get(0).getWidth()).as("Width for spot is correct").isEqualTo(1); - assertThat(spots.get(1).getSpotNr()).as("Spot nr for spot is correct").isEqualTo(2); - assertThat(spots.get(1).getWidth()).as("Width for spot is correct").isEqualTo(2); - - List solutions = shortAnswerQuestion.getSolutions(); - assertThat(solutions.get(0).getText()).as("Text for solution is correct").isEqualTo("is"); - assertThat(solutions.get(1).getText()).as("Text for solution is correct").isEqualTo("long"); - } - } + QuizExercise quizExercise = createQuizOnServerForExam(); + createdQuizAssert(quizExercise); } @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void createQuizExercise_setCourseAndExerciseGroup_badRequest() throws Exception { - ExerciseGroup exerciseGroup = database.addExerciseGroupWithExamAndCourse(true); + ExerciseGroup exerciseGroup = database.createAndSaveActiveExerciseGroup(true); QuizExercise quizExercise = ModelFactory.generateQuizExerciseForExam(exerciseGroup); quizExercise.setCourse(exerciseGroup.getExam().getCourse()); + request.postWithResponseBody("/api/quiz-exercises/", quizExercise, QuizExercise.class, HttpStatus.BAD_REQUEST); } @@ -273,278 +142,131 @@ void createQuizExercise_setNeitherCourseAndExerciseGroup_badRequest() throws Exc @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void createQuizExercise_InvalidMaxScore() throws Exception { - quizExercise = createQuizOnServer(ZonedDateTime.now().plusHours(5), null, QuizMode.SYNCHRONIZED); + QuizExercise quizExercise = database.createQuiz(ZonedDateTime.now().plusHours(5), null, QuizMode.SYNCHRONIZED); quizExercise.setMaxPoints(0.0); + request.postWithResponseBody("/api/quiz-exercises", quizExercise, QuizExercise.class, HttpStatus.BAD_REQUEST); } @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void createQuizExercise_InvalidDates_badRequest() throws Exception { - quizExercise = createQuizOnServer(ZonedDateTime.now().plusHours(5), null, QuizMode.SYNCHRONIZED); + QuizExercise quizExercise = database.createQuiz(ZonedDateTime.now().plusHours(5), null, QuizMode.SYNCHRONIZED); quizExercise.getQuizBatches().forEach(batch -> batch.setStartTime(ZonedDateTime.now())); + request.postWithResponseBody("/api/quiz-exercises", quizExercise, QuizExercise.class, HttpStatus.BAD_REQUEST); } @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void createQuizExercise_IncludedAsBonusInvalidBonusPoints() throws Exception { - quizExercise = createQuizOnServer(ZonedDateTime.now().plusHours(5), null, QuizMode.SYNCHRONIZED); + QuizExercise quizExercise = database.createQuiz(ZonedDateTime.now().plusHours(5), null, QuizMode.SYNCHRONIZED); + quizExercise.setMaxPoints(10.0); quizExercise.setBonusPoints(1.0); quizExercise.setIncludedInOverallScore(IncludedInOverallScore.INCLUDED_AS_BONUS); + request.postWithResponseBody("/api/quiz-exercises", quizExercise, QuizExercise.class, HttpStatus.BAD_REQUEST); } @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void createQuizExercise_NotIncludedInvalidBonusPoints() throws Exception { - quizExercise = createQuizOnServer(ZonedDateTime.now().plusHours(5), null, QuizMode.SYNCHRONIZED); + QuizExercise quizExercise = database.createQuiz(ZonedDateTime.now().plusHours(5), null, QuizMode.SYNCHRONIZED); + quizExercise.setMaxPoints(10.0); quizExercise.setBonusPoints(1.0); quizExercise.setIncludedInOverallScore(IncludedInOverallScore.NOT_INCLUDED); + request.postWithResponseBody("/api/quiz-exercises", quizExercise, QuizExercise.class, HttpStatus.BAD_REQUEST); } @ParameterizedTest(name = "{displayName} [{index}] {argumentsWithNames}") @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") @EnumSource(QuizMode.class) - void testEditQuizExercise(QuizMode quizMode) throws Exception { - quizExercise = createQuizOnServer(ZonedDateTime.now().plusHours(5), null, quizMode); - - MultipleChoiceQuestion mc = (MultipleChoiceQuestion) quizExercise.getQuizQuestions().get(0); - mc.getAnswerOptions().remove(0); - mc.getAnswerOptions().add(new AnswerOption().text("C").hint("H3").explanation("E3").isCorrect(true)); - mc.getAnswerOptions().add(new AnswerOption().text("D").hint("H4").explanation("E4").isCorrect(true)); + void testUpdateQuizExercise(QuizMode quizMode) throws Exception { + QuizExercise quizExercise = createQuizOnServer(ZonedDateTime.now().plusHours(5), null, quizMode); + updateQuizAndAssert(quizExercise); + } - DragAndDropQuestion dnd = (DragAndDropQuestion) quizExercise.getQuizQuestions().get(1); - dnd.getDropLocations().remove(0); - dnd.getCorrectMappings().remove(0); - dnd.getDragItems().remove(0); + @Test + @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") + void testUpdateQuizExerciseForExam() throws Exception { + QuizExercise quizExercise = createQuizOnServerForExam(); + updateQuizAndAssert(quizExercise); + } - ShortAnswerQuestion sa = (ShortAnswerQuestion) quizExercise.getQuizQuestions().get(2); - sa.getSpots().remove(0); - sa.getSolutions().remove(0); - sa.getCorrectMappings().remove(0); + @Test + @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") + void testUpdateQuizExercise_SingleChoiceMC_AllOrNothing() throws Exception { + QuizExercise quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().plusHours(5), null, QuizMode.SYNCHRONIZED); - quizExercise = request.putWithResponseBody("/api/quiz-exercises", quizExercise, QuizExercise.class, HttpStatus.OK); + // multiple correct answers are not allowed + MultipleChoiceQuestion mc = (MultipleChoiceQuestion) quizExercise.getQuizQuestions().get(0); + mc.setSingleChoice(true); + mc.getAnswerOptions().get(1).setIsCorrect(true); - // Quiz type specific assertions - for (QuizQuestion question : quizExercise.getQuizQuestions()) { - if (question instanceof MultipleChoiceQuestion multipleChoiceQuestion) { - assertThat(multipleChoiceQuestion.getAnswerOptions()).as("Multiple choice question answer options were saved").hasSize(3); - assertThat(multipleChoiceQuestion.getTitle()).as("Multiple choice question title is correct").isEqualTo("MC"); - assertThat(multipleChoiceQuestion.getText()).as("Multiple choice question text is correct").isEqualTo("Q1"); - assertThat(multipleChoiceQuestion.getPoints()).as("Multiple choice question score is correct").isEqualTo(4); + request.putWithResponseBody("/api/quiz-exercises", quizExercise, QuizExercise.class, HttpStatus.BAD_REQUEST); + } - List answerOptions = multipleChoiceQuestion.getAnswerOptions(); - assertThat(answerOptions.get(0).getText()).as("Text for answer option is correct").isEqualTo("B"); - assertThat(answerOptions.get(0).getHint()).as("Hint for answer option is correct").isEqualTo("H2"); - assertThat(answerOptions.get(0).getExplanation()).as("Explanation for answer option is correct").isEqualTo("E2"); - assertThat(answerOptions.get(0).isIsCorrect()).as("Is correct for answer option is correct").isFalse(); - assertThat(answerOptions.get(1).getText()).as("Text for answer option is correct").isEqualTo("C"); - assertThat(answerOptions.get(1).getHint()).as("Hint for answer option is correct").isEqualTo("H3"); - assertThat(answerOptions.get(1).getExplanation()).as("Explanation for answer option is correct").isEqualTo("E3"); - assertThat(answerOptions.get(1).isIsCorrect()).as("Is correct for answer option is correct").isTrue(); - assertThat(answerOptions.get(2).getText()).as("Text for answer option is correct").isEqualTo("D"); - assertThat(answerOptions.get(2).getHint()).as("Hint for answer option is correct").isEqualTo("H4"); - assertThat(answerOptions.get(2).getExplanation()).as("Explanation for answer option is correct").isEqualTo("E4"); - assertThat(answerOptions.get(2).isIsCorrect()).as("Is correct for answer option is correct").isTrue(); - } - else if (question instanceof DragAndDropQuestion dragAndDropQuestion) { - assertThat(dragAndDropQuestion.getDropLocations()).as("Drag and drop question drop locations were saved").hasSize(2); - assertThat(dragAndDropQuestion.getDragItems()).as("Drag and drop question drag items were saved").hasSize(2); - assertThat(dragAndDropQuestion.getTitle()).as("Drag and drop question title is correct").isEqualTo("DnD"); - assertThat(dragAndDropQuestion.getText()).as("Drag and drop question text is correct").isEqualTo("Q2"); - assertThat(dragAndDropQuestion.getPoints()).as("Drag and drop question score is correct").isEqualTo(3); + @ParameterizedTest(name = "{displayName} [{index}] {argumentsWithNames}") + @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") + @EnumSource(value = ScoringType.class, names = { "PROPORTIONAL_WITHOUT_PENALTY", "PROPORTIONAL_WITH_PENALTY" }) + void testUpdateQuizExercise_SingleChoiceMC_badRequest(ScoringType scoringType) throws Exception { + QuizExercise quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().plusHours(5), null, QuizMode.SYNCHRONIZED); - List dropLocations = dragAndDropQuestion.getDropLocations(); - assertThat(dropLocations.get(0).getPosX()).as("Pos X for drop location is correct").isEqualTo(20); - assertThat(dropLocations.get(0).getPosY()).as("Pos Y for drop location is correct").isEqualTo(20); - assertThat(dropLocations.get(0).getWidth()).as("Width for drop location is correct").isEqualTo(10); - assertThat(dropLocations.get(0).getHeight()).as("Height for drop location is correct").isEqualTo(10); + MultipleChoiceQuestion mc = (MultipleChoiceQuestion) quizExercise.getQuizQuestions().get(0); + mc.setSingleChoice(true); + mc.setScoringType(scoringType); - List dragItems = dragAndDropQuestion.getDragItems(); - assertThat(dragItems.get(0).getText()).as("Text for drag item is correct").isEqualTo("D2"); - } - else if (question instanceof ShortAnswerQuestion shortAnswerQuestion) { - assertThat(shortAnswerQuestion.getSpots()).as("Short answer question spots were saved").hasSize(1); - assertThat(shortAnswerQuestion.getSolutions()).as("Short answer question solutions were saved").hasSize(1); - assertThat(shortAnswerQuestion.getTitle()).as("Short answer question title is correct").isEqualTo("SA"); - assertThat(shortAnswerQuestion.getText()).as("Short answer question text is correct").isEqualTo("This is a long answer text"); - assertThat(shortAnswerQuestion.getPoints()).as("Short answer question score is correct").isEqualTo(2); + request.putWithResponseBody("/api/quiz-exercises", quizExercise, QuizExercise.class, HttpStatus.BAD_REQUEST); + } - List spots = shortAnswerQuestion.getSpots(); - assertThat(spots.get(0).getSpotNr()).as("Spot nr for spot is correct").isEqualTo(2); - assertThat(spots.get(0).getWidth()).as("Width for spot is correct").isEqualTo(2); + @Test + @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") + void updateQuizExercise_setCourseAndExerciseGroup_badRequest() throws Exception { + ExerciseGroup exerciseGroup = database.createAndSaveActiveExerciseGroup(true); + QuizExercise quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().plusHours(5), null, QuizMode.SYNCHRONIZED); + quizExercise.setExerciseGroup(exerciseGroup); - List solutions = shortAnswerQuestion.getSolutions(); - assertThat(solutions.get(0).getText()).as("Text for solution is correct").isEqualTo("long"); - } - } + request.putWithResponseBody("/api/quiz-exercises/", quizExercise, QuizExercise.class, HttpStatus.BAD_REQUEST); } @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") - void testEditQuizExercise_SingleChoiceMC_AllOrNothing() throws Exception { - quizExercise = createQuizOnServer(ZonedDateTime.now().plusHours(5), null, QuizMode.SYNCHRONIZED); - - MultipleChoiceQuestion mc = (MultipleChoiceQuestion) quizExercise.getQuizQuestions().get(0); - mc.setSingleChoice(true); - mc.setScoringType(ScoringType.ALL_OR_NOTHING); - quizExercise = request.putWithResponseBody("/api/quiz-exercises", quizExercise, QuizExercise.class, HttpStatus.OK); - assertThat(quizExercise.getQuizQuestions().get(0).getScoringType()).as("Scoring type was changed").isEqualTo(ScoringType.ALL_OR_NOTHING); + void updateQuizExercise_setNeitherCourseAndExerciseGroup_badRequest() throws Exception { + QuizExercise quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().plusHours(5), null, QuizMode.SYNCHRONIZED); + quizExercise.setCourse(null); - // multiple correct answers are not allowed - mc = (MultipleChoiceQuestion) quizExercise.getQuizQuestions().get(0); - mc.getAnswerOptions().get(1).setIsCorrect(true); - quizExercise = request.putWithResponseBody("/api/quiz-exercises", quizExercise, QuizExercise.class, HttpStatus.BAD_REQUEST); + request.putWithResponseBody("/api/quiz-exercises/", quizExercise, QuizExercise.class, HttpStatus.BAD_REQUEST); } @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") - void testEditQuizExercise_SingleChoiceMC_Proportional() throws Exception { - quizExercise = createQuizOnServer(ZonedDateTime.now().plusHours(5), null, QuizMode.SYNCHRONIZED); + void updateQuizExercise_invalidDates_badRequest() throws Exception { + QuizExercise quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().plusHours(5), null, QuizMode.SYNCHRONIZED); + quizExercise.getQuizBatches().forEach(batch -> batch.setStartTime(ZonedDateTime.now())); - MultipleChoiceQuestion mc = (MultipleChoiceQuestion) quizExercise.getQuizQuestions().get(0); - mc.setSingleChoice(true); - mc.setScoringType(ScoringType.PROPORTIONAL_WITHOUT_PENALTY); - quizExercise = request.putWithResponseBody("/api/quiz-exercises", quizExercise, QuizExercise.class, HttpStatus.BAD_REQUEST); + request.putWithResponseBody("/api/quiz-exercises/", quizExercise, QuizExercise.class, HttpStatus.BAD_REQUEST); } @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") - void testEditQuizExercise_SingleChoiceMC_ProportionalPenalty() throws Exception { - quizExercise = createQuizOnServer(ZonedDateTime.now().plusHours(5), null, QuizMode.SYNCHRONIZED); + void updateQuizExercise_convertFromCourseToExamExercise_badRequest() throws Exception { + QuizExercise quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().plusHours(5), null, QuizMode.SYNCHRONIZED); + ExerciseGroup exerciseGroup = database.createAndSaveActiveExerciseGroup(true); - MultipleChoiceQuestion mc = (MultipleChoiceQuestion) quizExercise.getQuizQuestions().get(0); - mc.setSingleChoice(true); - mc.setScoringType(ScoringType.PROPORTIONAL_WITH_PENALTY); - quizExercise = request.putWithResponseBody("/api/quiz-exercises", quizExercise, QuizExercise.class, HttpStatus.BAD_REQUEST); + quizExercise.setCourse(null); + quizExercise.setExerciseGroup(exerciseGroup); + + request.putWithResponseBody("/api/quiz-exercises/", quizExercise, QuizExercise.class, HttpStatus.BAD_REQUEST); } @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") - void testEditQuizExerciseForExam() throws Exception { - quizExercise = createQuizOnServerForExam(); - - MultipleChoiceQuestion mc = (MultipleChoiceQuestion) quizExercise.getQuizQuestions().get(0); - mc.getAnswerOptions().remove(0); - mc.getAnswerOptions().add(new AnswerOption().text("C").hint("H3").explanation("E3").isCorrect(true)); - mc.getAnswerOptions().add(new AnswerOption().text("D").hint("H4").explanation("E4").isCorrect(true)); - - DragAndDropQuestion dnd = (DragAndDropQuestion) quizExercise.getQuizQuestions().get(1); - dnd.getDropLocations().remove(0); - dnd.getCorrectMappings().remove(0); - dnd.getDragItems().remove(0); - - ShortAnswerQuestion sa = (ShortAnswerQuestion) quizExercise.getQuizQuestions().get(2); - sa.getSpots().remove(0); - sa.getSolutions().remove(0); - sa.getCorrectMappings().remove(0); - - quizExercise = request.putWithResponseBody("/api/quiz-exercises", quizExercise, QuizExercise.class, HttpStatus.OK); - - // Quiz type specific assertions - for (QuizQuestion question : quizExercise.getQuizQuestions()) { - if (question instanceof MultipleChoiceQuestion multipleChoiceQuestion) { - assertThat(multipleChoiceQuestion.getAnswerOptions()).as("Multiple choice question answer options were saved").hasSize(3); - assertThat(multipleChoiceQuestion.getTitle()).as("Multiple choice question title is correct").isEqualTo("MC"); - assertThat(multipleChoiceQuestion.getText()).as("Multiple choice question text is correct").isEqualTo("Q1"); - assertThat(multipleChoiceQuestion.getPoints()).as("Multiple choice question score is correct").isEqualTo(4); - - List answerOptions = multipleChoiceQuestion.getAnswerOptions(); - assertThat(answerOptions.get(0).getText()).as("Text for answer option is correct").isEqualTo("B"); - assertThat(answerOptions.get(0).getHint()).as("Hint for answer option is correct").isEqualTo("H2"); - assertThat(answerOptions.get(0).getExplanation()).as("Explanation for answer option is correct").isEqualTo("E2"); - assertThat(answerOptions.get(0).isIsCorrect()).as("Is correct for answer option is correct").isFalse(); - assertThat(answerOptions.get(1).getText()).as("Text for answer option is correct").isEqualTo("C"); - assertThat(answerOptions.get(1).getHint()).as("Hint for answer option is correct").isEqualTo("H3"); - assertThat(answerOptions.get(1).getExplanation()).as("Explanation for answer option is correct").isEqualTo("E3"); - assertThat(answerOptions.get(1).isIsCorrect()).as("Is correct for answer option is correct").isTrue(); - assertThat(answerOptions.get(2).getText()).as("Text for answer option is correct").isEqualTo("D"); - assertThat(answerOptions.get(2).getHint()).as("Hint for answer option is correct").isEqualTo("H4"); - assertThat(answerOptions.get(2).getExplanation()).as("Explanation for answer option is correct").isEqualTo("E4"); - assertThat(answerOptions.get(2).isIsCorrect()).as("Is correct for answer option is correct").isTrue(); - } - else if (question instanceof DragAndDropQuestion dragAndDropQuestion) { - assertThat(dragAndDropQuestion.getDropLocations()).as("Drag and drop question drop locations were saved").hasSize(2); - assertThat(dragAndDropQuestion.getDragItems()).as("Drag and drop question drag items were saved").hasSize(2); - assertThat(dragAndDropQuestion.getTitle()).as("Drag and drop question title is correct").isEqualTo("DnD"); - assertThat(dragAndDropQuestion.getText()).as("Drag and drop question text is correct").isEqualTo("Q2"); - assertThat(dragAndDropQuestion.getPoints()).as("Drag and drop question score is correct").isEqualTo(3); - - List dropLocations = dragAndDropQuestion.getDropLocations(); - assertThat(dropLocations.get(0).getPosX()).as("Pos X for drop location is correct").isEqualTo(20); - assertThat(dropLocations.get(0).getPosY()).as("Pos Y for drop location is correct").isEqualTo(20); - assertThat(dropLocations.get(0).getWidth()).as("Width for drop location is correct").isEqualTo(10); - assertThat(dropLocations.get(0).getHeight()).as("Height for drop location is correct").isEqualTo(10); - - List dragItems = dragAndDropQuestion.getDragItems(); - assertThat(dragItems.get(0).getText()).as("Text for drag item is correct").isEqualTo("D2"); - } - else if (question instanceof ShortAnswerQuestion shortAnswerQuestion) { - assertThat(shortAnswerQuestion.getSpots()).as("Short answer question spots were saved").hasSize(1); - assertThat(shortAnswerQuestion.getSolutions()).as("Short answer question solutions were saved").hasSize(1); - assertThat(shortAnswerQuestion.getTitle()).as("Short answer question title is correct").isEqualTo("SA"); - assertThat(shortAnswerQuestion.getText()).as("Short answer question text is correct").isEqualTo("This is a long answer text"); - assertThat(shortAnswerQuestion.getPoints()).as("Short answer question score is correct").isEqualTo(2); - - List spots = shortAnswerQuestion.getSpots(); - assertThat(spots.get(0).getSpotNr()).as("Spot nr for spot is correct").isEqualTo(2); - assertThat(spots.get(0).getWidth()).as("Width for spot is correct").isEqualTo(2); - - List solutions = shortAnswerQuestion.getSolutions(); - assertThat(solutions.get(0).getText()).as("Text for solution is correct").isEqualTo("long"); - } - } - } - - @Test - @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") - void updateQuizExercise_setCourseAndExerciseGroup_badRequest() throws Exception { - ExerciseGroup exerciseGroup = database.addExerciseGroupWithExamAndCourse(true); - QuizExercise quizExercise = createQuizOnServer(ZonedDateTime.now().plusHours(5), null, QuizMode.SYNCHRONIZED); - quizExercise.setExerciseGroup(exerciseGroup); - - request.putWithResponseBody("/api/quiz-exercises/", quizExercise, QuizExercise.class, HttpStatus.BAD_REQUEST); - } - - @Test - @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") - void updateQuizExercise_setNeitherCourseAndExerciseGroup_badRequest() throws Exception { - QuizExercise quizExercise = createQuizOnServer(ZonedDateTime.now().plusHours(5), null, QuizMode.SYNCHRONIZED); - quizExercise.setCourse(null); - - request.putWithResponseBody("/api/quiz-exercises/", quizExercise, QuizExercise.class, HttpStatus.BAD_REQUEST); - } - - @Test - @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") - void updateQuizExercise_InvalidDates_badRequest() throws Exception { - QuizExercise quizExercise = createQuizOnServer(ZonedDateTime.now().plusHours(5), null, QuizMode.SYNCHRONIZED); - quizExercise.getQuizBatches().forEach(batch -> batch.setStartTime(ZonedDateTime.now())); - request.putWithResponseBody("/api/quiz-exercises/", quizExercise, QuizExercise.class, HttpStatus.BAD_REQUEST); - } - - @Test - @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") - void updateQuizExercise_convertFromCourseToExamExercise_badRequest() throws Exception { - QuizExercise quizExercise = createQuizOnServer(ZonedDateTime.now().plusHours(5), null, QuizMode.SYNCHRONIZED); - ExerciseGroup exerciseGroup = database.addExerciseGroupWithExamAndCourse(true); - - quizExercise.setCourse(null); - quizExercise.setExerciseGroup(exerciseGroup); - - request.putWithResponseBody("/api/quiz-exercises/", quizExercise, QuizExercise.class, HttpStatus.BAD_REQUEST); - } - - @Test - @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") - void updateQuizExercise_convertFromExamToCourseExercise_badRequest() throws Exception { - Course course = database.addEmptyCourse(); - database.addExerciseGroupWithExamAndCourse(true); - QuizExercise quizExercise = createQuizOnServerForExam(); + void updateQuizExercise_convertFromExamToCourseExercise_badRequest() throws Exception { + QuizExercise quizExercise = database.createAndSaveExamQuiz(ZonedDateTime.now().plusDays(1), ZonedDateTime.now().plusDays(2)); + Course course = database.addEmptyCourse(); quizExercise.setExerciseGroup(null); quizExercise.setCourse(course); @@ -552,73 +274,11 @@ void updateQuizExercise_convertFromExamToCourseExercise_badRequest() throws Exce request.putWithResponseBody("/api/quiz-exercises/", quizExercise, QuizExercise.class, HttpStatus.BAD_REQUEST); } - private QuizExercise createQuizOnServer(ZonedDateTime releaseDate, ZonedDateTime dueDate, QuizMode quizMode) throws Exception { - Course course = database.createCourse(); - - QuizExercise quizExercise = database.createQuiz(course, releaseDate, dueDate, quizMode); - quizExercise.setDuration(3600); - QuizExercise quizExerciseServer = request.postWithResponseBody("/api/quiz-exercises", quizExercise, QuizExercise.class, HttpStatus.CREATED); - QuizExercise quizExerciseDatabase = quizExerciseRepository.findOneWithQuestionsAndStatistics(quizExerciseServer.getId()); - assertThat(quizExerciseDatabase).isNotNull(); - checkQuizExercises(quizExercise, quizExerciseServer); - checkQuizExercises(quizExercise, quizExerciseDatabase); - - for (int i = 0; i < quizExercise.getQuizQuestions().size(); i++) { - var question = quizExercise.getQuizQuestions().get(i); - var questionServer = quizExerciseServer.getQuizQuestions().get(i); - var questionDatabase = quizExerciseDatabase.getQuizQuestions().get(i); - - assertThat(question.getId()).as("Question IDs are correct").isNull(); - assertThat(questionDatabase.getId()).as("Question IDs are correct").isEqualTo(questionServer.getId()); - - assertThat(question.getExercise().getId()).as("Exercise IDs are correct").isNull(); - assertThat(questionDatabase.getExercise().getId()).as("Exercise IDs are correct").isEqualTo(quizExerciseDatabase.getId()); - - assertThat(question.getTitle()).as("Question titles are correct").isEqualTo(questionDatabase.getTitle()); - assertThat(question.getTitle()).as("Question titles are correct").isEqualTo(questionServer.getTitle()); - - assertThat(question.getPoints()).as("Question scores are correct").isEqualTo(questionDatabase.getPoints()); - assertThat(question.getPoints()).as("Question scores are correct").isEqualTo(questionServer.getPoints()); - } - return quizExerciseDatabase; // TODO: @sleiss: Check if this is fine - } - - private QuizExercise createQuizOnServerForExam() throws Exception { - ExerciseGroup exerciseGroup = database.addExerciseGroupWithExamAndCourse(true); - - QuizExercise quizExercise = database.createQuizForExam(exerciseGroup); - quizExercise.setDuration(3600); - QuizExercise quizExerciseServer = request.postWithResponseBody("/api/quiz-exercises", quizExercise, QuizExercise.class, HttpStatus.CREATED); - QuizExercise quizExerciseDatabase = quizExerciseRepository.findOneWithQuestionsAndStatistics(quizExerciseServer.getId()); - assertThat(quizExerciseDatabase).isNotNull(); - checkQuizExercises(quizExercise, quizExerciseServer); - checkQuizExercises(quizExercise, quizExerciseDatabase); - - for (int i = 0; i < quizExercise.getQuizQuestions().size(); i++) { - var question = quizExercise.getQuizQuestions().get(i); - var questionServer = quizExerciseServer.getQuizQuestions().get(i); - var questionDatabase = quizExerciseDatabase.getQuizQuestions().get(i); - - assertThat(question.getId()).as("Question IDs are correct").isNull(); - assertThat(questionDatabase.getId()).as("Question IDs are correct").isEqualTo(questionServer.getId()); - - assertThat(question.getExercise().getId()).as("Exercise IDs are correct").isNull(); - assertThat(questionDatabase.getExercise().getId()).as("Exercise IDs are correct").isEqualTo(quizExerciseDatabase.getId()); - - assertThat(question.getTitle()).as("Question titles are correct").isEqualTo(questionDatabase.getTitle()); - assertThat(question.getTitle()).as("Question titles are correct").isEqualTo(questionServer.getTitle()); - - assertThat(question.getPoints()).as("Question scores are correct").isEqualTo(questionDatabase.getPoints()); - assertThat(question.getPoints()).as("Question scores are correct").isEqualTo(questionServer.getPoints()); - } - return quizExerciseServer; - } - @ParameterizedTest(name = "{displayName} [{index}] {argumentsWithNames}") @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") @EnumSource(QuizMode.class) void testDeleteQuizExercise(QuizMode quizMode) throws Exception { - quizExercise = createQuizOnServer(ZonedDateTime.now().plusHours(5), null, quizMode); + QuizExercise quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().plusHours(5), null, quizMode); assertThat(quizExerciseRepository.findOneWithQuestionsAndStatistics(quizExercise.getId())).as("Exercise is created correctly").isNotNull(); request.delete("/api/quiz-exercises/" + quizExercise.getId(), HttpStatus.OK); @@ -629,11 +289,10 @@ void testDeleteQuizExercise(QuizMode quizMode) throws Exception { @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") @EnumSource(QuizMode.class) void testDeleteQuizExerciseWithSubmittedAnswers(QuizMode quizMode) throws Exception { - quizExercise = createQuizOnServer(ZonedDateTime.now(), ZonedDateTime.now().plusMinutes(1), quizMode); - + QuizExercise quizExercise = database.createAndSaveQuiz(ZonedDateTime.now(), ZonedDateTime.now().plusMinutes(1), quizMode); assertThat(quizExerciseRepository.findOneWithQuestionsAndStatistics(quizExercise.getId())).as("Exercise is created correctly").isNotNull(); - final var username = TEST_PREFIX + "student1"; + String username = TEST_PREFIX + "student1"; final Principal principal = () -> username; QuizSubmission quizSubmission = database.generateSubmissionForThreeQuestions(quizExercise, 1, true, null); @@ -657,50 +316,48 @@ void testDeleteQuizExerciseWithSubmittedAnswers(QuizMode quizMode) throws Except @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void testUpdateNotExistingQuizExercise() throws Exception { - Course course = database.createCourse(); - QuizExercise quizExercise = database.createQuiz(course, ZonedDateTime.now().plusHours(5), null, QuizMode.SYNCHRONIZED); + QuizExercise quizExercise = database.createQuiz(ZonedDateTime.now().plusHours(5), null, QuizMode.SYNCHRONIZED); QuizExercise quizExerciseServer = request.putWithResponseBody("/api/quiz-exercises", quizExercise, QuizExercise.class, HttpStatus.CREATED); + + assertThat(quizExerciseServer).usingRecursiveComparison().ignoringFieldsOfTypes(ZonedDateTime.class, ScheduledThreadPoolExecutor.class) + .ignoringFields("id", "quizPointStatistic", "quizQuestions", "quizBatches").isEqualTo(quizExercise); + assertThat(quizExerciseServer.getId()).isNotNull(); - assertThat(quizExerciseServer.getQuizQuestions()).hasSize(quizExercise.getQuizQuestions().size()); - assertThat(quizExerciseServer.getCourseViaExerciseGroupOrCourseMember().getId()).isEqualTo(quizExercise.getCourseViaExerciseGroupOrCourseMember().getId()); - assertThat(quizExerciseServer.getMaxPoints()).isEqualTo(quizExercise.getMaxPoints()); - // TODO: check more values for equality + assertThat(quizExerciseServer.getQuizPointStatistic()).isNotNull(); + assertThat(quizExerciseServer.getQuizQuestions()).hasSameSizeAs(quizExercise.getQuizQuestions()); + assertThat(quizExerciseServer.getQuizBatches()).hasSameSizeAs(quizExercise.getQuizBatches()); } @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void testUpdateRunningQuizExercise() throws Exception { - Course course = database.createCourse(); - // create QuizExercise that already started - QuizExercise startedQuizExercise = database.createQuiz(course, ZonedDateTime.now().minusHours(1), null, QuizMode.SYNCHRONIZED); - QuizExercise startedQuizExerciseServer = request.postWithResponseBody("/api/quiz-exercises", startedQuizExercise, QuizExercise.class, HttpStatus.CREATED); + QuizExercise quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().minusHours(1), null, QuizMode.SYNCHRONIZED); - MultipleChoiceQuestion mc = (MultipleChoiceQuestion) startedQuizExerciseServer.getQuizQuestions().get(0); + MultipleChoiceQuestion mc = (MultipleChoiceQuestion) quizExercise.getQuizQuestions().get(0); mc.getAnswerOptions().remove(0); mc.getAnswerOptions().add(new AnswerOption().text("C").hint("H3").explanation("E3").isCorrect(true)); - mc.getAnswerOptions().add(new AnswerOption().text("D").hint("H4").explanation("E4").isCorrect(true)); - QuizExercise updatedQuizExercise = request.putWithResponseBody("/api/quiz-exercises", startedQuizExerciseServer, QuizExercise.class, HttpStatus.BAD_REQUEST); + QuizExercise updatedQuizExercise = request.putWithResponseBody("/api/quiz-exercises", quizExercise, QuizExercise.class, HttpStatus.BAD_REQUEST); assertThat(updatedQuizExercise).isNull(); } @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void testCreateExistingQuizExercise() throws Exception { - quizExercise = createQuizOnServer(ZonedDateTime.now().plusHours(5), null, QuizMode.SYNCHRONIZED); + QuizExercise quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().plusHours(5), null, QuizMode.SYNCHRONIZED); - QuizExercise newQuizExerciseServer = request.postWithResponseBody("/api/quiz-exercises", quizExercise, QuizExercise.class, HttpStatus.BAD_REQUEST); - assertThat(newQuizExerciseServer).isNull(); + request.postWithResponseBody("/api/quiz-exercises", quizExercise, QuizExercise.class, HttpStatus.BAD_REQUEST); } @ParameterizedTest(name = "{displayName} [{index}] {argumentsWithNames}") @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") @EnumSource(QuizMode.class) void testGetQuizExercise(QuizMode quizMode) throws Exception { - quizExercise = createQuizOnServer(ZonedDateTime.now().plusHours(5), null, quizMode); + QuizExercise quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().plusHours(5), null, quizMode); QuizExercise quizExerciseGet = request.get("/api/quiz-exercises/" + quizExercise.getId(), HttpStatus.OK, QuizExercise.class); checkQuizExercises(quizExercise, quizExerciseGet); + // Start Date picker at Quiz Edit page should be populated correctly if (quizMode == QuizMode.SYNCHRONIZED) { assertThat(quizExerciseGet.getQuizBatches()).isNotEmpty(); @@ -709,36 +366,32 @@ void testGetQuizExercise(QuizMode quizMode) throws Exception { // get all exercises for a course List allQuizExercisesForCourse = request.getList("/api/courses/" + quizExercise.getCourseViaExerciseGroupOrCourseMember().getId() + "/quiz-exercises", HttpStatus.OK, QuizExercise.class); - assertThat(allQuizExercisesForCourse).hasSize(1); - assertThat(allQuizExercisesForCourse.get(0)).isEqualTo(quizExercise); + assertThat(allQuizExercisesForCourse).hasSize(1).contains(quizExercise); } @ParameterizedTest(name = "{displayName} [{index}] {argumentsWithNames}") @WithMockUser(username = TEST_PREFIX + "student1", roles = "USER") @EnumSource(QuizMode.class) void testGetQuizExercise_asStudent(QuizMode quizMode) throws Exception { - Course course = database.createCourse(); - QuizExercise quizExercise = database.createQuiz(course, ZonedDateTime.now().minusHours(5), null, quizMode); + QuizExercise quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().minusHours(5), null, quizMode); quizExercise.setDuration(360); quizExercise.getQuizBatches().forEach(batch -> batch.setStartTime(ZonedDateTime.now().plusHours(5))); - quizExercise = quizExerciseService.save(quizExercise); - // get not started exercise for students + // get not yet started exercise for students QuizExercise quizExerciseForStudent_notStarted = request.get("/api/quiz-exercises/" + quizExercise.getId() + "/for-student", HttpStatus.OK, QuizExercise.class); checkQuizExerciseForStudent(quizExerciseForStudent_notStarted); - // get started exercise for students + // set exercise started 5 min ago quizExercise.getQuizBatches().forEach(batch -> batch.setStartTime(ZonedDateTime.now().minusMinutes(5))); - quizExercise = quizExerciseService.save(quizExercise); assertThat(quizExercise.getQuizBatches()).allMatch(QuizBatch::isStarted); assertThat(quizExercise.getQuizBatches()).allMatch(QuizBatch::isSubmissionAllowed); + // get started exercise for students QuizExercise quizExerciseForStudent_Started = request.get("/api/quiz-exercises/" + quizExercise.getId() + "/for-student", HttpStatus.OK, QuizExercise.class); checkQuizExerciseForStudent(quizExerciseForStudent_Started); // get finished exercise for students quizExerciseService.endQuiz(quizExercise, ZonedDateTime.now().minusMinutes(2)); - quizExercise = quizExerciseService.save(quizExercise); assertThat(quizExercise.getQuizBatches()).allMatch(QuizBatch::isStarted); assertThat(quizExercise.getQuizBatches()).noneMatch(QuizBatch::isSubmissionAllowed); @@ -749,32 +402,27 @@ void testGetQuizExercise_asStudent(QuizMode quizMode) throws Exception { @Test @WithMockUser(username = TEST_PREFIX + "student1", roles = "USER") void testSetQuizBatchStartTimeForNonSynchronizedQuizExercises_asStudent() throws Exception { - Course course = database.createCourse(); - QuizExercise quizExercise = database.createQuizWithQuizBatchedExercises(course, ZonedDateTime.now().minusHours(5), null, QuizMode.INDIVIDUAL); + QuizExercise quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().minusHours(5), null, QuizMode.INDIVIDUAL); quizExercise.setDuration(400); - // get started exercise for students quizExercise.getQuizBatches().forEach(batch -> batch.setStartTime(ZonedDateTime.now().minusMinutes(5))); - quizExercise = quizExerciseService.save(quizExercise); + // get started exercise for students // when exercise due date is null QuizExercise quizExerciseForStudent = request.get("/api/quiz-exercises/" + quizExercise.getId() + "/for-student", HttpStatus.OK, QuizExercise.class); - assertThat(quizExerciseForStudent.getQuizBatches()).allMatch(quizBatch -> quizBatch.getStartTime() != null); + assertThat(quizExerciseForStudent.getQuizBatches()).usingRecursiveAssertion().hasNoNullFields(); // when exercise due date is later than now quizExercise.setDueDate(ZonedDateTime.now().plusHours(1)); - quizExerciseService.save(quizExercise); quizExerciseForStudent = request.get("/api/quiz-exercises/" + quizExercise.getId() + "/for-student", HttpStatus.OK, QuizExercise.class); - assertThat(quizExerciseForStudent.getQuizBatches()).allMatch(quizBatch -> quizBatch.getStartTime() != null); + assertThat(quizExerciseForStudent.getQuizBatches()).usingRecursiveAssertion().hasNoNullFields(); } @Test @WithMockUser(username = TEST_PREFIX + "student1", roles = "USER") void testCreateQuizExerciseWithoutQuizBatchForSynchronizedQuiz_asStudent() throws Exception { - Course course = database.createCourse(); - QuizExercise quizExercise = database.createQuiz(course, ZonedDateTime.now().minusHours(4), null, QuizMode.SYNCHRONIZED); + QuizExercise quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().minusHours(4), null, QuizMode.SYNCHRONIZED); quizExercise.setDuration(400); quizExercise.setQuizBatches(null); - quizExercise = quizExerciseService.save(quizExercise); QuizExercise quizExerciseForStudent = request.get("/api/quiz-exercises/" + quizExercise.getId() + "/for-student", HttpStatus.OK, QuizExercise.class); assertThat(quizExerciseForStudent.getQuizBatches()).hasSize(1); @@ -784,8 +432,9 @@ void testCreateQuizExerciseWithoutQuizBatchForSynchronizedQuiz_asStudent() throw @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void testGetQuizExercisesForExam() throws Exception { - quizExercise = createQuizOnServerForExam(); - var examId = quizExercise.getExerciseGroup().getExam().getId(); + QuizExercise quizExercise = database.createAndSaveExamQuiz(ZonedDateTime.now().minusHours(4), ZonedDateTime.now().plusHours(4)); + long examId = quizExercise.getExerciseGroup().getExam().getId(); + List quizExercises = request.getList("/api/exams/" + examId + "/quiz-exercises", HttpStatus.OK, QuizExercise.class); assertThat(quizExercises).as("Quiz exercise was retrieved").hasSize(1); assertThat(quizExercise.getId()).as("Quiz exercise with the right id was retrieved").isEqualTo(quizExercises.get(0).getId()); @@ -794,7 +443,7 @@ void testGetQuizExercisesForExam() throws Exception { @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void testGetExamQuizExercise() throws Exception { - quizExercise = createQuizOnServerForExam(); + QuizExercise quizExercise = database.createAndSaveExamQuiz(ZonedDateTime.now().minusHours(5), ZonedDateTime.now().minusHours(10)); QuizExercise quizExerciseGet = request.get("/api/quiz-exercises/" + quizExercise.getId(), HttpStatus.OK, QuizExercise.class); checkQuizExercises(quizExercise, quizExerciseGet); @@ -807,69 +456,40 @@ void testGetExamQuizExercise() throws Exception { @Test @WithMockUser(username = TEST_PREFIX + "tutor1", roles = "TA") void testGetExamQuizExercise_asTutor_forbidden() throws Exception { - ExerciseGroup exerciseGroup = database.addExerciseGroupWithExamAndCourse(true); - quizExercise = database.createQuizForExam(exerciseGroup); - quizExercise = quizExerciseService.save(quizExercise); + QuizExercise quizExercise = database.createAndSaveExamQuiz(ZonedDateTime.now().minusHours(5), ZonedDateTime.now().minusHours(10)); request.get("/api/quiz-exercises/" + quizExercise.getId(), HttpStatus.FORBIDDEN, QuizExercise.class); } @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void testInstructorSearchTermMatchesId() throws Exception { - testSearchTermMatchesId(); - } + QuizExercise quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().minusDays(1), ZonedDateTime.now().minusHours(2), QuizMode.INDIVIDUAL); + long exerciseId = quizExercise.getId(); - @Test - @WithMockUser(username = "admin", roles = "ADMIN") - void testAdminSearchTermMatchesId() throws Exception { - testSearchTermMatchesId(); - } + var searchTerm = database.configureSearch(String.valueOf(exerciseId)); + SearchResultPageDTO searchResult = request.get("/api/quiz-exercises", HttpStatus.OK, SearchResultPageDTO.class, database.searchMapping(searchTerm)); - private void testSearchTermMatchesId() throws Exception { - final Course course = database.addEmptyCourse(); - final var now = ZonedDateTime.now(); - QuizExercise exercise = ModelFactory.generateQuizExercise(now.minusDays(1), now.minusHours(2), QuizMode.INDIVIDUAL, course); - exercise.setTitle("LoremIpsum"); - exercise = quizExerciseRepository.save(exercise); - var exerciseId = exercise.getId(); - - final var searchTerm = database.configureSearch(exerciseId.toString()); - final var searchResult = request.getSearchResult("/api/quiz-exercises", HttpStatus.OK, QuizExercise.class, database.searchMapping(searchTerm)); - assertThat(searchResult.getResultsOnPage().stream().filter(quizExercise -> (Objects.equals(quizExercise.getId(), exerciseId)))).hasSize(1); + assertThat(searchResult.getResultsOnPage()).filteredOn(result -> ((int) ((LinkedHashMap) result).get("id")) == exerciseId).hasSize(1); } @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void testCourseAndExamFiltersAsInstructor() throws Exception { - String exerciseTitle = setupFilterTestCase(TEST_PREFIX + "testCourseAndExamFiltersAsInstructor"); - exerciseIntegrationTestUtils.testCourseAndExamFilters("/api/quiz-exercises/", exerciseTitle); - } - @Test - @WithMockUser(username = "admin", roles = "ADMIN") - void testCourseAndExamFiltersAsAdmin() throws Exception { - String exerciseTitle = setupFilterTestCase(TEST_PREFIX + "testCourseAndExamFiltersAsAdmin"); - exerciseIntegrationTestUtils.testCourseAndExamFilters("/api/quiz-exercises/", exerciseTitle); - } + QuizExercise quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().minusDays(2), ZonedDateTime.now().minusHours(1), QuizMode.SYNCHRONIZED); + QuizExercise examQuizExercise = database.createAndSaveExamQuiz(ZonedDateTime.now().minusDays(1), ZonedDateTime.now().minusHours(2)); - private String setupFilterTestCase(String exerciseTitle) { - ExerciseGroup exerciseGroup = database.addExerciseGroupWithExamAndCourse(true); - quizExercise = database.createQuizForExam(exerciseGroup); - quizExercise.setTitle(exerciseTitle + "-Morpork"); - quizExerciseRepository.save(quizExercise); + String searchTerm = "very unique quiz title"; + database.renameAndSaveQuiz(quizExercise, searchTerm); + database.renameAndSaveQuiz(examQuizExercise, searchTerm + "-Morpork"); - final Course course = database.addEmptyCourse(); - final var now = ZonedDateTime.now(); - QuizExercise exercise = ModelFactory.generateQuizExercise(now.minusDays(1), now.minusHours(2), QuizMode.INDIVIDUAL, course); - exercise.setTitle(exerciseTitle); - quizExerciseRepository.save(exercise); - return exerciseTitle; + exerciseIntegrationTestUtils.testCourseAndExamFilters("/api/quiz-exercises/", searchTerm); } @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void testRecalculateStatistics() throws Exception { - quizExercise = createQuizOnServer(ZonedDateTime.now().plusHours(5), null, QuizMode.SYNCHRONIZED); + QuizExercise quizExercise = createQuizOnServer(ZonedDateTime.now().plusHours(5), null, QuizMode.SYNCHRONIZED); quizExercise.setReleaseDate(ZonedDateTime.now().minusHours(5)); quizExercise.setDueDate(ZonedDateTime.now().minusHours(2)); @@ -947,7 +567,7 @@ else if (pointCounter.getPoints().equals(9.0)) { @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void testReevaluateStatistics() throws Exception { - quizExercise = createQuizOnServer(ZonedDateTime.now().plusSeconds(5), null, QuizMode.SYNCHRONIZED); + QuizExercise quizExercise = createQuizOnServer(ZonedDateTime.now().plusSeconds(5), null, QuizMode.SYNCHRONIZED); // we expect a bad request because the quiz has not ended yet request.putWithResponseBody("/api/quiz-exercises/" + quizExercise.getId() + "/re-evaluate/", quizExercise, QuizExercise.class, HttpStatus.BAD_REQUEST); @@ -1052,8 +672,7 @@ void testReevaluateStatistics() throws Exception { @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void testReevaluateStatistics_Practice() throws Exception { - - quizExercise = createQuizOnServer(ZonedDateTime.now().plusSeconds(5), null, QuizMode.SYNCHRONIZED); + QuizExercise quizExercise = createQuizOnServer(ZonedDateTime.now().plusSeconds(5), null, QuizMode.SYNCHRONIZED); // use the exact other scoring types to cover all combinations in the tests quizExercise.getQuizQuestions().get(0).setScoringType(ScoringType.PROPORTIONAL_WITH_PENALTY); // MC quizExercise.getQuizQuestions().get(1).setScoringType(ScoringType.ALL_OR_NOTHING); // DnD @@ -1143,33 +762,10 @@ void testReevaluateStatistics_Practice() throws Exception { assertQuizPointStatisticsPointCounters(quizExerciseWithReevaluatedStatistics, Map.of(2.0, pc04, 6.0, pc06)); } - private PointCounter pc(int rated, int unrated) { - var pointCounter = new PointCounter(); - pointCounter.setRatedCounter(rated); - pointCounter.setUnRatedCounter(unrated); - return pointCounter; - } - - private void assertQuizPointStatisticsPointCounters(QuizExercise quizExercise, Map expectedPointCounters) { - for (PointCounter pointCounter : quizExercise.getQuizPointStatistic().getPointCounters()) { - var expectedPointCounter = expectedPointCounters.get(pointCounter.getPoints()); - if (expectedPointCounter != null) { - assertThat(pointCounter.getRatedCounter()).as(pointCounter.getPoints() + " should have a rated counter of " + expectedPointCounter.getRatedCounter()) - .isEqualTo(expectedPointCounter.getRatedCounter()); - assertThat(pointCounter.getUnRatedCounter()).as(pointCounter.getPoints() + " should have an unrated counter of " + expectedPointCounter.getUnRatedCounter()) - .isEqualTo(expectedPointCounter.getUnRatedCounter()); - } - else { - assertThat(pointCounter.getRatedCounter()).as(pointCounter.getPoints() + " should have a rated counter of 0").isZero(); - assertThat(pointCounter.getUnRatedCounter()).as(pointCounter.getPoints() + " should have a rated counter of 0").isZero(); - } - } - } - @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void testReEvaluateQuizQuestionWithMoreSolutions() throws Exception { - quizExercise = createQuizOnServer(ZonedDateTime.now().minusHours(5), ZonedDateTime.now().minusHours(2), QuizMode.SYNCHRONIZED); + QuizExercise quizExercise = createQuizOnServer(ZonedDateTime.now().minusHours(5), ZonedDateTime.now().minusHours(2), QuizMode.SYNCHRONIZED); QuizQuestion question = quizExercise.getQuizQuestions().get(2); if (question instanceof ShortAnswerQuestion shortAnswerQuestion) { @@ -1206,13 +802,13 @@ void testReEvaluateQuizQuestionWithMoreSolutions() throws Exception { @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void testPerformStartNow() throws Exception { - quizExercise = createQuizOnServer(ZonedDateTime.now().plusHours(5), null, QuizMode.SYNCHRONIZED); + QuizExercise quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().plusHours(5), null, QuizMode.SYNCHRONIZED); quizExercise.setReleaseDate(ZonedDateTime.now().minusHours(5)); - quizExercise = quizExerciseService.save(quizExercise); QuizExercise updatedQuizExercise = request.putWithResponseBody("/api/quiz-exercises/" + quizExercise.getId() + "/start-now", quizExercise, QuizExercise.class, HttpStatus.OK); - long millis = ChronoUnit.MILLIS.between(updatedQuizExercise.getQuizBatches().stream().findAny().get().getStartTime(), ZonedDateTime.now()); + + long millis = ChronoUnit.MILLIS.between(Objects.requireNonNull(updatedQuizExercise.getQuizBatches().stream().findAny().orElseThrow().getStartTime()), ZonedDateTime.now()); // actually the two dates should be "exactly" the same, but for the sake of slow CI testing machines and to prevent flaky tests, we live with the following rule assertThat(millis).isCloseTo(0, byLessThan(2000L)); assertThat(updatedQuizExercise.getQuizBatches()).allMatch(QuizBatch::isStarted); @@ -1222,9 +818,8 @@ void testPerformStartNow() throws Exception { @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") @EnumSource(value = QuizMode.class, names = { "SYNCHRONIZED" }, mode = EnumSource.Mode.EXCLUDE) void testPerformStartNow_invalidMode(QuizMode quizMode) throws Exception { - quizExercise = createQuizOnServer(ZonedDateTime.now().plusHours(5), null, quizMode); + QuizExercise quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().plusHours(5), null, quizMode); quizExercise.setReleaseDate(ZonedDateTime.now().minusHours(5)); - quizExercise = quizExerciseService.save(quizExercise); request.putWithResponseBody("/api/quiz-exercises/" + quizExercise.getId() + "/start-now", quizExercise, QuizExercise.class, HttpStatus.BAD_REQUEST); } @@ -1233,12 +828,13 @@ void testPerformStartNow_invalidMode(QuizMode quizMode) throws Exception { @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") @EnumSource(QuizMode.class) void testPerformSetVisible(QuizMode quizMode) throws Exception { - quizExercise = createQuizOnServer(ZonedDateTime.now().minusHours(5), null, quizMode); + QuizExercise quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().minusHours(5), null, quizMode); // we expect a bad request because the quiz is already visible request.putWithResponseBody("/api/quiz-exercises/" + quizExercise.getId() + "/set-visible", quizExercise, QuizExercise.class, HttpStatus.BAD_REQUEST); quizExercise.setReleaseDate(ZonedDateTime.now().plusDays(1)); quizExerciseService.save(quizExercise); + QuizExercise updatedQuizExercise = request.putWithResponseBody("/api/quiz-exercises/" + quizExercise.getId() + "/set-visible", quizExercise, QuizExercise.class, HttpStatus.OK); assertThat(updatedQuizExercise.isVisibleToStudents()).isTrue(); @@ -1248,74 +844,32 @@ void testPerformSetVisible(QuizMode quizMode) throws Exception { @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") @EnumSource(QuizMode.class) void testPerformOpenForPractice(QuizMode quizMode) throws Exception { - quizExercise = createQuizOnServer(ZonedDateTime.now().plusHours(5), null, quizMode); + QuizExercise quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().plusHours(5), null, quizMode); // we expect a bad request because the quiz has not ended yet request.putWithResponseBody("/api/quiz-exercises/" + quizExercise.getId() + "/open-for-practice", quizExercise, QuizExercise.class, HttpStatus.BAD_REQUEST); + quizExercise.setDuration(180); quizExerciseService.endQuiz(quizExercise, ZonedDateTime.now().minusMinutes(2)); quizExerciseService.save(quizExercise); + QuizExercise updatedQuizExercise = request.putWithResponseBody("/api/quiz-exercises/" + quizExercise.getId() + "/open-for-practice", quizExercise, QuizExercise.class, HttpStatus.OK); assertThat(updatedQuizExercise.isIsOpenForPractice()).isTrue(); } - private static List testPerformJoin_args() { - var now = ZonedDateTime.now(); - var longPast = now.minusHours(4); - var past = now.minusMinutes(30); - var future = now.plusMinutes(30); - var longFuture = now.plusHours(4); - - var batchLongPast = new QuizBatch(); - batchLongPast.setStartTime(longPast); - batchLongPast.setPassword("12345678"); - var batchPast = new QuizBatch(); - batchPast.setStartTime(past); - batchPast.setPassword("12345678"); - var batchFuture = new QuizBatch(); - batchFuture.setStartTime(future); - batchFuture.setPassword("12345678"); - - return List.of(Arguments.of(QuizMode.SYNCHRONIZED, future, null, null, null, HttpStatus.FORBIDDEN), // start in future - Arguments.of(QuizMode.SYNCHRONIZED, past, null, null, null, HttpStatus.NOT_FOUND), // synchronized - Arguments.of(QuizMode.SYNCHRONIZED, past, null, null, "12345678", HttpStatus.NOT_FOUND), // synchronized - Arguments.of(QuizMode.SYNCHRONIZED, longPast, past, null, null, HttpStatus.FORBIDDEN), // due date passed - Arguments.of(QuizMode.INDIVIDUAL, future, null, null, null, HttpStatus.FORBIDDEN), // start in future - Arguments.of(QuizMode.INDIVIDUAL, longPast, null, null, null, HttpStatus.OK), Arguments.of(QuizMode.INDIVIDUAL, longPast, longFuture, null, null, HttpStatus.OK), - Arguments.of(QuizMode.INDIVIDUAL, longPast, future, null, null, HttpStatus.OK), // NOTE: reduced working time because of due date - Arguments.of(QuizMode.INDIVIDUAL, longPast, past, null, null, HttpStatus.FORBIDDEN), // after due date - Arguments.of(QuizMode.BATCHED, future, null, null, null, HttpStatus.FORBIDDEN), // start in future - Arguments.of(QuizMode.BATCHED, longPast, null, null, null, HttpStatus.NOT_FOUND), // no pw - Arguments.of(QuizMode.BATCHED, longPast, longFuture, null, null, HttpStatus.NOT_FOUND), // no pw - Arguments.of(QuizMode.BATCHED, longPast, future, null, null, HttpStatus.NOT_FOUND), // no pw - Arguments.of(QuizMode.BATCHED, longPast, past, null, null, HttpStatus.FORBIDDEN), // after due date - Arguments.of(QuizMode.BATCHED, longPast, null, batchLongPast, null, HttpStatus.NOT_FOUND), // no pw - Arguments.of(QuizMode.BATCHED, longPast, null, batchPast, null, HttpStatus.NOT_FOUND), // no pw - Arguments.of(QuizMode.BATCHED, longPast, null, batchFuture, null, HttpStatus.NOT_FOUND), // no pw - Arguments.of(QuizMode.BATCHED, longPast, null, batchLongPast, "87654321", HttpStatus.NOT_FOUND), // wrong pw - Arguments.of(QuizMode.BATCHED, longPast, null, batchPast, "87654321", HttpStatus.NOT_FOUND), // wrong pw - Arguments.of(QuizMode.BATCHED, longPast, null, batchFuture, "87654321", HttpStatus.NOT_FOUND), // wrong pw - Arguments.of(QuizMode.BATCHED, longPast, null, batchLongPast, "12345678", HttpStatus.NOT_FOUND), // batch done - Arguments.of(QuizMode.BATCHED, longPast, null, batchPast, "12345678", HttpStatus.OK), // NOTE: reduced working time because batch had already started - Arguments.of(QuizMode.BATCHED, longPast, null, batchFuture, "12345678", HttpStatus.OK)); - } - @ParameterizedTest(name = "{displayName} [{index}] {argumentsWithNames}") @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") @MethodSource(value = "testPerformJoin_args") void testPerformJoin(QuizMode quizMode, ZonedDateTime release, ZonedDateTime due, QuizBatch batch, String password, HttpStatus result) throws Exception { - quizExercise = createQuizOnServer(release, due, quizMode); + QuizExercise quizExercise = createQuizOnServer(release, due, quizMode); if (batch != null) { - batch.setQuizExercise(quizExercise); - quizBatchRepository.save(batch); + database.setQuizBatchExerciseAndSave(batch, quizExercise); } - // switch to student SecurityContextHolder.getContext().setAuthentication(SecurityUtils.makeAuthorizationObject(TEST_PREFIX + "student1")); request.postWithResponseBody("/api/quiz-exercises/" + quizExercise.getId() + "/join", new QuizBatchJoinDTO(password), QuizBatch.class, result); - if (result == HttpStatus.OK) { // if joining was successful repeating the request should fail, otherwise with the same reason as the first attempt result = HttpStatus.BAD_REQUEST; @@ -1330,11 +884,10 @@ void testPerformJoin(QuizMode quizMode, ZonedDateTime release, ZonedDateTime due @Test @WithMockUser(username = TEST_PREFIX + "tutor1", roles = "TA") void testCreateQuizExerciseAsNonEditorForbidden() throws Exception { - Course course = database.createAndSaveCourse(null, ZonedDateTime.now().minusDays(1), null, Set.of()); - quizExercise = ModelFactory.generateQuizExercise(ZonedDateTime.now().plusDays(5), null, QuizMode.SYNCHRONIZED, course); + QuizExercise quizExercise = database.createQuiz(ZonedDateTime.now().plusDays(5), null, QuizMode.SYNCHRONIZED); request.postWithResponseBody("/api/quiz-exercises", quizExercise, QuizExercise.class, HttpStatus.FORBIDDEN); - assertThat(course.getExercises()).isEmpty(); + assertThat(quizExercise.getCourseViaExerciseGroupOrCourseMember().getExercises()).isEmpty(); } /** @@ -1343,7 +896,7 @@ void testCreateQuizExerciseAsNonEditorForbidden() throws Exception { @Test @WithMockUser(username = TEST_PREFIX + "student1", roles = "USER") void testGetAllQuizExercisesAsNonTutorForbidden() throws Exception { - quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().minusDays(1), null, QuizMode.SYNCHRONIZED); + QuizExercise quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().minusDays(1), null, QuizMode.SYNCHRONIZED); request.getList("/api/courses/" + quizExercise.getCourseViaExerciseGroupOrCourseMember().getId() + "/quiz-exercises", HttpStatus.FORBIDDEN, QuizExercise.class); } @@ -1355,7 +908,7 @@ void testGetAllQuizExercisesAsNonTutorForbidden() throws Exception { @WithMockUser(username = TEST_PREFIX + "tutor1", roles = "TA") @ValueSource(strings = { "start-now", "set-visible", "open-for-practice" }) void testPerformPutActionAsNonEditorForbidden(String action) throws Exception { - quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().plusDays(1), null, QuizMode.SYNCHRONIZED); + QuizExercise quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().plusDays(1), null, QuizMode.SYNCHRONIZED); request.put("/api/quiz-exercises/" + quizExercise.getId() + "/" + action, quizExercise, HttpStatus.FORBIDDEN); } @@ -1366,7 +919,7 @@ void testPerformPutActionAsNonEditorForbidden(String action) throws Exception { @Test @WithMockUser(username = TEST_PREFIX + "student1", roles = "USER") void testViewQuizExerciseAsNonTutorNotVisibleForbidden() throws Exception { - quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().plusDays(1), null, QuizMode.SYNCHRONIZED); + QuizExercise quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().plusDays(1), null, QuizMode.SYNCHRONIZED); request.get("/api/quiz-exercises/" + quizExercise.getId() + "/for-student", HttpStatus.FORBIDDEN, QuizExercise.class); } @@ -1377,7 +930,7 @@ void testViewQuizExerciseAsNonTutorNotVisibleForbidden() throws Exception { @Test @WithMockUser(username = TEST_PREFIX + "editor1", roles = "EDITOR") void testDeleteQuizExerciseAsNonInstructorForbidden() throws Exception { - quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().minusDays(1), null, QuizMode.SYNCHRONIZED); + QuizExercise quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().minusDays(1), null, QuizMode.SYNCHRONIZED); request.delete("/api/quiz-exercises/" + quizExercise.getId(), HttpStatus.FORBIDDEN); } @@ -1388,7 +941,7 @@ void testDeleteQuizExerciseAsNonInstructorForbidden() throws Exception { @Test @WithMockUser(username = TEST_PREFIX + "student1", roles = "USER") void testRecalculateStatisticsAsNonTutorForbidden() throws Exception { - quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().minusDays(1), ZonedDateTime.now().minusHours(1), QuizMode.SYNCHRONIZED); + QuizExercise quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().minusDays(1), ZonedDateTime.now().minusHours(1), QuizMode.SYNCHRONIZED); request.get("/api/quiz-exercises/" + quizExercise.getId() + "/recalculate-statistics", HttpStatus.FORBIDDEN, QuizExercise.class); } @@ -1411,7 +964,7 @@ void testGetQuizExerciseForNonTutorNotInCourseForbidden() throws Exception { @Test @WithMockUser(username = TEST_PREFIX + "editor1", roles = "EDITOR") void testReEvaluateQuizAsNonInstructorForbidden() throws Exception { - quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().minusDays(2), ZonedDateTime.now().plusDays(2), QuizMode.SYNCHRONIZED); + QuizExercise quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().minusDays(2), ZonedDateTime.now().plusDays(2), QuizMode.SYNCHRONIZED); request.put("/api/quiz-exercises/" + quizExercise.getId() + "/re-evaluate", quizExercise, HttpStatus.FORBIDDEN); } @@ -1422,7 +975,7 @@ void testReEvaluateQuizAsNonInstructorForbidden() throws Exception { @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void testUnfinishedExamReEvaluateBadRequest() throws Exception { - quizExercise = database.createAndSaveExamQuiz(ZonedDateTime.now().minusDays(2), ZonedDateTime.now().plusDays(2)); + QuizExercise quizExercise = database.createAndSaveExamQuiz(ZonedDateTime.now().minusDays(2), ZonedDateTime.now().plusDays(2)); request.put("/api/quiz-exercises/" + quizExercise.getId() + "/re-evaluate", quizExercise, HttpStatus.BAD_REQUEST); } @@ -1433,7 +986,7 @@ void testUnfinishedExamReEvaluateBadRequest() throws Exception { @Test @WithMockUser(username = TEST_PREFIX + "tutor1", roles = "TA") void testUpdateQuizExerciseAsNonEditorForbidden() throws Exception { - quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().minusDays(2), ZonedDateTime.now().minusHours(1), QuizMode.SYNCHRONIZED); + QuizExercise quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().minusDays(2), ZonedDateTime.now().minusHours(1), QuizMode.SYNCHRONIZED); quizExercise.setTitle("New Title"); request.put("/api/quiz-exercises", quizExercise, HttpStatus.FORBIDDEN); @@ -1445,7 +998,7 @@ void testUpdateQuizExerciseAsNonEditorForbidden() throws Exception { @Test @WithMockUser(username = TEST_PREFIX + "editor1", roles = "EDITOR") void testUpdateQuizExerciseInvalidBadRequest() throws Exception { - quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().minusDays(2), ZonedDateTime.now().minusHours(1), QuizMode.SYNCHRONIZED); + QuizExercise quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().minusDays(2), ZonedDateTime.now().minusHours(1), QuizMode.SYNCHRONIZED); assertThat(quizExercise.isValid()).isTrue(); // make the exercise invalid @@ -1460,25 +1013,12 @@ void testUpdateQuizExerciseInvalidBadRequest() throws Exception { @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void testUpdateQuizExerciseWithNotificationText() throws Exception { - quizExercise = createQuizOnServer(ZonedDateTime.now().plusHours(5), null, QuizMode.SYNCHRONIZED); - - MultipleChoiceQuestion mc = (MultipleChoiceQuestion) quizExercise.getQuizQuestions().get(0); - mc.getAnswerOptions().remove(0); - mc.getAnswerOptions().add(new AnswerOption().text("C").hint("H3").explanation("E3").isCorrect(true)); - mc.getAnswerOptions().add(new AnswerOption().text("D").hint("H4").explanation("E4").isCorrect(true)); - - DragAndDropQuestion dnd = (DragAndDropQuestion) quizExercise.getQuizQuestions().get(1); - dnd.getDropLocations().remove(0); - dnd.getCorrectMappings().remove(0); - dnd.getDragItems().remove(0); - - ShortAnswerQuestion sa = (ShortAnswerQuestion) quizExercise.getQuizQuestions().get(2); - sa.getSpots().remove(0); - sa.getSolutions().remove(0); - sa.getCorrectMappings().remove(0); + QuizExercise quizExercise = createQuizOnServer(ZonedDateTime.now().plusHours(5), null, QuizMode.SYNCHRONIZED); + updateMultipleChoice(quizExercise); var params = new LinkedMultiValueMap(); params.add("notificationText", "NotificationTextTEST!"); + request.putWithResponseBodyAndParams("/api/quiz-exercises", quizExercise, QuizExercise.class, HttpStatus.OK, params); // TODO check if notifications arrived correctly } @@ -1489,15 +1029,14 @@ void testUpdateQuizExerciseWithNotificationText() throws Exception { @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void importQuizExerciseToSameCourse() throws Exception { - var now = ZonedDateTime.now(); - Course course = database.addEmptyCourse(); - quizExercise = database.createQuiz(course, now.plusHours(2), null, QuizMode.SYNCHRONIZED); - quizExerciseService.save(quizExercise); + ZonedDateTime now = ZonedDateTime.now(); + QuizExercise quizExercise = database.createAndSaveQuiz(now.plusHours(2), null, QuizMode.SYNCHRONIZED); QuizExercise changedQuiz = quizExerciseRepository.findOneWithQuestionsAndStatistics(quizExercise.getId()); assertThat(changedQuiz).isNotNull(); changedQuiz.setTitle("New title"); changedQuiz.setReleaseDate(now); + QuizExercise importedExercise = request.postWithResponseBody("/api/quiz-exercises/import/" + changedQuiz.getId(), changedQuiz, QuizExercise.class, HttpStatus.CREATED); assertThat(importedExercise.getId()).as("Imported exercise has different id").isNotEqualTo(quizExercise.getId()); @@ -1505,7 +1044,8 @@ void importQuizExerciseToSameCourse() throws Exception { assertThat(importedExercise.getReleaseDate()).as("Imported exercise has updated release data").isEqualTo(now); assertThat(importedExercise.getCourseViaExerciseGroupOrCourseMember().getId()).as("Imported exercise has same course") .isEqualTo(quizExercise.getCourseViaExerciseGroupOrCourseMember().getId()); - assertThat(importedExercise.getQuizQuestions().size()).as("Imported exercise has same number of questions").isEqualTo(quizExercise.getQuizQuestions().size()); + assertThat(importedExercise.getCourseViaExerciseGroupOrCourseMember()).isEqualTo(quizExercise.getCourseViaExerciseGroupOrCourseMember()); + assertThat(importedExercise.getQuizQuestions()).as("Imported exercise has same number of questions").hasSameSizeAs(quizExercise.getQuizQuestions()); } /** @@ -1513,17 +1053,14 @@ void importQuizExerciseToSameCourse() throws Exception { */ @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") - void importQuizExerciseFromCourseToCourseT() throws Exception { - var now = ZonedDateTime.now(); - Course course1 = database.addEmptyCourse(); - Course course2 = database.addEmptyCourse(); - quizExercise = database.createQuiz(course1, now.plusHours(2), null, QuizMode.SYNCHRONIZED); - quizExerciseService.save(quizExercise); - quizExercise.setCourse(course2); + void importQuizExerciseFromCourseToCourse() throws Exception { + QuizExercise quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().plusHours(2), null, QuizMode.SYNCHRONIZED); - QuizExercise importedExercise = request.postWithResponseBody("/api/quiz-exercises/import/" + quizExercise.getId(), quizExercise, QuizExercise.class, HttpStatus.CREATED); + Course course = database.addEmptyCourse(); + quizExercise.setCourse(course); - assertThat(importedExercise.getCourseViaExerciseGroupOrCourseMember()).as("Quiz was imported for different course").isEqualTo(course2); + QuizExercise importedExercise = request.postWithResponseBody("/api/quiz-exercises/import/" + quizExercise.getId(), quizExercise, QuizExercise.class, HttpStatus.CREATED); + assertThat(importedExercise.getCourseViaExerciseGroupOrCourseMember()).as("Quiz was imported for different course").isEqualTo(course); } /** @@ -1532,36 +1069,27 @@ void importQuizExerciseFromCourseToCourseT() throws Exception { @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void importQuizExerciseFromCourseToExam() throws Exception { - var now = ZonedDateTime.now(); - Course course1 = database.addEmptyCourse(); - ExerciseGroup exerciseGroup1 = database.addExerciseGroupWithExamAndCourse(true); - quizExercise = database.createQuiz(course1, now.plusHours(2), null, QuizMode.SYNCHRONIZED); - quizExerciseService.save(quizExercise); - quizExercise.setReleaseDate(null); - quizExercise.setCourse(null); - quizExercise.setDueDate(null); - quizExercise.setAssessmentDueDate(null); - quizExercise.setQuizBatches(new HashSet<>()); - quizExercise.setExerciseGroup(exerciseGroup1); + ExerciseGroup exerciseGroup = database.createAndSaveActiveExerciseGroup(true); + QuizExercise quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().plusHours(2), null, QuizMode.SYNCHRONIZED); - QuizExercise importedExercise = request.postWithResponseBody("/api/quiz-exercises/import/" + quizExercise.getId(), quizExercise, QuizExercise.class, HttpStatus.CREATED); + database.emptyOutQuizExercise(quizExercise); + quizExercise.setExerciseGroup(exerciseGroup); - assertThat(importedExercise.getExerciseGroup()).as("Quiz was imported for different exercise group").isEqualTo(exerciseGroup1); + QuizExercise importedExercise = request.postWithResponseBody("/api/quiz-exercises/import/" + quizExercise.getId(), quizExercise, QuizExercise.class, HttpStatus.CREATED); + assertThat(importedExercise.getExerciseGroup()).as("Quiz was imported for different exercise group").isEqualTo(exerciseGroup); } /** * test import quiz exercise to exam with invalid roles */ @Test - @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "TA") + @WithMockUser(username = TEST_PREFIX + "tutor1", roles = "TA") void importQuizExerciseFromCourseToExam_forbidden() throws Exception { - var now = ZonedDateTime.now(); - Course course1 = database.addEmptyCourse(); - ExerciseGroup exerciseGroup1 = database.addExerciseGroupWithExamAndCourse(true); - quizExercise = database.createQuiz(course1, now.plusHours(2), null, QuizMode.SYNCHRONIZED); - quizExerciseService.save(quizExercise); - quizExercise.setCourse(null); - quizExercise.setExerciseGroup(exerciseGroup1); + ExerciseGroup exerciseGroup = database.createAndSaveActiveExerciseGroup(true); + QuizExercise quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().plusHours(2), null, QuizMode.SYNCHRONIZED); + + database.emptyOutQuizExercise(quizExercise); + quizExercise.setExerciseGroup(exerciseGroup); request.postWithResponseBody("/api/quiz-exercises/import/" + quizExercise.getId(), quizExercise, QuizExercise.class, HttpStatus.FORBIDDEN); } @@ -1572,28 +1100,27 @@ void importQuizExerciseFromCourseToExam_forbidden() throws Exception { @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void importQuizExerciseFromExamToCourse() throws Exception { - ExerciseGroup exerciseGroup1 = database.addExerciseGroupWithExamAndCourse(true); - quizExercise = database.createQuizForExam(exerciseGroup1); + QuizExercise quizExercise = database.createAndSaveExamQuiz(ZonedDateTime.now(), ZonedDateTime.now().plusDays(1)); + + quizExercise.setExerciseGroup(null); Course course1 = database.addEmptyCourse(); - quizExerciseService.save(quizExercise); quizExercise.setCourse(course1); - quizExercise.setExerciseGroup(null); - request.postWithResponseBody("/api/quiz-exercises/import/" + quizExercise.getId(), quizExercise, QuizExercise.class, HttpStatus.CREATED); + QuizExercise importedExercise = request.postWithResponseBody("/api/quiz-exercises/import/" + quizExercise.getId(), quizExercise, QuizExercise.class, HttpStatus.CREATED); + assertThat(importedExercise.getCourseViaExerciseGroupOrCourseMember()).isEqualTo(course1); } /** * test import quiz exercise from exam to course with invalid roles */ @Test - @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "TA") + @WithMockUser(username = TEST_PREFIX + "tutor1", roles = "TA") void importQuizExerciseFromExamToCourse_forbidden() throws Exception { - ExerciseGroup exerciseGroup1 = database.addExerciseGroupWithExamAndCourse(true); - quizExercise = database.createQuizForExam(exerciseGroup1); + QuizExercise quizExercise = database.createAndSaveExamQuiz(ZonedDateTime.now().plusDays(1), ZonedDateTime.now().plusDays(2)); + + quizExercise.setExerciseGroup(null); Course course1 = database.addEmptyCourse(); - quizExerciseService.save(quizExercise); quizExercise.setCourse(course1); - quizExercise.setExerciseGroup(null); request.postWithResponseBody("/api/quiz-exercises/import/" + quizExercise.getId(), quizExercise, QuizExercise.class, HttpStatus.FORBIDDEN); } @@ -1604,15 +1131,12 @@ void importQuizExerciseFromExamToCourse_forbidden() throws Exception { @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void importQuizExerciseFromExamToExam() throws Exception { - ExerciseGroup exerciseGroup1 = database.addExerciseGroupWithExamAndCourse(true); - ExerciseGroup exerciseGroup2 = database.addExerciseGroupWithExamAndCourse(true); - quizExercise = database.createQuizForExam(exerciseGroup1); - quizExerciseService.save(quizExercise); - quizExercise.setExerciseGroup(exerciseGroup2); + ExerciseGroup exerciseGroup = database.createAndSaveActiveExerciseGroup(true); + QuizExercise quizExercise = database.createAndSaveExamQuiz(ZonedDateTime.now().plusDays(1), ZonedDateTime.now().plusDays(2)); + quizExercise.setExerciseGroup(exerciseGroup); QuizExercise importedExercise = request.postWithResponseBody("/api/quiz-exercises/import/" + quizExercise.getId(), quizExercise, QuizExercise.class, HttpStatus.CREATED); - - assertThat(importedExercise.getExerciseGroup()).as("Quiz was imported for different exercise group").isEqualTo(exerciseGroup2); + assertThat(importedExercise.getExerciseGroup()).as("Quiz was imported for different exercise group").isEqualTo(exerciseGroup); } /** @@ -1620,11 +1144,8 @@ void importQuizExerciseFromExamToExam() throws Exception { */ @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") - void importTextExerciseFromCourseToCourse_badRequest() throws Exception { - var now = ZonedDateTime.now(); - Course course1 = database.addEmptyCourse(); - quizExercise = database.createQuiz(course1, now.plusHours(2), null, QuizMode.SYNCHRONIZED); - quizExerciseService.save(quizExercise); + void importQuizExerciseFromCourseToCourse_badRequest() throws Exception { + QuizExercise quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().plusHours(2), null, QuizMode.SYNCHRONIZED); quizExercise.setCourse(null); request.postWithResponseBody("/api/quiz-exercises/import/" + quizExercise.getId(), quizExercise, QuizExercise.class, HttpStatus.BAD_REQUEST); @@ -1636,34 +1157,25 @@ void importTextExerciseFromCourseToCourse_badRequest() throws Exception { @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void testImportQuizExercise_team_modeChange() throws Exception { - var now = ZonedDateTime.now(); - Course course1 = database.addEmptyCourse(); - Course course2 = database.addEmptyCourse(); - quizExercise = database.createQuiz(course1, now.plusHours(2), null, QuizMode.SYNCHRONIZED); - quizExerciseService.save(quizExercise); + QuizExercise quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().plusHours(2), null, QuizMode.SYNCHRONIZED); + Course course = database.addEmptyCourse(); QuizExercise changedQuiz = quizExerciseRepository.findOneWithQuestionsAndStatistics(quizExercise.getId()); assertThat(changedQuiz).isNotNull(); - changedQuiz.setMode(ExerciseMode.TEAM); - var teamAssignmentConfig = new TeamAssignmentConfig(); - teamAssignmentConfig.setExercise(changedQuiz); - teamAssignmentConfig.setMinTeamSize(1); - teamAssignmentConfig.setMaxTeamSize(10); - changedQuiz.setTeamAssignmentConfig(teamAssignmentConfig); - changedQuiz.setCourse(course2); - changedQuiz.setMaxPoints(1.0); + changedQuiz.setCourse(course); + database.setupTeamQuizExercise(changedQuiz, 1, 10); changedQuiz = request.postWithResponseBody("/api/quiz-exercises/import/" + quizExercise.getId(), changedQuiz, QuizExercise.class, HttpStatus.CREATED); - assertThat(changedQuiz.getCourseViaExerciseGroupOrCourseMember().getId()).isEqualTo(course2.getId()); + assertThat(changedQuiz.getCourseViaExerciseGroupOrCourseMember().getId()).isEqualTo(course.getId()); assertThat(changedQuiz.getMode()).isEqualTo(ExerciseMode.TEAM); - assertThat(changedQuiz.getTeamAssignmentConfig().getMinTeamSize()).isEqualTo(teamAssignmentConfig.getMinTeamSize()); - assertThat(changedQuiz.getTeamAssignmentConfig().getMaxTeamSize()).isEqualTo(teamAssignmentConfig.getMaxTeamSize()); + assertThat(changedQuiz.getTeamAssignmentConfig().getMinTeamSize()).isEqualTo(1); + assertThat(changedQuiz.getTeamAssignmentConfig().getMaxTeamSize()).isEqualTo(10); assertThat(teamRepository.findAllByExerciseIdWithEagerStudents(changedQuiz, null)).isEmpty(); - quizExercise = quizExerciseRepository.findById(quizExercise.getId()).get(); - assertThat(quizExercise.getCourseViaExerciseGroupOrCourseMember().getId()).isEqualTo(course1.getId()); + quizExercise = quizExerciseRepository.findByIdElseThrow(quizExercise.getId()); + assertThat(quizExercise.getMode()).isEqualTo(ExerciseMode.INDIVIDUAL); assertThat(quizExercise.getTeamAssignmentConfig()).isNull(); assertThat(teamRepository.findAllByExerciseIdWithEagerStudents(quizExercise, null)).isEmpty(); @@ -1675,38 +1187,23 @@ void testImportQuizExercise_team_modeChange() throws Exception { @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void testImportQuizExercise_individual_modeChange() throws Exception { - var now = ZonedDateTime.now(); - Course course1 = database.addEmptyCourse(); - Course course2 = database.addEmptyCourse(); - quizExercise = database.createQuiz(course1, now.plusHours(2), null, QuizMode.SYNCHRONIZED); - quizExercise.setMode(ExerciseMode.TEAM); - var teamAssignmentConfig = new TeamAssignmentConfig(); - teamAssignmentConfig.setExercise(quizExercise); - teamAssignmentConfig.setMinTeamSize(1); - teamAssignmentConfig.setMaxTeamSize(10); - quizExercise.setTeamAssignmentConfig(teamAssignmentConfig); - quizExercise.setCourse(course1); // remove line - - quizExercise = quizExerciseService.save(quizExercise); - var team = new Team(); - team.setShortName(TEST_PREFIX + "testImportQuizExercise_individual_modeChange"); - teamRepository.save(quizExercise, team); + QuizExercise quizExercise = database.createAndSaveTeamQuiz(ZonedDateTime.now().plusHours(2), null, QuizMode.SYNCHRONIZED, 2, 5); + Course course = database.addEmptyCourse(); QuizExercise changedQuiz = quizExerciseRepository.findOneWithQuestionsAndStatistics(quizExercise.getId()); assertThat(changedQuiz).isNotNull(); + changedQuiz.setMode(ExerciseMode.INDIVIDUAL); - changedQuiz.setCourse(course2); - changedQuiz.setMaxPoints(1.0); + changedQuiz.setCourse(course); changedQuiz = request.postWithResponseBody("/api/quiz-exercises/import/" + quizExercise.getId(), changedQuiz, QuizExercise.class, HttpStatus.CREATED); - assertThat(changedQuiz.getCourseViaExerciseGroupOrCourseMember().getId()).isEqualTo(course2.getId()); + assertThat(changedQuiz.getCourseViaExerciseGroupOrCourseMember().getId()).isEqualTo(course.getId()); assertThat(changedQuiz.getMode()).isEqualTo(ExerciseMode.INDIVIDUAL); assertThat(changedQuiz.getTeamAssignmentConfig()).isNull(); assertThat(teamRepository.findAllByExerciseIdWithEagerStudents(changedQuiz, null)).isEmpty(); - quizExercise = quizExerciseRepository.findById(quizExercise.getId()).get(); - assertThat(quizExercise.getCourseViaExerciseGroupOrCourseMember().getId()).isEqualTo(course1.getId()); + quizExercise = quizExerciseRepository.findByIdElseThrow(quizExercise.getId()); assertThat(quizExercise.getMode()).isEqualTo(ExerciseMode.TEAM); assertThat(teamRepository.findAllByExerciseIdWithEagerStudents(quizExercise, null)).hasSize(1); } @@ -1717,20 +1214,16 @@ void testImportQuizExercise_individual_modeChange() throws Exception { @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void testImportQuizExerciseChangeQuizMode() throws Exception { - var now = ZonedDateTime.now(); - Course course = database.addEmptyCourse(); - quizExercise = database.createQuiz(course, now.plusHours(2), null, QuizMode.SYNCHRONIZED); - quizExerciseService.save(quizExercise); + QuizExercise quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().plusHours(2), null, QuizMode.SYNCHRONIZED); QuizExercise changedQuiz = quizExerciseRepository.findOneWithQuestionsAndStatistics(quizExercise.getId()); assertThat(changedQuiz).isNotNull(); changedQuiz.setQuizMode(QuizMode.INDIVIDUAL); - changedQuiz.setReleaseDate(now); + QuizExercise importedExercise = request.postWithResponseBody("/api/quiz-exercises/import/" + changedQuiz.getId(), changedQuiz, QuizExercise.class, HttpStatus.CREATED); assertThat(importedExercise.getId()).as("Imported exercise has different id").isNotEqualTo(quizExercise.getId()); assertThat(importedExercise.getQuizMode()).as("Imported exercise has different quiz mode").isEqualTo(QuizMode.INDIVIDUAL); - assertThat(importedExercise.getReleaseDate()).as("Imported exercise has updated release data").isEqualTo(now); } /** @@ -1739,20 +1232,21 @@ void testImportQuizExerciseChangeQuizMode() throws Exception { @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void testRedundantActionsBadRequest() throws Exception { + QuizExercise quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().minusHours(5), null, QuizMode.SYNCHRONIZED); + // set-visible - quizExercise = createQuizOnServer(ZonedDateTime.now().minusHours(5), null, QuizMode.SYNCHRONIZED); + assertThat(quizExercise.isVisibleToStudents()).isTrue(); request.putWithResponseBody("/api/quiz-exercises/" + quizExercise.getId() + "/set-visible", quizExercise, QuizExercise.class, HttpStatus.BAD_REQUEST); + // start-now - quizExercise = createQuizOnServer(ZonedDateTime.now().minusDays(1), null, QuizMode.SYNCHRONIZED); assertThat(quizExercise.isQuizStarted()).isTrue(); request.putWithResponseBody("/api/quiz-exercises/" + quizExercise.getId() + "/start-now", quizExercise, QuizExercise.class, HttpStatus.BAD_REQUEST); + // open-for-practice - quizExercise = createQuizOnServer(ZonedDateTime.now().minusDays(1), null, QuizMode.SYNCHRONIZED); quizExercise.setIsOpenForPractice(true); - quizExerciseRepository.save(quizExercise); request.putWithResponseBody("/api/quiz-exercises/" + quizExercise.getId() + "/open-for-practice", quizExercise, QuizExercise.class, HttpStatus.BAD_REQUEST); + // misspelled request - quizExercise = createQuizOnServer(ZonedDateTime.now().minusDays(1), null, QuizMode.SYNCHRONIZED); request.putWithResponseBody("/api/quiz-exercises/" + quizExercise.getId() + "/lorem-ipsum", quizExercise, QuizExercise.class, HttpStatus.BAD_REQUEST); } @@ -1763,8 +1257,8 @@ void testRedundantActionsBadRequest() throws Exception { @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void testMultipleChoiceQuizExplanationLength_Valid() throws Exception { int validityThreshold = 500; + QuizExercise quizExercise = createMultipleChoiceQuizExercise(); - QuizExercise quizExercise = createMultipleChoiceQuizExerciseDummy(); MultipleChoiceQuestion question = (MultipleChoiceQuestion) quizExercise.getQuizQuestions().get(0); question.setExplanation("0".repeat(validityThreshold)); @@ -1779,8 +1273,8 @@ void testMultipleChoiceQuizExplanationLength_Valid() throws Exception { @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void testMultipleChoiceQuizExplanationLength_Invalid() throws Exception { int validityThreshold = 500; + QuizExercise quizExercise = createMultipleChoiceQuizExercise(); - QuizExercise quizExercise = createMultipleChoiceQuizExerciseDummy(); MultipleChoiceQuestion question = (MultipleChoiceQuestion) quizExercise.getQuizQuestions().get(0); question.setExplanation("0".repeat(validityThreshold + 1)); @@ -1794,8 +1288,8 @@ void testMultipleChoiceQuizExplanationLength_Invalid() throws Exception { @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void testMultipleChoiceQuizOptionExplanationLength_Valid() throws Exception { int validityThreshold = 500; + QuizExercise quizExercise = createMultipleChoiceQuizExercise(); - QuizExercise quizExercise = createMultipleChoiceQuizExerciseDummy(); MultipleChoiceQuestion question = (MultipleChoiceQuestion) quizExercise.getQuizQuestions().get(0); question.getAnswerOptions().get(0).setExplanation("0".repeat(validityThreshold)); @@ -1808,10 +1302,10 @@ void testMultipleChoiceQuizOptionExplanationLength_Valid() throws Exception { */ @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") - void testMultipleChoiceQuizOptionExplanationLength_Inalid() throws Exception { + void testMultipleChoiceQuizOptionExplanationLength_Invalid() throws Exception { int validityThreshold = 500; + QuizExercise quizExercise = createMultipleChoiceQuizExercise(); - QuizExercise quizExercise = createMultipleChoiceQuizExerciseDummy(); MultipleChoiceQuestion question = (MultipleChoiceQuestion) quizExercise.getQuizQuestions().get(0); question.getAnswerOptions().get(0).setExplanation("0".repeat(validityThreshold + 1)); @@ -1821,47 +1315,239 @@ void testMultipleChoiceQuizOptionExplanationLength_Inalid() throws Exception { @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void testReset() throws Exception { - Course course = database.addCourseWithOneQuizExercise(); - QuizExercise quizExercise = (QuizExercise) course.getExercises().stream().findFirst().get(); + QuizExercise quizExercise = database.createAndSaveQuiz(ZonedDateTime.now().plusHours(2), null, QuizMode.SYNCHRONIZED); + for (QuizQuestion quizQuestion : quizExercise.getQuizQuestions()) { quizQuestion.setInvalid(true); } - quizExerciseRepository.save(quizExercise); - request.delete("/api/exercises/" + quizExercise.getId() + "/reset", HttpStatus.OK); quizExercise = quizExerciseRepository.findOneWithQuestionsAndStatistics(quizExercise.getId()); - assertThat(studentParticipationRepository.findByExerciseId(quizExercise.getId())).as("Student participations have been deleted").isEmpty(); + assertThat(quizExercise).isNotNull(); assertThat(quizExercise.isIsOpenForPractice()).as("Quiz Question is open for practice has been set to false").isFalse(); - assertThat(ZonedDateTime.now().isAfter(quizExercise.getReleaseDate())).as("Quiz Question is released").isTrue(); + assertThat(quizExercise.getReleaseDate()).as("Quiz Question is released").isBeforeOrEqualTo(ZonedDateTime.now()); assertThat(quizExercise.getDueDate()).as("Quiz Question due date has been set to null").isNull(); assertThat(quizExercise.getQuizBatches()).as("Quiz Question batches has been set to empty").isEmpty(); + for (QuizQuestion quizQuestion : quizExercise.getQuizQuestions()) { assertThat(quizQuestion.isInvalid()).as("Quiz Question invalid flag has been set to false").isFalse(); } } - @Test - @WithMockUser(username = TEST_PREFIX + "student1", roles = "USER") - void testFilterForCourseDashboard_QuizSubmissionButNoParticipation() { - Course course = database.addCourseWithOneQuizExercise(); - QuizExercise quizExercise = (QuizExercise) course.getExercises().stream().findFirst().get(); + private QuizExercise createQuizOnServer(ZonedDateTime releaseDate, ZonedDateTime dueDate, QuizMode quizMode) throws Exception { + QuizExercise quizExercise = database.createQuiz(releaseDate, dueDate, quizMode); + quizExercise.setDuration(3600); - QuizSubmission quizSubmission = database.generateSubmissionForThreeQuestions(quizExercise, 1, true, ZonedDateTime.now()); - database.addSubmission(quizExercise, quizSubmission, TEST_PREFIX + "student1"); + QuizExercise quizExerciseServer = request.postWithResponseBody("/api/quiz-exercises", quizExercise, QuizExercise.class, HttpStatus.CREATED); + QuizExercise quizExerciseDatabase = quizExerciseRepository.findOneWithQuestionsAndStatistics(quizExerciseServer.getId()); + assertThat(quizExerciseServer).isNotNull(); + assertThat(quizExerciseDatabase).isNotNull(); - quizScheduleService.updateSubmission(quizExercise.getId(), TEST_PREFIX + "student1", quizSubmission); + checkQuizExercises(quizExercise, quizExerciseServer); + checkQuizExercises(quizExercise, quizExerciseDatabase); - exerciseService.filterForCourseDashboard(quizExercise, List.of(), TEST_PREFIX + "student1", true); + for (int i = 0; i < quizExercise.getQuizQuestions().size(); i++) { + QuizQuestion question = quizExercise.getQuizQuestions().get(i); + QuizQuestion questionServer = quizExerciseServer.getQuizQuestions().get(i); + QuizQuestion questionDatabase = quizExerciseDatabase.getQuizQuestions().get(i); - assertThat(quizExercise.getStudentParticipations()).hasSize(1); - assertThat(quizExercise.getStudentParticipations().stream().findFirst().get().getInitializationState()).isEqualTo(InitializationState.INITIALIZED); + assertThat(question.getId()).as("Question IDs are correct").isNull(); + assertThat(questionDatabase.getId()).as("Question IDs are correct").isEqualTo(questionServer.getId()); + + assertThat(question.getExercise().getId()).as("Exercise IDs are correct").isNull(); + assertThat(questionDatabase.getExercise().getId()).as("Exercise IDs are correct").isEqualTo(quizExerciseDatabase.getId()); + + assertThat(question.getTitle()).as("Question titles are correct").isEqualTo(questionDatabase.getTitle()); + assertThat(question.getTitle()).as("Question titles are correct").isEqualTo(questionServer.getTitle()); + + assertThat(question.getPoints()).as("Question scores are correct").isEqualTo(questionDatabase.getPoints()); + assertThat(question.getPoints()).as("Question scores are correct").isEqualTo(questionServer.getPoints()); + } + return quizExerciseDatabase; } - private QuizExercise createMultipleChoiceQuizExerciseDummy() { - Course course = database.createCourse(); - MultipleChoiceQuestion question = (MultipleChoiceQuestion) new MultipleChoiceQuestion().title("MC").score(4).text("Q1"); + private QuizExercise createQuizOnServerForExam() throws Exception { + ExerciseGroup exerciseGroup = database.createAndSaveActiveExerciseGroup(true); + QuizExercise quizExercise = database.createQuizForExam(exerciseGroup); + quizExercise.setDuration(3600); + + QuizExercise quizExerciseServer = request.postWithResponseBody("/api/quiz-exercises", quizExercise, QuizExercise.class, HttpStatus.CREATED); + QuizExercise quizExerciseDatabase = quizExerciseRepository.findOneWithQuestionsAndStatistics(quizExerciseServer.getId()); + assertThat(quizExerciseServer).isNotNull(); + assertThat(quizExerciseDatabase).isNotNull(); + + checkQuizExercises(quizExercise, quizExerciseServer); + checkQuizExercises(quizExercise, quizExerciseDatabase); + + for (int i = 0; i < quizExercise.getQuizQuestions().size(); i++) { + QuizQuestion question = quizExercise.getQuizQuestions().get(i); + QuizQuestion questionServer = quizExerciseServer.getQuizQuestions().get(i); + QuizQuestion questionDatabase = quizExerciseDatabase.getQuizQuestions().get(i); + + assertThat(question.getId()).as("Question IDs are correct").isNull(); + assertThat(questionDatabase.getId()).as("Question IDs are correct").isEqualTo(questionServer.getId()); + + assertThat(question.getExercise().getId()).as("Exercise IDs are correct").isNull(); + assertThat(questionDatabase.getExercise().getId()).as("Exercise IDs are correct").isEqualTo(quizExerciseDatabase.getId()); + + assertThat(question.getTitle()).as("Question titles are correct").isEqualTo(questionDatabase.getTitle()); + assertThat(question.getTitle()).as("Question titles are correct").isEqualTo(questionServer.getTitle()); + + assertThat(question.getPoints()).as("Question scores are correct").isEqualTo(questionDatabase.getPoints()); + assertThat(question.getPoints()).as("Question scores are correct").isEqualTo(questionServer.getPoints()); + } + + return quizExerciseDatabase; + } + + private void createdQuizAssert(QuizExercise quizExercise) { + // General assertions + assertThat(quizExercise.getQuizQuestions()).as("Quiz questions were saved").hasSize(3); + assertThat(quizExercise.getDuration()).as("Quiz duration was correctly set").isEqualTo(3600); + assertThat(quizExercise.getDifficulty()).as("Quiz difficulty was correctly set").isEqualTo(DifficultyLevel.MEDIUM); + + // Quiz type specific assertions + for (QuizQuestion question : quizExercise.getQuizQuestions()) { + if (question instanceof MultipleChoiceQuestion multipleChoiceQuestion) { + assertThat(multipleChoiceQuestion.getAnswerOptions()).as("Multiple choice question answer options were saved").hasSize(2); + assertThat(multipleChoiceQuestion.getTitle()).as("Multiple choice question title is correct").isEqualTo("MC"); + assertThat(multipleChoiceQuestion.getText()).as("Multiple choice question text is correct").isEqualTo("Q1"); + assertThat(multipleChoiceQuestion.getPoints()).as("Multiple choice question score is correct").isEqualTo(4); + + List answerOptions = multipleChoiceQuestion.getAnswerOptions(); + assertThat(answerOptions.get(0).getText()).as("Text for answer option is correct").isEqualTo("A"); + assertThat(answerOptions.get(0).getHint()).as("Hint for answer option is correct").isEqualTo("H1"); + assertThat(answerOptions.get(0).getExplanation()).as("Explanation for answer option is correct").isEqualTo("E1"); + assertThat(answerOptions.get(0).isIsCorrect()).as("Is correct for answer option is correct").isTrue(); + assertThat(answerOptions.get(1).getText()).as("Text for answer option is correct").isEqualTo("B"); + assertThat(answerOptions.get(1).getHint()).as("Hint for answer option is correct").isEqualTo("H2"); + assertThat(answerOptions.get(1).getExplanation()).as("Explanation for answer option is correct").isEqualTo("E2"); + assertThat(answerOptions.get(1).isIsCorrect()).as("Is correct for answer option is correct").isFalse(); + } + else if (question instanceof DragAndDropQuestion dragAndDropQuestion) { + assertThat(dragAndDropQuestion.getDropLocations()).as("Drag and drop question drop locations were saved").hasSize(3); + assertThat(dragAndDropQuestion.getDragItems()).as("Drag and drop question drag items were saved").hasSize(3); + assertThat(dragAndDropQuestion.getTitle()).as("Drag and drop question title is correct").isEqualTo("DnD"); + assertThat(dragAndDropQuestion.getText()).as("Drag and drop question text is correct").isEqualTo("Q2"); + assertThat(dragAndDropQuestion.getPoints()).as("Drag and drop question score is correct").isEqualTo(3); + + List dropLocations = dragAndDropQuestion.getDropLocations(); + assertThat(dropLocations.get(0).getPosX()).as("Pos X for drop location is correct").isEqualTo(10); + assertThat(dropLocations.get(0).getPosY()).as("Pos Y for drop location is correct").isEqualTo(10); + assertThat(dropLocations.get(0).getWidth()).as("Width for drop location is correct").isEqualTo(10); + assertThat(dropLocations.get(0).getHeight()).as("Height for drop location is correct").isEqualTo(10); + assertThat(dropLocations.get(0).getQuestion()).isNotNull(); + assertThat(dropLocations.get(1).getPosX()).as("Pos X for drop location is correct").isEqualTo(20); + assertThat(dropLocations.get(1).getPosY()).as("Pos Y for drop location is correct").isEqualTo(20); + assertThat(dropLocations.get(1).getWidth()).as("Width for drop location is correct").isEqualTo(10); + assertThat(dropLocations.get(1).getHeight()).as("Height for drop location is correct").isEqualTo(10); + assertThat(dropLocations.get(1).getQuestion()).isNotNull(); + + List dragItems = dragAndDropQuestion.getDragItems(); + assertThat(dragItems.get(0).getText()).as("Text for drag item is correct").isEqualTo("D1"); + assertThat(dragItems.get(1).getText()).as("Text for drag item is correct").isEqualTo("D2"); + } + else if (question instanceof ShortAnswerQuestion shortAnswerQuestion) { + assertThat(shortAnswerQuestion.getSpots()).as("Short answer question spots were saved").hasSize(2); + assertThat(shortAnswerQuestion.getSolutions()).as("Short answer question solutions were saved").hasSize(2); + assertThat(shortAnswerQuestion.getTitle()).as("Short answer question title is correct").isEqualTo("SA"); + assertThat(shortAnswerQuestion.getText()).as("Short answer question text is correct").isEqualTo("This is a long answer text"); + assertThat(shortAnswerQuestion.getPoints()).as("Short answer question score is correct").isEqualTo(2); + + List spots = shortAnswerQuestion.getSpots(); + assertThat(spots.get(0).getSpotNr()).as("Spot nr for spot is correct").isZero(); + assertThat(spots.get(0).getWidth()).as("Width for spot is correct").isEqualTo(1); + assertThat(spots.get(1).getSpotNr()).as("Spot nr for spot is correct").isEqualTo(2); + assertThat(spots.get(1).getWidth()).as("Width for spot is correct").isEqualTo(2); + + List solutions = shortAnswerQuestion.getSolutions(); + assertThat(solutions.get(0).getText()).as("Text for solution is correct").isEqualTo("is"); + assertThat(solutions.get(1).getText()).as("Text for solution is correct").isEqualTo("long"); + } + } + } + + private void updateQuizAndAssert(QuizExercise quizExercise) throws Exception { + updateMultipleChoice(quizExercise); + + quizExercise = request.putWithResponseBody("/api/quiz-exercises", quizExercise, QuizExercise.class, HttpStatus.OK); + + // Quiz type specific assertions + for (QuizQuestion question : quizExercise.getQuizQuestions()) { + if (question instanceof MultipleChoiceQuestion multipleChoiceQuestion) { + assertThat(multipleChoiceQuestion.getAnswerOptions()).as("Multiple choice question answer options were saved").hasSize(3); + assertThat(multipleChoiceQuestion.getTitle()).as("Multiple choice question title is correct").isEqualTo("MC"); + assertThat(multipleChoiceQuestion.getText()).as("Multiple choice question text is correct").isEqualTo("Q1"); + assertThat(multipleChoiceQuestion.getPoints()).as("Multiple choice question score is correct").isEqualTo(4); + + List answerOptions = multipleChoiceQuestion.getAnswerOptions(); + assertThat(answerOptions.get(0).getText()).as("Text for answer option is correct").isEqualTo("B"); + assertThat(answerOptions.get(0).getHint()).as("Hint for answer option is correct").isEqualTo("H2"); + assertThat(answerOptions.get(0).getExplanation()).as("Explanation for answer option is correct").isEqualTo("E2"); + assertThat(answerOptions.get(0).isIsCorrect()).as("Is correct for answer option is correct").isFalse(); + assertThat(answerOptions.get(1).getText()).as("Text for answer option is correct").isEqualTo("C"); + assertThat(answerOptions.get(1).getHint()).as("Hint for answer option is correct").isEqualTo("H3"); + assertThat(answerOptions.get(1).getExplanation()).as("Explanation for answer option is correct").isEqualTo("E3"); + assertThat(answerOptions.get(1).isIsCorrect()).as("Is correct for answer option is correct").isTrue(); + assertThat(answerOptions.get(2).getText()).as("Text for answer option is correct").isEqualTo("D"); + assertThat(answerOptions.get(2).getHint()).as("Hint for answer option is correct").isEqualTo("H4"); + assertThat(answerOptions.get(2).getExplanation()).as("Explanation for answer option is correct").isEqualTo("E4"); + assertThat(answerOptions.get(2).isIsCorrect()).as("Is correct for answer option is correct").isTrue(); + } + else if (question instanceof DragAndDropQuestion dragAndDropQuestion) { + assertThat(dragAndDropQuestion.getDropLocations()).as("Drag and drop question drop locations were saved").hasSize(2); + assertThat(dragAndDropQuestion.getDragItems()).as("Drag and drop question drag items were saved").hasSize(2); + assertThat(dragAndDropQuestion.getTitle()).as("Drag and drop question title is correct").isEqualTo("DnD"); + assertThat(dragAndDropQuestion.getText()).as("Drag and drop question text is correct").isEqualTo("Q2"); + assertThat(dragAndDropQuestion.getPoints()).as("Drag and drop question score is correct").isEqualTo(3); + + List dropLocations = dragAndDropQuestion.getDropLocations(); + assertThat(dropLocations.get(0).getPosX()).as("Pos X for drop location is correct").isEqualTo(20); + assertThat(dropLocations.get(0).getPosY()).as("Pos Y for drop location is correct").isEqualTo(20); + assertThat(dropLocations.get(0).getWidth()).as("Width for drop location is correct").isEqualTo(10); + assertThat(dropLocations.get(0).getHeight()).as("Height for drop location is correct").isEqualTo(10); + + List dragItems = dragAndDropQuestion.getDragItems(); + assertThat(dragItems.get(0).getText()).as("Text for drag item is correct").isEqualTo("D2"); + } + else if (question instanceof ShortAnswerQuestion shortAnswerQuestion) { + assertThat(shortAnswerQuestion.getSpots()).as("Short answer question spots were saved").hasSize(1); + assertThat(shortAnswerQuestion.getSolutions()).as("Short answer question solutions were saved").hasSize(1); + assertThat(shortAnswerQuestion.getTitle()).as("Short answer question title is correct").isEqualTo("SA"); + assertThat(shortAnswerQuestion.getText()).as("Short answer question text is correct").isEqualTo("This is a long answer text"); + assertThat(shortAnswerQuestion.getPoints()).as("Short answer question score is correct").isEqualTo(2); + + List spots = shortAnswerQuestion.getSpots(); + assertThat(spots.get(0).getSpotNr()).as("Spot nr for spot is correct").isEqualTo(2); + assertThat(spots.get(0).getWidth()).as("Width for spot is correct").isEqualTo(2); + + List solutions = shortAnswerQuestion.getSolutions(); + assertThat(solutions.get(0).getText()).as("Text for solution is correct").isEqualTo("long"); + } + } + } + + private void updateMultipleChoice(QuizExercise quizExercise) { + MultipleChoiceQuestion mc = (MultipleChoiceQuestion) quizExercise.getQuizQuestions().get(0); + mc.getAnswerOptions().remove(0); + mc.getAnswerOptions().add(new AnswerOption().text("C").hint("H3").explanation("E3").isCorrect(true)); + mc.getAnswerOptions().add(new AnswerOption().text("D").hint("H4").explanation("E4").isCorrect(true)); + + DragAndDropQuestion dnd = (DragAndDropQuestion) quizExercise.getQuizQuestions().get(1); + dnd.getDropLocations().remove(0); + dnd.getCorrectMappings().remove(0); + dnd.getDragItems().remove(0); + + ShortAnswerQuestion sa = (ShortAnswerQuestion) quizExercise.getQuizQuestions().get(2); + sa.getSpots().remove(0); + sa.getSolutions().remove(0); + sa.getCorrectMappings().remove(0); + } + + private QuizExercise createMultipleChoiceQuizExercise() { + Course course = database.createAndSaveCourse(null, ZonedDateTime.now().minusDays(1), null, Set.of()); QuizExercise quizExercise = ModelFactory.generateQuizExercise(ZonedDateTime.now().plusHours(5), null, QuizMode.SYNCHRONIZED, course); + MultipleChoiceQuestion question = (MultipleChoiceQuestion) new MultipleChoiceQuestion().title("MC").score(4).text("Q1"); question.setScoringType(ScoringType.ALL_OR_NOTHING); question.getAnswerOptions().add(new AnswerOption().text("A").hint("H1").explanation("E1").isCorrect(true)); @@ -1880,7 +1566,7 @@ private QuizExercise createMultipleChoiceQuizExerciseDummy() { * Check that the general information of two exercises is equal. */ private void checkQuizExercises(QuizExercise quizExercise, QuizExercise quizExercise2) { - assertThat(quizExercise.getQuizQuestions()).as("Same amount of questions saved").hasSize(quizExercise2.getQuizQuestions().size()); + assertThat(quizExercise.getQuizQuestions()).as("Same amount of questions saved").hasSameSizeAs(quizExercise2.getQuizQuestions()); assertThat(quizExercise.getTitle()).as("Title saved correctly").isEqualTo(quizExercise2.getTitle()); assertThat(quizExercise.getAllowedNumberOfAttempts()).as("Number of attempts saved correctly").isEqualTo(quizExercise2.getAllowedNumberOfAttempts()); assertThat(quizExercise.getMaxPoints()).as("Max score saved correctly").isEqualTo(quizExercise2.getMaxPoints()); @@ -1888,6 +1574,13 @@ private void checkQuizExercises(QuizExercise quizExercise, QuizExercise quizExer assertThat(quizExercise.getType()).as("Type saved correctly").isEqualTo(quizExercise2.getType()); } + private PointCounter pc(int rated, int unrated) { + PointCounter pointCounter = new PointCounter(); + pointCounter.setRatedCounter(rated); + pointCounter.setUnRatedCounter(unrated); + return pointCounter; + } + /** * Check that the general statistics of two exercises are equal. */ @@ -1931,6 +1624,22 @@ else if (statistic instanceof ShortAnswerQuestionStatistic saStatistic) { } } + private void assertQuizPointStatisticsPointCounters(QuizExercise quizExercise, Map expectedPointCounters) { + for (PointCounter pointCounter : quizExercise.getQuizPointStatistic().getPointCounters()) { + PointCounter expectedPointCounter = expectedPointCounters.get(pointCounter.getPoints()); + if (expectedPointCounter != null) { + assertThat(pointCounter.getRatedCounter()).as(pointCounter.getPoints() + " should have a rated counter of " + expectedPointCounter.getRatedCounter()) + .isEqualTo(expectedPointCounter.getRatedCounter()); + assertThat(pointCounter.getUnRatedCounter()).as(pointCounter.getPoints() + " should have an unrated counter of " + expectedPointCounter.getUnRatedCounter()) + .isEqualTo(expectedPointCounter.getUnRatedCounter()); + } + else { + assertThat(pointCounter.getRatedCounter()).as(pointCounter.getPoints() + " should have a rated counter of 0").isZero(); + assertThat(pointCounter.getUnRatedCounter()).as(pointCounter.getPoints() + " should have a rated counter of 0").isZero(); + } + } + } + /** * Check if a QuizExercise contains the correct information for students. * @@ -1940,6 +1649,7 @@ private void checkQuizExerciseForStudent(QuizExercise quizExercise) { assertThat(quizExercise.getQuizPointStatistic()).isNull(); assertThat(quizExercise.getGradingInstructions()).isNull(); assertThat(quizExercise.getGradingCriteria()).isEmpty(); + if (!quizExercise.isQuizEnded()) { for (QuizQuestion question : quizExercise.getQuizQuestions()) { assertThat(question.getExplanation()).isNull(); @@ -1950,4 +1660,45 @@ else if (!quizExercise.isQuizStarted()) { assertThat(quizExercise.getQuizQuestions()).isEmpty(); } } + + private static List testPerformJoin_args() { + var now = ZonedDateTime.now(); + var longPast = now.minusHours(4); + var past = now.minusMinutes(30); + var future = now.plusMinutes(30); + var longFuture = now.plusHours(4); + + var batchLongPast = new QuizBatch(); + batchLongPast.setStartTime(longPast); + batchLongPast.setPassword("12345678"); + var batchPast = new QuizBatch(); + batchPast.setStartTime(past); + batchPast.setPassword("12345678"); + var batchFuture = new QuizBatch(); + batchFuture.setStartTime(future); + batchFuture.setPassword("12345678"); + + return List.of(Arguments.of(QuizMode.SYNCHRONIZED, future, null, null, null, HttpStatus.FORBIDDEN), // start in future + Arguments.of(QuizMode.SYNCHRONIZED, past, null, null, null, HttpStatus.NOT_FOUND), // synchronized + Arguments.of(QuizMode.SYNCHRONIZED, past, null, null, "12345678", HttpStatus.NOT_FOUND), // synchronized + Arguments.of(QuizMode.SYNCHRONIZED, longPast, past, null, null, HttpStatus.FORBIDDEN), // due date passed + Arguments.of(QuizMode.INDIVIDUAL, future, null, null, null, HttpStatus.FORBIDDEN), // start in future + Arguments.of(QuizMode.INDIVIDUAL, longPast, null, null, null, HttpStatus.OK), Arguments.of(QuizMode.INDIVIDUAL, longPast, longFuture, null, null, HttpStatus.OK), + Arguments.of(QuizMode.INDIVIDUAL, longPast, future, null, null, HttpStatus.OK), // NOTE: reduced working time because of due date + Arguments.of(QuizMode.INDIVIDUAL, longPast, past, null, null, HttpStatus.FORBIDDEN), // after due date + Arguments.of(QuizMode.BATCHED, future, null, null, null, HttpStatus.FORBIDDEN), // start in future + Arguments.of(QuizMode.BATCHED, longPast, null, null, null, HttpStatus.NOT_FOUND), // no pw + Arguments.of(QuizMode.BATCHED, longPast, longFuture, null, null, HttpStatus.NOT_FOUND), // no pw + Arguments.of(QuizMode.BATCHED, longPast, future, null, null, HttpStatus.NOT_FOUND), // no pw + Arguments.of(QuizMode.BATCHED, longPast, past, null, null, HttpStatus.FORBIDDEN), // after due date + Arguments.of(QuizMode.BATCHED, longPast, null, batchLongPast, null, HttpStatus.NOT_FOUND), // no pw + Arguments.of(QuizMode.BATCHED, longPast, null, batchPast, null, HttpStatus.NOT_FOUND), // no pw + Arguments.of(QuizMode.BATCHED, longPast, null, batchFuture, null, HttpStatus.NOT_FOUND), // no pw + Arguments.of(QuizMode.BATCHED, longPast, null, batchLongPast, "87654321", HttpStatus.NOT_FOUND), // wrong pw + Arguments.of(QuizMode.BATCHED, longPast, null, batchPast, "87654321", HttpStatus.NOT_FOUND), // wrong pw + Arguments.of(QuizMode.BATCHED, longPast, null, batchFuture, "87654321", HttpStatus.NOT_FOUND), // wrong pw + Arguments.of(QuizMode.BATCHED, longPast, null, batchLongPast, "12345678", HttpStatus.NOT_FOUND), // batch done + Arguments.of(QuizMode.BATCHED, longPast, null, batchPast, "12345678", HttpStatus.OK), // NOTE: reduced working time because batch had already started + Arguments.of(QuizMode.BATCHED, longPast, null, batchFuture, "12345678", HttpStatus.OK)); + } } diff --git a/src/test/java/de/tum/in/www1/artemis/util/DatabaseUtilService.java b/src/test/java/de/tum/in/www1/artemis/util/DatabaseUtilService.java index 8b90bbf45316..ad67ce0e4bc2 100644 --- a/src/test/java/de/tum/in/www1/artemis/util/DatabaseUtilService.java +++ b/src/test/java/de/tum/in/www1/artemis/util/DatabaseUtilService.java @@ -330,6 +330,9 @@ public class DatabaseUtilService { @Autowired private ReactionRepository reactionRepository; + @Autowired + private QuizBatchRepository quizBatchRepository; + @Value("${info.guided-tour.course-group-students:#{null}}") private Optional tutorialGroupStudents; @@ -4018,6 +4021,34 @@ public QuizExercise createQuiz(Course course, ZonedDateTime releaseDate, ZonedDa return quizExercise; } + /** + * creates and saves an exam exercise group in a course that is currently active. + * + * @param mandatory if the exerciseGroup is mandatory + * @return exercise group created + */ + public ExerciseGroup createAndSaveActiveExerciseGroup(boolean mandatory) { + Course course = createAndSaveCourse(1L, pastTimestamp, futureFutureTimestamp, Set.of()); + Exam exam = ModelFactory.generateExam(course); + ExerciseGroup exerciseGroup = ModelFactory.generateExerciseGroup(mandatory, exam); + examRepository.save(exam); + + return exerciseGroup; + } + + /** + * important quiz fields are emptied, so it can be imported, + * + * @param quizExercise to be emptied + */ + public void emptyOutQuizExercise(QuizExercise quizExercise) { + quizExercise.setReleaseDate(null); + quizExercise.setCourse(null); + quizExercise.setDueDate(null); + quizExercise.setAssessmentDueDate(null); + quizExercise.setQuizBatches(new HashSet<>()); + } + /** * Creates a new quiz that gets saved in the QuizExercise repository. * @@ -4027,15 +4058,67 @@ public QuizExercise createQuiz(Course course, ZonedDateTime releaseDate, ZonedDa * @return quiz that was created */ public QuizExercise createAndSaveQuiz(ZonedDateTime releaseDate, ZonedDateTime dueDate, QuizMode quizMode) { + QuizExercise quizExercise = createQuiz(releaseDate, dueDate, quizMode); + quizExerciseRepository.save(quizExercise); + + return quizExercise; + } + + /** + * Creates a new quiz + * + * @param releaseDate release date of the quiz, is also used to set the start date of the course + * @param dueDate due date of the quiz, is also used to set the end date of the course + * @param quizMode SYNCHRONIZED, BATCHED or INDIVIDUAL + * @return quiz that was created + */ + public QuizExercise createQuiz(ZonedDateTime releaseDate, ZonedDateTime dueDate, QuizMode quizMode) { Course course = createAndSaveCourse(null, releaseDate == null ? null : releaseDate.minusDays(1), dueDate == null ? null : dueDate.plusDays(1), Set.of()); QuizExercise quizExercise = ModelFactory.generateQuizExercise(releaseDate, dueDate, quizMode, course); initializeQuizExercise(quizExercise); + + return quizExercise; + } + + /** + * Creates a team quiz exercise with a team and saves it into the repository. + * + * @param releaseDate release date of the quiz + * @param dueDate due date of the quiz + * @param quizMode SYNCHRONIZED, BATCHED or INDIVIDUAL + * @param minTeamSize minimum number of members the team is allowed to have + * @param maxTeamSize maximum number of members the team is allowed to have + * @return exercise created + */ + public QuizExercise createAndSaveTeamQuiz(ZonedDateTime releaseDate, ZonedDateTime dueDate, QuizMode quizMode, int minTeamSize, int maxTeamSize) { + QuizExercise quizExercise = createQuiz(releaseDate, dueDate, quizMode); + setupTeamQuizExercise(quizExercise, minTeamSize, maxTeamSize); quizExerciseRepository.save(quizExercise); + Team team = new Team(); + team.setShortName("team"); + teamRepo.save(quizExercise, team); + return quizExercise; } + /** + * sets up a team quiz exercise. + * + * @param quiz quiz exercise that should be a team exercise. + * @param minTeamSize minimum number of members the team is allowed to have + * @param maxTeamSize maximum number of members the team is allowed to have + */ + public void setupTeamQuizExercise(QuizExercise quiz, int minTeamSize, int maxTeamSize) { + var teamAssignmentConfig = new TeamAssignmentConfig(); + teamAssignmentConfig.setExercise(quiz); + teamAssignmentConfig.setMinTeamSize(minTeamSize); + teamAssignmentConfig.setMaxTeamSize(maxTeamSize); + quiz.setMode(ExerciseMode.TEAM); + quiz.setTeamAssignmentConfig(teamAssignmentConfig); + } + /** * Creates a new course that gets saved in the Course repository. * @@ -4069,13 +4152,14 @@ public QuizExercise createAndSaveExamQuiz(ZonedDateTime startDate, ZonedDateTime QuizExercise quizExercise = ModelFactory.generateQuizExerciseForExam(exerciseGroup); initializeQuizExercise(quizExercise); + quizExerciseRepository.save(quizExercise); return quizExercise; } /** - * Removes a user from all courses they are currently in + * Removes a user from all courses they are currently in. * * @param login login to find user with */ @@ -4085,11 +4169,26 @@ public void removeUserFromAllCourses(String login) { userRepo.save(user); } - @NotNull - public QuizExercise createQuizWithQuizBatchedExercises(Course course, ZonedDateTime releaseDate, ZonedDateTime dueDate, QuizMode quizMode) { - QuizExercise quizExerciseWithQuizBatches = ModelFactory.generateQuizExerciseWithQuizBatches(releaseDate, dueDate, quizMode, course); - initializeQuizExercise(quizExerciseWithQuizBatches); - return quizExerciseWithQuizBatches; + /** + * renames the quiz with the passed title, the quiz gets saved in the repository. + * + * @param quizExercise quiz to be renamed + * @param newTitle new name of the quiz + */ + public void renameAndSaveQuiz(QuizExercise quizExercise, String newTitle) { + quizExercise.setTitle(newTitle); + quizExerciseRepository.save(quizExercise); + } + + /** + * sets the quiz exercise of quiz batch and saves the batch into the repository + * + * @param batch quiz batch that should get saved + * @param quizExercise quiz exercise to be added to the batch + */ + public void setQuizBatchExerciseAndSave(QuizBatch batch, QuizExercise quizExercise) { + batch.setQuizExercise(quizExercise); + quizBatchRepository.save(batch); } @NotNull @@ -4100,6 +4199,11 @@ public QuizExercise createQuizForExam(ExerciseGroup exerciseGroup) { return quizExercise; } + /** + * initializes a quiz with all different types of questions + * + * @param quizExercise to be initialized + */ private void initializeQuizExercise(QuizExercise quizExercise) { quizExercise.addQuestions(createMultipleChoiceQuestion()); quizExercise.addQuestions(createDragAndDropQuestion()); @@ -4212,6 +4316,7 @@ public DragAndDropQuestion createDragAndDropQuestion() { log.debug("DnD: {}", dnd); log.debug("DnD.hashCode: {}", dnd.hashCode()); dnd.copyQuestionId(); + return dnd; } @@ -4226,9 +4331,7 @@ public MultipleChoiceQuestion createMultipleChoiceQuestion() { mc.getAnswerOptions().add(new AnswerOption().text("A").hint("H1").explanation("E1").isCorrect(true)); mc.getAnswerOptions().add(new AnswerOption().text("B").hint("H2").explanation("E2").isCorrect(false)); mc.setExplanation("Explanation"); - // invoke some util methods - log.debug("MC: {}", mc); - log.debug("MC.hashCode: {}", mc.hashCode()); + mc.copyQuestionId(); return mc; } diff --git a/src/test/java/de/tum/in/www1/artemis/util/ModelFactory.java b/src/test/java/de/tum/in/www1/artemis/util/ModelFactory.java index 0963a8f7d4fc..71624eea2eb5 100644 --- a/src/test/java/de/tum/in/www1/artemis/util/ModelFactory.java +++ b/src/test/java/de/tum/in/www1/artemis/util/ModelFactory.java @@ -106,7 +106,9 @@ public static QuizBatch generateQuizBatch(QuizExercise quizExercise, ZonedDateTi } public static QuizExercise generateQuizExercise(ZonedDateTime releaseDate, ZonedDateTime dueDate, QuizMode quizMode, Course course) { - var quizExercise = (QuizExercise) populateExercise(new QuizExercise(), releaseDate, dueDate, null, course); + QuizExercise quizExercise = (QuizExercise) populateExercise(new QuizExercise(), releaseDate, dueDate, null, course); + quizExercise.setTitle("my cool quiz title"); + quizExercise.setProblemStatement(null); quizExercise.setGradingInstructions(null); quizExercise.setPresentationScoreEnabled(false); @@ -121,14 +123,9 @@ public static QuizExercise generateQuizExercise(ZonedDateTime releaseDate, Zoned return quizExercise; } - public static QuizExercise generateQuizExerciseWithQuizBatches(ZonedDateTime releaseDate, ZonedDateTime dueDate, QuizMode quizMode, Course course) { - var quizExercise = generateQuizExercise(releaseDate, dueDate, quizMode, course); - quizExercise.setQuizBatches(Set.of(generateQuizBatch(quizExercise, releaseDate))); - return quizExercise; - } - public static QuizExercise generateQuizExerciseForExam(ExerciseGroup exerciseGroup) { var quizExercise = (QuizExercise) populateExerciseForExam(new QuizExercise(), exerciseGroup); + quizExercise.setProblemStatement(null); quizExercise.setGradingInstructions(null); quizExercise.setPresentationScoreEnabled(false); @@ -148,6 +145,7 @@ else if (question instanceof MultipleChoiceQuestion) { } } quizExercise.setRandomizeQuestionOrder(true); + return quizExercise; }