diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/service/learningpath/LearningPathService.java b/src/main/java/de/tum/cit/aet/artemis/atlas/service/learningpath/LearningPathService.java index 7b92b207d867..d2e5caef82c9 100644 --- a/src/main/java/de/tum/cit/aet/artemis/atlas/service/learningpath/LearningPathService.java +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/service/learningpath/LearningPathService.java @@ -284,6 +284,9 @@ public void startLearningPathForCurrentUser(long learningPathId) { if (!learningPath.getUser().equals(currentUser)) { throw new AccessForbiddenException("You are not allowed to start this learning path."); } + else if (learningPath.isStartedByStudent()) { + throw new BadRequestException("Learning path already started."); + } learningPath.setStartedByStudent(true); learningPathRepository.save(learningPath); } diff --git a/src/test/java/de/tum/cit/aet/artemis/atlas/learningpath/LearningPathIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/atlas/learningpath/LearningPathIntegrationTest.java index 0641c4697e21..65c4a85f1fa1 100644 --- a/src/test/java/de/tum/cit/aet/artemis/atlas/learningpath/LearningPathIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/atlas/learningpath/LearningPathIntegrationTest.java @@ -122,7 +122,9 @@ class LearningPathIntegrationTest extends AbstractSpringIntegrationIndependentTe private static final int NUMBER_OF_STUDENTS = 5; - private static final String STUDENT_OF_COURSE = TEST_PREFIX + "student1"; + private static final String STUDENT1_OF_COURSE = TEST_PREFIX + "student1"; + + private static final String STUDENT2_OF_COURSE = TEST_PREFIX + "student2"; private static final String TUTOR_OF_COURSE = TEST_PREFIX + "tutor1"; @@ -159,7 +161,7 @@ void setupTestScenario() throws Exception { lecture.setCourse(course); lectureRepository.save(lecture); - final var student = userTestRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + final var student = userTestRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); textUnit = createAndLinkTextUnit(student, competencies[0], true); textExercise = createAndLinkTextExercise(competencies[1], false); @@ -217,7 +219,7 @@ private void deleteCompetencyRESTCall(Competency competency) throws Exception { } @Test - @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") + @WithMockUser(username = STUDENT1_OF_COURSE, roles = "USER") void testAll_asStudent() throws Exception { this.testAllPreAuthorize(); request.get("/api/courses/" + course.getId() + "/learning-path-id", HttpStatus.BAD_REQUEST, Long.class); @@ -316,7 +318,7 @@ void testGetLearningPathsOnPageForCourseLearningPathsDisabled() throws Exception @WithMockUser(username = INSTRUCTOR_OF_COURSE, roles = "INSTRUCTOR") void testGetLearningPathsOnPageForCourseEmpty() throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); - final var search = pageableSearchUtilService.configureSearch(STUDENT_OF_COURSE + "SuffixThatAllowsTheResultToBeEmpty"); + final var search = pageableSearchUtilService.configureSearch(STUDENT1_OF_COURSE + "SuffixThatAllowsTheResultToBeEmpty"); final var result = request.getSearchResult("/api/courses/" + course.getId() + "/learning-paths", HttpStatus.OK, LearningPathInformationDTO.class, pageableSearchUtilService.searchMapping(search)); assertThat(result.getResultsOnPage()).isNullOrEmpty(); @@ -326,7 +328,7 @@ void testGetLearningPathsOnPageForCourseEmpty() throws Exception { @WithMockUser(username = INSTRUCTOR_OF_COURSE, roles = "INSTRUCTOR") void testGetLearningPathsOnPageForCourseExactlyStudent() throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); - final var search = pageableSearchUtilService.configureSearch(STUDENT_OF_COURSE); + final var search = pageableSearchUtilService.configureSearch(STUDENT1_OF_COURSE); final var result = request.getSearchResult("/api/courses/" + course.getId() + "/learning-paths", HttpStatus.OK, LearningPathInformationDTO.class, pageableSearchUtilService.searchMapping(search)); assertThat(result.getResultsOnPage()).hasSize(1); @@ -360,7 +362,7 @@ void addCompetencyToLearningPaths(Function competencyProgressService.updateCompetencyProgress(competency.getId(), student)); @@ -489,10 +491,10 @@ void testGetLearningPathCompetencyGraph() throws Exception { @ParameterizedTest(name = "{displayName} [{index}] {argumentsWithNames}") @EnumSource(LearningPathResource.NgxRequestType.class) - @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") + @WithMockUser(username = STUDENT1_OF_COURSE, roles = "USER") void testGetLearningPathNgxForLearningPathsDisabled(LearningPathResource.NgxRequestType type) throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); - final var student = userTestRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + final var student = userTestRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); course.setLearningPathsEnabled(false); courseRepository.save(course); @@ -504,7 +506,7 @@ void testGetLearningPathNgxForLearningPathsDisabled(LearningPathResource.NgxRequ @WithMockUser(username = TEST_PREFIX + "student2", roles = "USER") void testGetLearningPathNgxForOtherStudent(LearningPathResource.NgxRequestType type) throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); - final var student = userTestRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + final var student = userTestRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); request.get("/api/learning-path/" + learningPath.getId() + "/" + type, HttpStatus.FORBIDDEN, NgxLearningPathDTO.class); } @@ -517,10 +519,10 @@ void testGetLearningPathNgxForOtherStudent(LearningPathResource.NgxRequestType t */ @ParameterizedTest(name = "{displayName} [{index}] {argumentsWithNames}") @EnumSource(LearningPathResource.NgxRequestType.class) - @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") + @WithMockUser(username = STUDENT1_OF_COURSE, roles = "USER") void testGetLearningPathNgxAsStudent(LearningPathResource.NgxRequestType type) throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); - final var student = userTestRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + final var student = userTestRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); request.get("/api/learning-path/" + learningPath.getId() + "/" + type, HttpStatus.OK, NgxLearningPathDTO.class); } @@ -568,7 +570,7 @@ void testGetLearningPathNgxAsEditor(LearningPathResource.NgxRequestType type) th @WithMockUser(username = INSTRUCTOR_OF_COURSE, roles = "INSTRUCTOR") void testGetLearningPathNgxAsInstructor(LearningPathResource.NgxRequestType type) throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); - final var student = userTestRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + final var student = userTestRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); request.get("/api/learning-path/" + learningPath.getId() + "/" + type, HttpStatus.OK, NgxLearningPathDTO.class); } @@ -577,21 +579,21 @@ void testGetLearningPathNgxAsInstructor(LearningPathResource.NgxRequestType type class GetLearningPath { @Test - @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") + @WithMockUser(username = STUDENT1_OF_COURSE, roles = "USER") void shouldReturnExisting() throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); - final var student = userTestRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + final var student = userTestRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); final var result = request.get("/api/courses/" + course.getId() + "/learning-path/me", HttpStatus.OK, LearningPathDTO.class); assertThat(result).isEqualTo(LearningPathDTO.of(learningPath)); } @Test - @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") + @WithMockUser(username = STUDENT1_OF_COURSE, roles = "USER") void shouldReturnNotFoundIfNotExists() throws Exception { course.setLearningPathsEnabled(true); course = courseRepository.save(course); - var student = userTestRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + var student = userTestRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); student = userTestRepository.findWithLearningPathsByIdElseThrow(student.getId()); learningPathRepository.deleteAll(student.getLearningPaths()); request.get("/api/courses/" + course.getId() + "/learning-path/me", HttpStatus.NOT_FOUND, LearningPathDTO.class); @@ -602,44 +604,79 @@ void shouldReturnNotFoundIfNotExists() throws Exception { class GenerateLearningPath { @Test - @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") + @WithMockUser(username = STUDENT1_OF_COURSE, roles = "USER") void shouldReturnForbiddenIfNotEnabled() throws Exception { request.postWithResponseBody("/api/courses/" + course.getId() + "/learning-path", null, LearningPathDTO.class, HttpStatus.BAD_REQUEST); } @Test - @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") + @WithMockUser(username = STUDENT1_OF_COURSE, roles = "USER") void shouldReturnBadRequestIfAlreadyExists() throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); request.postWithResponseBody("/api/courses/" + course.getId() + "/learning-path", null, LearningPathDTO.class, HttpStatus.BAD_REQUEST); } @Test - @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") + @WithMockUser(username = STUDENT1_OF_COURSE, roles = "USER") void shouldGenerateLearningPath() throws Exception { course.setLearningPathsEnabled(true); course = courseRepository.save(course); final var response = request.postWithResponseBody("/api/courses/" + course.getId() + "/learning-path", null, LearningPathDTO.class, HttpStatus.CREATED); assertThat(response).isNotNull(); - final var student = userTestRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + final var student = userTestRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); assertThat(learningPath).isNotNull(); } } - // TODO: Write tests for start learning path + @Nested + class StartLearningPath { + + @BeforeEach + void setup() { + course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); + } + + @Test + @WithMockUser(username = STUDENT2_OF_COURSE, roles = "USER") + void shouldReturnForbiddenIfNotOwn() throws Exception { + final var student = userRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); + final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); + request.patch("/api/learning-path/" + learningPath.getId() + "/start", null, HttpStatus.FORBIDDEN); + } + + @Test + @WithMockUser(username = STUDENT1_OF_COURSE, roles = "USER") + void shouldReturnBadRequestIfAlreadyStarted() throws Exception { + final var student = userRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); + final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); + learningPath.setStartedByStudent(true); + learningPathRepository.save(learningPath); + request.patch("/api/learning-path/" + learningPath.getId() + "/start", null, HttpStatus.BAD_REQUEST); + } + + @Test + @WithMockUser(username = STUDENT1_OF_COURSE, roles = "USER") + void shouldStartLearningPath() throws Exception { + final var student = userRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); + final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); + request.patch("/api/learning-path/" + learningPath.getId() + "/start", null, HttpStatus.NO_CONTENT); + final var updatedLearningPath = learningPathRepository.findByIdElseThrow(learningPath.getId()); + assertThat(updatedLearningPath.isStartedByStudent()).isTrue(); + } + } @Test @WithMockUser(username = TEST_PREFIX + "student2", roles = "USER") void testGetCompetencyProgressForLearningPathByOtherStudent() throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); - final var student = userTestRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + final var student = userTestRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); request.get("/api/learning-path/" + learningPath.getId() + "/competency-progress", HttpStatus.FORBIDDEN, Set.class); } @Test - @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") + @WithMockUser(username = STUDENT1_OF_COURSE, roles = "USER") void testGetCompetencyProgressForLearningPathByOwner() throws Exception { testGetCompetencyProgressForLearningPath(); } @@ -651,10 +688,10 @@ void testGetCompetencyProgressForLearningPathByInstructor() throws Exception { } @Test - @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") + @WithMockUser(username = STUDENT1_OF_COURSE, roles = "USER") void testGetLearningPathNavigation() throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); - final var student = userTestRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + final var student = userTestRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); competencyProgressService.updateProgressByLearningObjectSync(textUnit, Set.of(student)); @@ -666,10 +703,10 @@ void testGetLearningPathNavigation() throws Exception { } @Test - @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") + @WithMockUser(username = STUDENT1_OF_COURSE, roles = "USER") void testGetLearningPathNavigationEmptyCompetencies() throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); - final var student = userTestRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + final var student = userTestRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); textExercise.setCompetencies(Set.of()); @@ -691,10 +728,10 @@ void testGetLearningPathNavigationEmptyCompetencies() throws Exception { } @Test - @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") + @WithMockUser(username = STUDENT1_OF_COURSE, roles = "USER") void testGetLearningPathNavigationDoesNotLeakUnreleasedLearningObjects() throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); - final var student = userTestRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + final var student = userTestRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); textExercise.setCompetencies(Set.of()); @@ -739,10 +776,10 @@ private void verifyNavigationObjectResult(LearningObject expectedObject, Learnin } @Test - @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") + @WithMockUser(username = STUDENT1_OF_COURSE, roles = "USER") void testGetRelativeLearningPathNavigation() throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); - final var student = userTestRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + final var student = userTestRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); final var result = request.get("/api/learning-path/" + learningPath.getId() + "/relative-navigation?learningObjectId=" + textUnit.getId() + "&learningObjectType=" + LearningPathNavigationObjectDTO.LearningObjectType.LECTURE + "&competencyId=" + competencies[0].getId(), HttpStatus.OK, LearningPathNavigationDTO.class); @@ -756,16 +793,16 @@ void testGetRelativeLearningPathNavigation() throws Exception { @WithMockUser(username = TEST_PREFIX + "student1337", roles = "USER") void testGetLearningPathNavigationForOtherStudent() throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); - final var student = userTestRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + final var student = userTestRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); request.get("/api/learning-path/" + learningPath.getId() + "/navigation", HttpStatus.FORBIDDEN, LearningPathNavigationDTO.class); } @Test - @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") + @WithMockUser(username = STUDENT1_OF_COURSE, roles = "USER") void testGetLearningPathNavigationOverview() throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); - final var student = userTestRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + final var student = userTestRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); final var result = request.get("/api/learning-path/" + learningPath.getId() + "/navigation-overview", HttpStatus.OK, LearningPathNavigationOverviewDTO.class); @@ -779,26 +816,26 @@ void testGetLearningPathNavigationOverview() throws Exception { @WithMockUser(username = TEST_PREFIX + "student1337", roles = "USER") void testGetLearningPathNavigationOverviewForOtherStudent() throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); - final var student = userTestRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + final var student = userTestRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); request.get("/api/learning-path/" + learningPath.getId() + "/navigation-overview", HttpStatus.FORBIDDEN, LearningPathNavigationOverviewDTO.class); } @Test - @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") + @WithMockUser(username = STUDENT1_OF_COURSE, roles = "USER") void testGetCompetencyOrderForLearningPath() throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); - final var student = userTestRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + final var student = userTestRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); final var result = request.getList("/api/learning-path/" + learningPath.getId() + "/competencies", HttpStatus.OK, CompetencyNameDTO.class); assertThat(result).containsExactlyElementsOf(Arrays.stream(competencies).map(CompetencyNameDTO::of).toList()); } @Test - @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") + @WithMockUser(username = STUDENT1_OF_COURSE, roles = "USER") void testGetLearningObjectsForCompetency() throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); - final var student = userTestRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + final var student = userTestRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); var result = request.getList("/api/learning-path/" + learningPath.getId() + "/competencies/" + competencies[0].getId() + "/learning-objects", HttpStatus.OK, LearningPathNavigationObjectDTO.class); @@ -812,10 +849,10 @@ void testGetLearningObjectsForCompetency() throws Exception { } @Test - @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") + @WithMockUser(username = STUDENT1_OF_COURSE, roles = "USER") void testGetLearningObjectsForCompetencyMultipleObjects() throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); - final var student = userTestRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + final var student = userTestRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); List completedLectureUnits = List.of(createAndLinkTextUnit(student, competencies[4], true), createAndLinkTextUnit(student, competencies[4], true)); @@ -846,7 +883,7 @@ void testGetLearningObjectsForCompetencyMultipleObjects() throws Exception { void testGetCompetencyProgressForLearningPath() throws Exception { course = learningPathUtilService.enableAndGenerateLearningPathsForCourse(course); - final var student = userTestRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + final var student = userTestRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(course.getId(), student.getId()); final var result = request.get("/api/learning-path/" + learningPath.getId() + "/competency-progress", HttpStatus.OK, Set.class); assertThat(result).hasSize(5); @@ -857,7 +894,7 @@ private TextExercise createAndLinkTextExercise(Competency competency, boolean wi Set gradingCriteria = exerciseUtilService.addGradingInstructionsToExercise(textExercise); gradingCriterionRepository.saveAll(gradingCriteria); if (withAssessment) { - var student = userTestRepository.findOneByLogin(STUDENT_OF_COURSE).orElseThrow(); + var student = userTestRepository.findOneByLogin(STUDENT1_OF_COURSE).orElseThrow(); studentScoreUtilService.createStudentScore(textExercise, student, 100.0); } competencyUtilService.linkExerciseToCompetency(competency, textExercise);