diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/domain/competency/LearningPath.java b/src/main/java/de/tum/cit/aet/artemis/atlas/domain/competency/LearningPath.java index 267efe722982..ab4c961be143 100644 --- a/src/main/java/de/tum/cit/aet/artemis/atlas/domain/competency/LearningPath.java +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/domain/competency/LearningPath.java @@ -31,6 +31,12 @@ public class LearningPath extends DomainObject { @Column(name = "progress") private int progress; + /** + * flag indicating if a student started the learning path + */ + @Column(name = "started_by_student") + private boolean startedByStudent = false; + @ManyToOne @JoinColumn(name = "user_id") private User user; @@ -89,8 +95,16 @@ public void removeCompetency(CourseCompetency competency) { this.competencies.remove(competency); } + public boolean isStartedByStudent() { + return startedByStudent; + } + + public void setStartedByStudent(boolean startedByStudent) { + this.startedByStudent = startedByStudent; + } + @Override public String toString() { - return "LearningPath{" + "id=" + getId() + ", user=" + user + ", course=" + course + ", competencies=" + competencies + '}'; + return "LearningPath{" + "id=" + getId() + ", user=" + user + ", course=" + course + ", competencies=" + competencies + ", startedByStudent=" + startedByStudent + "}"; } } diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/dto/LearningPathDTO.java b/src/main/java/de/tum/cit/aet/artemis/atlas/dto/LearningPathDTO.java new file mode 100644 index 000000000000..f61598b30cf4 --- /dev/null +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/dto/LearningPathDTO.java @@ -0,0 +1,13 @@ +package de.tum.cit.aet.artemis.atlas.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; + +import de.tum.cit.aet.artemis.atlas.domain.competency.LearningPath; + +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public record LearningPathDTO(long id, boolean startedByStudent, int progress) { + + public static LearningPathDTO of(LearningPath learningPath) { + return new LearningPathDTO(learningPath.getId(), learningPath.isStartedByStudent(), learningPath.getProgress()); + } +} 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 937123a30fe1..190565c5c35c 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 @@ -25,6 +25,7 @@ import de.tum.cit.aet.artemis.atlas.dto.CompetencyGraphEdgeDTO; import de.tum.cit.aet.artemis.atlas.dto.CompetencyGraphNodeDTO; import de.tum.cit.aet.artemis.atlas.dto.LearningPathCompetencyGraphDTO; +import de.tum.cit.aet.artemis.atlas.dto.LearningPathDTO; import de.tum.cit.aet.artemis.atlas.dto.LearningPathHealthDTO; import de.tum.cit.aet.artemis.atlas.dto.LearningPathInformationDTO; import de.tum.cit.aet.artemis.atlas.dto.LearningPathNavigationOverviewDTO; @@ -39,6 +40,7 @@ import de.tum.cit.aet.artemis.core.dto.SearchResultPageDTO; import de.tum.cit.aet.artemis.core.dto.pageablesearch.SearchTermPageableSearchDTO; import de.tum.cit.aet.artemis.core.exception.AccessForbiddenException; +import de.tum.cit.aet.artemis.core.exception.ConflictException; import de.tum.cit.aet.artemis.core.repository.CourseRepository; import de.tum.cit.aet.artemis.core.repository.UserRepository; import de.tum.cit.aet.artemis.core.util.PageUtil; @@ -243,6 +245,52 @@ private void updateLearningPathProgress(@NotNull LearningPath learningPath) { log.debug("Updated LearningPath (id={}) for user (id={})", learningPath.getId(), userId); } + /** + * Get the learning path for the current user in the given course. + * + * @param courseId the id of the course + * @return the learning path of the current user + */ + public LearningPathDTO getLearningPathForCurrentUser(long courseId) { + final var currentUser = userRepository.getUser(); + final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(courseId, currentUser.getId()); + return LearningPathDTO.of(learningPath); + } + + /** + * Generate a learning path for the current user in the given course. + * + * @param courseId the id of the course + * @return the generated learning path + */ + public LearningPathDTO generateLearningPathForCurrentUser(long courseId) { + final var currentUser = userRepository.getUser(); + final var course = courseRepository.findWithEagerCompetenciesAndPrerequisitesByIdElseThrow(courseId); + if (learningPathRepository.findByCourseIdAndUserId(courseId, currentUser.getId()).isPresent()) { + throw new ConflictException("Learning path already exists.", "LearningPath", "learningPathAlreadyExists"); + } + final var learningPath = generateLearningPathForUser(course, currentUser); + return LearningPathDTO.of(learningPath); + } + + /** + * Start the learning path for the current user + * + * @param learningPathId the id of the learning path + */ + public void startLearningPathForCurrentUser(long learningPathId) { + final var learningPath = learningPathRepository.findByIdElseThrow(learningPathId); + final var currentUser = userRepository.getUser(); + if (!learningPath.getUser().equals(currentUser)) { + throw new AccessForbiddenException("You are not allowed to start this learning path."); + } + else if (learningPath.isStartedByStudent()) { + throw new ConflictException("Learning path already started.", "LearningPath", "learningPathAlreadyStarted"); + } + learningPath.setStartedByStudent(true); + learningPathRepository.save(learningPath); + } + /** * Gets the health status of learning paths for the given course. * diff --git a/src/main/java/de/tum/cit/aet/artemis/atlas/web/LearningPathResource.java b/src/main/java/de/tum/cit/aet/artemis/atlas/web/LearningPathResource.java index 709ac913cdf9..d940e50acc51 100644 --- a/src/main/java/de/tum/cit/aet/artemis/atlas/web/LearningPathResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/atlas/web/LearningPathResource.java @@ -17,6 +17,7 @@ import org.springframework.context.annotation.Profile; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; @@ -28,6 +29,7 @@ import de.tum.cit.aet.artemis.atlas.dto.CompetencyNameDTO; import de.tum.cit.aet.artemis.atlas.dto.CompetencyProgressForLearningPathDTO; import de.tum.cit.aet.artemis.atlas.dto.LearningPathCompetencyGraphDTO; +import de.tum.cit.aet.artemis.atlas.dto.LearningPathDTO; import de.tum.cit.aet.artemis.atlas.dto.LearningPathHealthDTO; import de.tum.cit.aet.artemis.atlas.dto.LearningPathInformationDTO; import de.tum.cit.aet.artemis.atlas.dto.LearningPathNavigationDTO; @@ -301,19 +303,31 @@ private ResponseEntity getLearningPathNgx(@PathVariable long } /** - * GET courses/:courseId/learning-path-id : Gets the id of the learning path. + * GET courses/:courseId/learning-path/me : Gets the learning path of the current user in the course. * - * @param courseId the id of the course from which the learning path id should be fetched - * @return the ResponseEntity with status 200 (OK) and with body the id of the learning path + * @param courseId the id of the course for which the learning path should be fetched + * @return the ResponseEntity with status 200 (OK) and with body the learning path */ - @GetMapping("courses/{courseId}/learning-path-id") + @GetMapping("courses/{courseId}/learning-path/me") @EnforceAtLeastStudentInCourse - public ResponseEntity getLearningPathId(@PathVariable long courseId) { - log.debug("REST request to get learning path id for course with id: {}", courseId); + public ResponseEntity getLearningPathForCurrentUser(@PathVariable long courseId) { + log.debug("REST request to get learning path of current user for course with id: {}", courseId); courseService.checkLearningPathsEnabledElseThrow(courseId); - User user = userRepository.getUser(); - final var learningPath = learningPathRepository.findByCourseIdAndUserIdElseThrow(courseId, user.getId()); - return ResponseEntity.ok(learningPath.getId()); + return ResponseEntity.ok(learningPathService.getLearningPathForCurrentUser(courseId)); + } + + /** + * PATCH learning-path/:learningPathId/start : Starts the learning path for the current user. + * + * @param learningPathId the id of the learning path to start + * @return the ResponseEntity with status 204 (NO_CONTENT) + */ + @PatchMapping("learning-path/{learningPathId}/start") + @EnforceAtLeastStudent + public ResponseEntity startLearningPathForCurrentUser(@PathVariable long learningPathId) { + log.debug("REST request to start learning path with id: {}", learningPathId); + learningPathService.startLearningPathForCurrentUser(learningPathId); + return ResponseEntity.noContent().build(); } /** @@ -324,20 +338,11 @@ public ResponseEntity getLearningPathId(@PathVariable long courseId) { */ @PostMapping("courses/{courseId}/learning-path") @EnforceAtLeastStudentInCourse - public ResponseEntity generateLearningPath(@PathVariable long courseId) throws URISyntaxException { - log.debug("REST request to generate learning path for user in course with id: {}", courseId); + public ResponseEntity generateLearningPathForCurrentUser(@PathVariable long courseId) throws URISyntaxException { + log.debug("REST request to generate learning path for current user in course with id: {}", courseId); courseService.checkLearningPathsEnabledElseThrow(courseId); - - User user = userRepository.getUser(); - final var learningPathOptional = learningPathRepository.findByCourseIdAndUserId(courseId, user.getId()); - - if (learningPathOptional.isPresent()) { - throw new BadRequestException("Learning path already exists."); - } - - final var course = courseRepository.findWithEagerCompetenciesAndPrerequisitesByIdElseThrow(courseId); - final var learningPath = learningPathService.generateLearningPathForUser(course, user); - return ResponseEntity.created(new URI("api/learning-path/" + learningPath.getId())).body(learningPath.getId()); + final var learningPathDTO = learningPathService.generateLearningPathForCurrentUser(courseId); + return ResponseEntity.created(new URI("api/learning-path/" + learningPathDTO.id())).body(learningPathDTO); } /** diff --git a/src/main/java/de/tum/cit/aet/artemis/core/web/CourseResource.java b/src/main/java/de/tum/cit/aet/artemis/core/web/CourseResource.java index 10da69a96a5d..0cb3379e4f99 100644 --- a/src/main/java/de/tum/cit/aet/artemis/core/web/CourseResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/core/web/CourseResource.java @@ -364,14 +364,14 @@ else if (courseUpdate.getCourseIcon() == null && existingCourse.getCourseIcon() } /** - * PUT courses/:courseId/onlineCourseConfiguration : Updates the onlineCourseConfiguration for the given course. + * PUT courses/:courseId/online-course-configuration : Updates the onlineCourseConfiguration for the given course. * * @param courseId the id of the course to update * @param onlineCourseConfiguration the online course configuration to update * @return the ResponseEntity with status 200 (OK) and with body the updated online course configuration */ // TODO: move into LTIResource - @PutMapping("courses/{courseId}/onlineCourseConfiguration") + @PutMapping("courses/{courseId}/online-course-configuration") @EnforceAtLeastInstructor @Profile(PROFILE_LTI) public ResponseEntity updateOnlineCourseConfiguration(@PathVariable Long courseId, @@ -821,12 +821,12 @@ public ResponseEntity getCourseWithOrganizations(@PathVariable Long cour } /** - * GET /courses/:courseId/lockedSubmissions Get locked submissions for course for user + * GET /courses/:courseId/locked-submissions Get locked submissions for course for user * * @param courseId the id of the course * @return the ResponseEntity with status 200 (OK) and with body the course, or with status 404 (Not Found) */ - @GetMapping("courses/{courseId}/lockedSubmissions") + @GetMapping("courses/{courseId}/locked-submissions") @EnforceAtLeastTutor public ResponseEntity> getLockedSubmissionsForCourse(@PathVariable Long courseId) { log.debug("REST request to get all locked submissions for course : {}", courseId); diff --git a/src/main/java/de/tum/cit/aet/artemis/exam/web/ExamResource.java b/src/main/java/de/tum/cit/aet/artemis/exam/web/ExamResource.java index 40be2685e58e..209f2a4fa040 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exam/web/ExamResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/exam/web/ExamResource.java @@ -1170,13 +1170,13 @@ public ResponseEntity getLatestIndividualEndDateOfExam(@Path } /** - * GET /courses/:courseId/exams/:examId/lockedSubmissions Get locked submissions for exam for user + * GET /courses/:courseId/exams/:examId/locked-submissions Get locked submissions for exam for user * * @param courseId - the id of the course * @param examId - the id of the exam * @return the ResponseEntity with status 200 (OK) and with body the course, or with status 404 (Not Found) */ - @GetMapping("courses/{courseId}/exams/{examId}/lockedSubmissions") + @GetMapping("courses/{courseId}/exams/{examId}/locked-submissions") @EnforceAtLeastInstructor public ResponseEntity> getLockedSubmissionsForExam(@PathVariable Long courseId, @PathVariable Long examId) { log.debug("REST request to get all locked submissions for course : {}", courseId); diff --git a/src/main/java/de/tum/cit/aet/artemis/exam/web/ExerciseGroupResource.java b/src/main/java/de/tum/cit/aet/artemis/exam/web/ExerciseGroupResource.java index 2593102ed063..97ebc25f858b 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exam/web/ExerciseGroupResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/exam/web/ExerciseGroupResource.java @@ -83,7 +83,7 @@ public ExerciseGroupResource(ExerciseGroupRepository exerciseGroupRepository, Ex } /** - * POST /courses/{courseId}/exams/{examId}/exerciseGroups : Create a new exercise group. + * POST /courses/{courseId}/exams/{examId}/exercise-groups : Create a new exercise group. * * @param courseId the course to which the exercise group belongs to * @param examId the exam to which the exercise group belongs to @@ -92,7 +92,7 @@ public ExerciseGroupResource(ExerciseGroupRepository exerciseGroupRepository, Ex * or with status 400 (Bad Request) if the exerciseGroup has already an ID * @throws URISyntaxException if the Location URI syntax is incorrect */ - @PostMapping("courses/{courseId}/exams/{examId}/exerciseGroups") + @PostMapping("courses/{courseId}/exams/{examId}/exercise-groups") @EnforceAtLeastEditor public ResponseEntity createExerciseGroup(@PathVariable Long courseId, @PathVariable Long examId, @RequestBody ExerciseGroup exerciseGroup) throws URISyntaxException { @@ -117,11 +117,11 @@ public ResponseEntity createExerciseGroup(@PathVariable Long cour Exam savedExam = examRepository.save(examFromDB); ExerciseGroup savedExerciseGroup = savedExam.getExerciseGroups().getLast(); - return ResponseEntity.created(new URI("/api/courses/" + courseId + "/exams/" + examId + "/exerciseGroups/" + savedExerciseGroup.getId())).body(savedExerciseGroup); + return ResponseEntity.created(new URI("/api/courses/" + courseId + "/exams/" + examId + "/exercise-groups/" + savedExerciseGroup.getId())).body(savedExerciseGroup); } /** - * PUT /courses/{courseId}/exams/{examId}/exerciseGroups : Update an existing exercise group. + * PUT /courses/{courseId}/exams/{examId}/exercise-groups : Update an existing exercise group. * * @param courseId the course to which the exercise group belongs to * @param examId the exam to which the exercise group belongs to @@ -129,7 +129,7 @@ public ResponseEntity createExerciseGroup(@PathVariable Long cour * @return the ResponseEntity with status 200 (OK) and with the body of the updated exercise group * @throws URISyntaxException if the Location URI syntax is incorrect */ - @PutMapping("courses/{courseId}/exams/{examId}/exerciseGroups") + @PutMapping("courses/{courseId}/exams/{examId}/exercise-groups") @EnforceAtLeastEditor public ResponseEntity updateExerciseGroup(@PathVariable Long courseId, @PathVariable Long examId, @RequestBody ExerciseGroup updatedExerciseGroup) throws URISyntaxException { @@ -170,14 +170,14 @@ public ResponseEntity> importExerciseGroup(@PathVariable Lon } /** - * GET /courses/{courseId}/exams/{examId}/exerciseGroups/{exerciseGroupId} : Find an exercise group by id. + * GET /courses/{courseId}/exams/{examId}/exercise-groups/{exerciseGroupId} : Find an exercise group by id. * * @param courseId the course to which the exercise group belongs to * @param examId the exam to which the exercise group belongs to * @param exerciseGroupId the id of the exercise group to find * @return the ResponseEntity with status 200 (OK) and with the found exercise group as body */ - @GetMapping("courses/{courseId}/exams/{examId}/exerciseGroups/{exerciseGroupId}") + @GetMapping("courses/{courseId}/exams/{examId}/exercise-groups/{exerciseGroupId}") @EnforceAtLeastEditor public ResponseEntity getExerciseGroup(@PathVariable Long courseId, @PathVariable Long examId, @PathVariable Long exerciseGroupId) { log.debug("REST request to get exercise group : {}", exerciseGroupId); @@ -189,13 +189,13 @@ public ResponseEntity getExerciseGroup(@PathVariable Long courseI } /** - * GET courses/{courseId}/exams/{examId}/exerciseGroups : Get all exercise groups of the given exam + * GET courses/{courseId}/exams/{examId}/exercise-groups : Get all exercise groups of the given exam * * @param courseId the course to which the exercise groups belong to * @param examId the exam to which the exercise groups belong to * @return the ResponseEntity with status 200 (OK) and a list of exercise groups. The list can be empty */ - @GetMapping("courses/{courseId}/exams/{examId}/exerciseGroups") + @GetMapping("courses/{courseId}/exams/{examId}/exercise-groups") @EnforceAtLeastEditor public ResponseEntity> getExerciseGroupsForExam(@PathVariable Long courseId, @PathVariable Long examId) { log.debug("REST request to get all exercise groups for exam : {}", examId); @@ -207,7 +207,7 @@ public ResponseEntity> getExerciseGroupsForExam(@PathVariabl } /** - * DELETE /courses/{courseId}/exams/{examId}/exerciseGroups/{exerciseGroupId} : Delete the exercise group with the given id. + * DELETE /courses/{courseId}/exams/{examId}/exercise-groups/{exerciseGroupId} : Delete the exercise group with the given id. * * @param courseId the course to which the exercise group belongs to * @param examId the exam to which the exercise group belongs to @@ -218,7 +218,7 @@ public ResponseEntity> getExerciseGroupsForExam(@PathVariabl * LocalCI, it does not make sense to keep these artifacts * @return the ResponseEntity with status 200 (OK) */ - @DeleteMapping("courses/{courseId}/exams/{examId}/exerciseGroups/{exerciseGroupId}") + @DeleteMapping("courses/{courseId}/exams/{examId}/exercise-groups/{exerciseGroupId}") @EnforceAtLeastInstructor public ResponseEntity deleteExerciseGroup(@PathVariable Long courseId, @PathVariable Long examId, @PathVariable Long exerciseGroupId, @RequestParam(defaultValue = "true") boolean deleteStudentReposBuildPlans, @RequestParam(defaultValue = "true") boolean deleteBaseReposBuildPlans) { diff --git a/src/main/java/de/tum/cit/aet/artemis/exercise/web/ParticipationResource.java b/src/main/java/de/tum/cit/aet/artemis/exercise/web/ParticipationResource.java index 6559c28b9d93..c6cdc6ee1730 100644 --- a/src/main/java/de/tum/cit/aet/artemis/exercise/web/ParticipationResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/exercise/web/ParticipationResource.java @@ -718,7 +718,7 @@ public ResponseEntity> getAllParticipationsForCourse( * @param participationId the participationId of the participation to retrieve * @return the ResponseEntity with status 200 (OK) and with body the participation, or with status 404 (Not Found) */ - @GetMapping("participations/{participationId}/withLatestResult") + @GetMapping("participations/{participationId}/with-latest-result") @EnforceAtLeastStudent public ResponseEntity getParticipationWithLatestResult(@PathVariable Long participationId) { log.debug("REST request to get Participation : {}", participationId); @@ -756,7 +756,7 @@ public ResponseEntity getParticipationForCurrentUser(@Path * @param participationId The participationId of the participation * @return The latest build artifact (JAR/WAR) for the participation */ - @GetMapping("participations/{participationId}/buildArtifact") + @GetMapping("participations/{participationId}/build-artifact") @EnforceAtLeastStudent public ResponseEntity getParticipationBuildArtifact(@PathVariable Long participationId) { log.debug("REST request to get Participation build artifact: {}", participationId); @@ -931,14 +931,14 @@ private ResponseEntity deleteParticipation(StudentParticipation participat } /** - * DELETE /participations/:participationId : remove the build plan of the ProgrammingExerciseStudentParticipation of the "participationId". + * DELETE /participations/:participationId/cleanup-build-plan : remove the build plan of the ProgrammingExerciseStudentParticipation of the "participationId". * This only works for programming exercises. * * @param participationId the participationId of the ProgrammingExerciseStudentParticipation for which the build plan should be removed * @param principal The identity of the user accessing this resource * @return the ResponseEntity with status 200 (OK) */ - @PutMapping("participations/{participationId}/cleanupBuildPlan") + @PutMapping("participations/{participationId}/cleanup-build-plan") @EnforceAtLeastInstructor @FeatureToggle(Feature.ProgrammingExercises) public ResponseEntity cleanupBuildPlan(@PathVariable Long participationId, Principal principal) { diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/web/localci/AeolusTemplateResource.java b/src/main/java/de/tum/cit/aet/artemis/programming/web/localci/AeolusTemplateResource.java index 9919c2440364..9e5530e94813 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/web/localci/AeolusTemplateResource.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/web/localci/AeolusTemplateResource.java @@ -79,8 +79,8 @@ public ResponseEntity getAeolusTemplate(@PathVariable ProgrammingLanguag } /** - * GET /api/aeolus/templates/:language/:projectType : Get the aeolus template file with the given filename
- * GET /api/aeolus/templates/:language : Get the aeolus template file with the given filename + * GET /api/aeolus/template-scripts/:language/:projectType : Get the aeolus template file with the given filename
+ * GET /api/aeolus/template-scripts/:language : Get the aeolus template file with the given filename *

* The windfile contains the default build plan configuration for new programming exercises. * @@ -91,7 +91,7 @@ public ResponseEntity getAeolusTemplate(@PathVariable ProgrammingLanguag * @param testCoverage Whether the test coverage template should be used * @return The requested file, or 404 if the file doesn't exist */ - @GetMapping({ "templateScripts/{language}/{projectType}", "templateScripts/{language}" }) + @GetMapping({ "template-scripts/{language}/{projectType}", "template-scripts/{language}" }) @EnforceAtLeastEditor public ResponseEntity getAeolusTemplateScript(@PathVariable ProgrammingLanguage language, @PathVariable Optional projectType, @RequestParam(value = "staticAnalysis", defaultValue = "false") boolean staticAnalysis, diff --git a/src/main/resources/config/liquibase/changelog/20240924125742_changelog.xml b/src/main/resources/config/liquibase/changelog/20240924125742_changelog.xml new file mode 100644 index 000000000000..c74139c77c6d --- /dev/null +++ b/src/main/resources/config/liquibase/changelog/20240924125742_changelog.xml @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/src/main/resources/config/liquibase/master.xml b/src/main/resources/config/liquibase/master.xml index 2c204094c0ff..64295fe02504 100644 --- a/src/main/resources/config/liquibase/master.xml +++ b/src/main/resources/config/liquibase/master.xml @@ -23,6 +23,7 @@ + diff --git a/src/main/webapp/app/admin/metrics/metrics.model.ts b/src/main/webapp/app/admin/metrics/metrics.model.ts index 1e006405c805..dbed33af6fc7 100644 --- a/src/main/webapp/app/admin/metrics/metrics.model.ts +++ b/src/main/webapp/app/admin/metrics/metrics.model.ts @@ -84,6 +84,7 @@ export enum HttpMethod { Post = 'POST', Get = 'GET', Delete = 'DELETE', + Patch = 'PATCH', } export interface ProcessMetrics { diff --git a/src/main/webapp/app/course/learning-paths/pages/learning-path-student-page/learning-path-student-page.component.html b/src/main/webapp/app/course/learning-paths/pages/learning-path-student-page/learning-path-student-page.component.html index b6a8f8255ef3..7e116bc6a169 100644 --- a/src/main/webapp/app/course/learning-paths/pages/learning-path-student-page/learning-path-student-page.component.html +++ b/src/main/webapp/app/course/learning-paths/pages/learning-path-student-page/learning-path-student-page.component.html @@ -1,12 +1,12 @@

- @if (isLearningPathIdLoading()) { + @if (isLearningPathLoading()) {
- } @else if (learningPathId()) { - + } @else if (learningPath() && learningPath()!.startedByStudent) { +
@if (currentLearningObject()?.type === LearningObjectType.LECTURE) { @@ -27,10 +27,10 @@

diff --git a/src/main/webapp/app/course/learning-paths/pages/learning-path-student-page/learning-path-student-page.component.ts b/src/main/webapp/app/course/learning-paths/pages/learning-path-student-page/learning-path-student-page.component.ts index 423415683d94..8366f6d25e22 100644 --- a/src/main/webapp/app/course/learning-paths/pages/learning-path-student-page/learning-path-student-page.component.ts +++ b/src/main/webapp/app/course/learning-paths/pages/learning-path-student-page/learning-path-student-page.component.ts @@ -1,5 +1,5 @@ import { Component, effect, inject, signal } from '@angular/core'; -import { LearningObjectType } from 'app/entities/competency/learning-path.model'; +import { LearningObjectType, LearningPathDTO } from 'app/entities/competency/learning-path.model'; import { map } from 'rxjs'; import { toSignal } from '@angular/core/rxjs-interop'; import { LearningPathNavComponent } from 'app/course/learning-paths/components/learning-path-student-nav/learning-path-student-nav.component'; @@ -21,45 +21,49 @@ import { onError } from 'app/shared/util/global.utils'; export class LearningPathStudentPageComponent { protected readonly LearningObjectType = LearningObjectType; - private readonly learningApiService: LearningPathApiService = inject(LearningPathApiService); + private readonly learningApiService = inject(LearningPathApiService); private readonly learningPathNavigationService = inject(LearningPathNavigationService); - private readonly alertService: AlertService = inject(AlertService); - private readonly activatedRoute: ActivatedRoute = inject(ActivatedRoute); + private readonly alertService = inject(AlertService); + private readonly activatedRoute = inject(ActivatedRoute); - readonly isLearningPathIdLoading = signal(false); - readonly learningPathId = signal(undefined); + readonly isLearningPathLoading = signal(false); + readonly learningPath = signal(undefined); readonly courseId = toSignal(this.activatedRoute.parent!.parent!.params.pipe(map((params) => Number(params.courseId))), { requireSync: true }); readonly currentLearningObject = this.learningPathNavigationService.currentLearningObject; readonly isLearningPathNavigationLoading = this.learningPathNavigationService.isLoading; constructor() { - effect(async () => await this.loadLearningPathId(this.courseId()), { allowSignalWrites: true }); + effect(() => this.loadLearningPath(this.courseId()), { allowSignalWrites: true }); } - private async loadLearningPathId(courseId: number): Promise { + private async loadLearningPath(courseId: number): Promise { try { - this.isLearningPathIdLoading.set(true); - const learningPathId = await this.learningApiService.getLearningPathId(courseId); - this.learningPathId.set(learningPathId); + this.isLearningPathLoading.set(true); + const learningPath = await this.learningApiService.getLearningPathForCurrentUser(courseId); + this.learningPath.set(learningPath); } catch (error) { // If learning path does not exist (404) ignore the error if (error.status != 404) { onError(this.alertService, error); } } finally { - this.isLearningPathIdLoading.set(false); + this.isLearningPathLoading.set(false); } } - async generateLearningPath(courseId: number): Promise { + async startLearningPath(): Promise { try { - this.isLearningPathIdLoading.set(true); - const learningPathId = await this.learningApiService.generateLearningPath(courseId); - this.learningPathId.set(learningPathId); + this.isLearningPathLoading.set(true); + if (!this.learningPath()) { + const learningPath = await this.learningApiService.generateLearningPathForCurrentUser(this.courseId()); + this.learningPath.set(learningPath); + } + await this.learningApiService.startLearningPathForCurrentUser(this.learningPath()!.id); + this.learningPath.update((learningPath) => ({ ...learningPath!, startedByStudent: true })); } catch (error) { this.alertService.error(error); } finally { - this.isLearningPathIdLoading.set(false); + this.isLearningPathLoading.set(false); } } } diff --git a/src/main/webapp/app/course/learning-paths/services/base-api-http.service.ts b/src/main/webapp/app/course/learning-paths/services/base-api-http.service.ts index ee3d465d83d2..a5b5653ce442 100644 --- a/src/main/webapp/app/course/learning-paths/services/base-api-http.service.ts +++ b/src/main/webapp/app/course/learning-paths/services/base-api-http.service.ts @@ -155,4 +155,34 @@ export abstract class BaseApiHttpService { ): Promise { return await this.request(HttpMethod.Delete, url, options); } + + /** + * Constructs a `PATCH` request that interprets the body as JSON and + * returns a Promise of an object of type `T`. + * + * @param url The endpoint URL excluding the base server url (/api). + * @param body The content to include in the body of the request. + * @param options The HTTP options to send with the request. + * @protected + * + * @return A `Promise` of type `Object` (T), + */ + protected async patch( + url: string, + body?: any, + options?: { + headers?: + | HttpHeaders + | { + [header: string]: string | string[]; + }; + params?: + | HttpParams + | { + [param: string]: string | number | boolean | ReadonlyArray; + }; + }, + ): Promise { + return await this.request(HttpMethod.Patch, url, { body: body, ...options }); + } } diff --git a/src/main/webapp/app/course/learning-paths/services/learning-path-api.service.ts b/src/main/webapp/app/course/learning-paths/services/learning-path-api.service.ts index 35dcf071e505..01a43398f1c8 100644 --- a/src/main/webapp/app/course/learning-paths/services/learning-path-api.service.ts +++ b/src/main/webapp/app/course/learning-paths/services/learning-path-api.service.ts @@ -3,6 +3,7 @@ import { CompetencyGraphDTO, LearningObjectType, LearningPathCompetencyDTO, + LearningPathDTO, LearningPathNavigationDTO, LearningPathNavigationObjectDTO, LearningPathNavigationOverviewDTO, @@ -14,8 +15,12 @@ import { BaseApiHttpService } from 'app/course/learning-paths/services/base-api- providedIn: 'root', }) export class LearningPathApiService extends BaseApiHttpService { - async getLearningPathId(courseId: number): Promise { - return await this.get(`courses/${courseId}/learning-path-id`); + async getLearningPathForCurrentUser(courseId: number): Promise { + return await this.get(`courses/${courseId}/learning-path/me`); + } + + async startLearningPathForCurrentUser(learningPathId: number): Promise { + return await this.patch(`learning-path/${learningPathId}/start`); } async getLearningPathNavigation(learningPathId: number): Promise { @@ -35,8 +40,8 @@ export class LearningPathApiService extends BaseApiHttpService { return await this.get(`learning-path/${learningPathId}/relative-navigation`, { params: params }); } - async generateLearningPath(courseId: number): Promise { - return await this.post(`courses/${courseId}/learning-path`); + async generateLearningPathForCurrentUser(courseId: number): Promise { + return await this.post(`courses/${courseId}/learning-path`); } async getLearningPathNavigationOverview(learningPathId: number): Promise { diff --git a/src/main/webapp/app/course/manage/course-management.service.ts b/src/main/webapp/app/course/manage/course-management.service.ts index 0f442cada5ea..89af4f811106 100644 --- a/src/main/webapp/app/course/manage/course-management.service.ts +++ b/src/main/webapp/app/course/manage/course-management.service.ts @@ -77,7 +77,7 @@ export class CourseManagementService { * @param onlineCourseConfiguration - the updates to the online course configuration */ updateOnlineCourseConfiguration(courseId: number, onlineCourseConfiguration: OnlineCourseConfiguration): Observable { - return this.http.put(`${this.resourceUrl}/${courseId}/onlineCourseConfiguration`, onlineCourseConfiguration, { observe: 'response' }); + return this.http.put(`${this.resourceUrl}/${courseId}/online-course-configuration`, onlineCourseConfiguration, { observe: 'response' }); } findAllOnlineCoursesWithRegistrationId(clientId: string): Observable { @@ -442,7 +442,7 @@ export class CourseManagementService { * @param {number} courseId - The id of the course to be searched for */ findAllLockedSubmissionsOfCourse(courseId: number): Observable> { - return this.http.get(`${this.resourceUrl}/${courseId}/lockedSubmissions`, { observe: 'response' }).pipe( + return this.http.get(`${this.resourceUrl}/${courseId}/locked-submissions`, { observe: 'response' }).pipe( filter((res) => !!res.body), tap((res) => reconnectSubmissions(res.body!)), ); diff --git a/src/main/webapp/app/entities/competency/learning-path.model.ts b/src/main/webapp/app/entities/competency/learning-path.model.ts index 3f5c0e7775de..2c55868d0af0 100644 --- a/src/main/webapp/app/entities/competency/learning-path.model.ts +++ b/src/main/webapp/app/entities/competency/learning-path.model.ts @@ -32,6 +32,12 @@ export interface LearningPathCompetencyDTO { masteryProgress: number; } +export interface LearningPathDTO { + id: number; + progress: number; + startedByStudent: boolean; +} + export interface LearningPathNavigationObjectDTO { id: number; completed: boolean; diff --git a/src/main/webapp/app/exam/manage/exam-management.service.ts b/src/main/webapp/app/exam/manage/exam-management.service.ts index 5f2d4c8f8397..ef0a74bd28fc 100644 --- a/src/main/webapp/app/exam/manage/exam-management.service.ts +++ b/src/main/webapp/app/exam/manage/exam-management.service.ts @@ -487,7 +487,7 @@ export class ExamManagementService { } findAllLockedSubmissionsOfExam(courseId: number, examId: number) { - return this.http.get(`${this.resourceUrl}/${courseId}/exams/${examId}/lockedSubmissions`, { observe: 'response' }).pipe( + return this.http.get(`${this.resourceUrl}/${courseId}/exams/${examId}/locked-submissions`, { observe: 'response' }).pipe( filter((res) => !!res.body), tap((res) => reconnectSubmissions(res.body!)), ); diff --git a/src/main/webapp/app/exam/manage/exercise-groups/exercise-group.service.ts b/src/main/webapp/app/exam/manage/exercise-groups/exercise-group.service.ts index 0c5935f3be25..2481ccc98dbd 100644 --- a/src/main/webapp/app/exam/manage/exercise-groups/exercise-group.service.ts +++ b/src/main/webapp/app/exam/manage/exercise-groups/exercise-group.service.ts @@ -23,7 +23,7 @@ export class ExerciseGroupService { * @param exerciseGroup The exercise group to create. */ create(courseId: number, examId: number, exerciseGroup: ExerciseGroup): Observable { - return this.http.post(`${this.resourceUrl}/${courseId}/exams/${examId}/exerciseGroups`, exerciseGroup, { observe: 'response' }); + return this.http.post(`${this.resourceUrl}/${courseId}/exams/${examId}/exercise-groups`, exerciseGroup, { observe: 'response' }); } /** @@ -33,7 +33,7 @@ export class ExerciseGroupService { * @param exerciseGroup The exercise group to update. */ update(courseId: number, examId: number, exerciseGroup: ExerciseGroup): Observable { - return this.http.put(`${this.resourceUrl}/${courseId}/exams/${examId}/exerciseGroups`, exerciseGroup, { observe: 'response' }); + return this.http.put(`${this.resourceUrl}/${courseId}/exams/${examId}/exercise-groups`, exerciseGroup, { observe: 'response' }); } /** @@ -43,7 +43,7 @@ export class ExerciseGroupService { * @param exerciseGroupId The id of the exercise group to get. */ find(courseId: number, examId: number, exerciseGroupId: number): Observable { - return this.http.get(`${this.resourceUrl}/${courseId}/exams/${examId}/exerciseGroups/${exerciseGroupId}`, { observe: 'response' }); + return this.http.get(`${this.resourceUrl}/${courseId}/exams/${examId}/exercise-groups/${exerciseGroupId}`, { observe: 'response' }); } /** @@ -60,7 +60,7 @@ export class ExerciseGroupService { params = params.set('deleteStudentReposBuildPlans', deleteStudentReposBuildPlans.toString()); params = params.set('deleteBaseReposBuildPlans', deleteBaseReposBuildPlans.toString()); } - return this.http.delete(`${this.resourceUrl}/${courseId}/exams/${examId}/exerciseGroups/${exerciseGroupId}`, { params, observe: 'response' }); + return this.http.delete(`${this.resourceUrl}/${courseId}/exams/${examId}/exercise-groups/${exerciseGroupId}`, { params, observe: 'response' }); } /** @@ -69,6 +69,6 @@ export class ExerciseGroupService { * @param examId The exam id. */ findAllForExam(courseId: number, examId: number): Observable { - return this.http.get(`${this.resourceUrl}/${courseId}/exams/${examId}/exerciseGroups`, { observe: 'response' }); + return this.http.get(`${this.resourceUrl}/${courseId}/exams/${examId}/exercise-groups`, { observe: 'response' }); } } diff --git a/src/main/webapp/app/exercises/programming/shared/service/aeolus.service.ts b/src/main/webapp/app/exercises/programming/shared/service/aeolus.service.ts index 1b468776d570..6c6fca8ee1c4 100644 --- a/src/main/webapp/app/exercises/programming/shared/service/aeolus.service.ts +++ b/src/main/webapp/app/exercises/programming/shared/service/aeolus.service.ts @@ -40,7 +40,7 @@ export class AeolusService { */ getAeolusTemplateScript(language: ProgrammingLanguage, projectType?: ProjectType, staticAnalysis?: boolean, sequentialRuns?: boolean, coverage?: boolean): Observable { const uriWithParams = this.buildURIWithParams(language, projectType, staticAnalysis, sequentialRuns, coverage); - return this.http.get(`${this.resourceUrl}/templateScripts/` + uriWithParams.uri, { + return this.http.get(`${this.resourceUrl}/template-scripts/` + uriWithParams.uri, { responseType: 'text' as 'json', params: uriWithParams.params, }); diff --git a/src/main/webapp/app/exercises/shared/participation/participation.service.ts b/src/main/webapp/app/exercises/shared/participation/participation.service.ts index d3e5909a01ce..424d03ac8597 100644 --- a/src/main/webapp/app/exercises/shared/participation/participation.service.ts +++ b/src/main/webapp/app/exercises/shared/participation/participation.service.ts @@ -99,12 +99,12 @@ export class ParticipationService { cleanupBuildPlan(participation: StudentParticipation): Observable { const copy = this.convertParticipationDatesFromClient(participation); return this.http - .put(`${this.resourceUrl}/${participation.id}/cleanupBuildPlan`, copy, { observe: 'response' }) + .put(`${this.resourceUrl}/${participation.id}/cleanup-build-plan`, copy, { observe: 'response' }) .pipe(map((res: EntityResponseType) => this.convertParticipationResponseDatesFromServer(res))); } downloadArtifact(participationId: number): Observable { - return this.http.get(`${this.resourceUrl}/${participationId}/buildArtifact`, { observe: 'response', responseType: 'blob' }).pipe( + return this.http.get(`${this.resourceUrl}/${participationId}/build-artifact`, { observe: 'response', responseType: 'blob' }).pipe( map((res: EntityBlobResponseType) => { const fileNameCandidate = (res.headers.get('content-disposition') || '').split('filename=')[1]; const fileName = fileNameCandidate ? fileNameCandidate.replace(/"/g, '') : 'artifact'; diff --git a/src/main/webapp/app/home/home.component.ts b/src/main/webapp/app/home/home.component.ts index 27f816174872..af02b8b5862e 100644 --- a/src/main/webapp/app/home/home.component.ts +++ b/src/main/webapp/app/home/home.component.ts @@ -65,7 +65,7 @@ export class HomeComponent implements OnInit, AfterViewChecked { usernamePlaceholder = 'global.form.username.placeholder'; // default, might be overridden usernamePlaceholderTranslated = 'Login or email'; // default, might be overridden // if the server is not connected to an external user management, we accept all valid username patterns - usernameRegexPattern = /^[a-z0-9.@_-]{4,50}$/; // default (at least 4, at most 50 characters), might be overridden + usernameRegexPattern = /^[a-zA-Z0-9.@_-]{4,50}$/; // default (at least 4, at most 50 characters), might be overridden errorMessageUsername = 'home.errors.usernameIncorrect'; // default, might be overridden accountName?: string; // additional information in the welcome message @@ -120,7 +120,7 @@ export class HomeComponent implements OnInit, AfterViewChecked { this.errorMessageUsername = 'home.errors.tumWarning'; // Temporary workaround: Do not show a warning when TUM users login with an email address with a specific ending // allow emails with exactly one @ and usernames between 7 and 50 characters (shorter TUM usernames are not possible) - this.usernameRegexPattern = new RegExp(/^(?!.*@.*@)[a-z0-9.@_-]{7,50}$/); + this.usernameRegexPattern = new RegExp(/^(?!.*@.*@)[a-zA-Z0-9.@_-]{7,50}$/); } this.usernamePlaceholderTranslated = this.translateService.instant(this.usernamePlaceholder); this.translateService.onLangChange.subscribe(() => { diff --git a/src/test/java/de/tum/cit/aet/artemis/atlas/AbstractAtlasIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/atlas/AbstractAtlasIntegrationTest.java new file mode 100644 index 000000000000..3880c3527b23 --- /dev/null +++ b/src/test/java/de/tum/cit/aet/artemis/atlas/AbstractAtlasIntegrationTest.java @@ -0,0 +1,159 @@ +package de.tum.cit.aet.artemis.atlas; + +import org.springframework.beans.factory.annotation.Autowired; + +import de.tum.cit.aet.artemis.assessment.repository.GradingCriterionRepository; +import de.tum.cit.aet.artemis.assessment.util.StudentScoreUtilService; +import de.tum.cit.aet.artemis.atlas.competency.util.CompetencyProgressUtilService; +import de.tum.cit.aet.artemis.atlas.competency.util.CompetencyUtilService; +import de.tum.cit.aet.artemis.atlas.competency.util.PrerequisiteUtilService; +import de.tum.cit.aet.artemis.atlas.competency.util.StandardizedCompetencyUtilService; +import de.tum.cit.aet.artemis.atlas.learningpath.util.LearningPathUtilService; +import de.tum.cit.aet.artemis.atlas.repository.CompetencyJolRepository; +import de.tum.cit.aet.artemis.atlas.repository.CompetencyRelationRepository; +import de.tum.cit.aet.artemis.atlas.repository.CompetencyRepository; +import de.tum.cit.aet.artemis.atlas.repository.CourseCompetencyRepository; +import de.tum.cit.aet.artemis.atlas.repository.KnowledgeAreaRepository; +import de.tum.cit.aet.artemis.atlas.repository.PrerequisiteRepository; +import de.tum.cit.aet.artemis.atlas.repository.ScienceSettingRepository; +import de.tum.cit.aet.artemis.atlas.repository.SourceRepository; +import de.tum.cit.aet.artemis.atlas.repository.StandardizedCompetencyRepository; +import de.tum.cit.aet.artemis.atlas.service.competency.CompetencyProgressService; +import de.tum.cit.aet.artemis.atlas.test_repository.CompetencyProgressTestRepository; +import de.tum.cit.aet.artemis.atlas.test_repository.LearningPathTestRepository; +import de.tum.cit.aet.artemis.atlas.test_repository.ScienceEventTestRepository; +import de.tum.cit.aet.artemis.core.service.feature.FeatureToggleService; +import de.tum.cit.aet.artemis.core.util.PageableSearchUtilService; +import de.tum.cit.aet.artemis.exercise.participation.util.ParticipationUtilService; +import de.tum.cit.aet.artemis.exercise.service.ParticipationService; +import de.tum.cit.aet.artemis.exercise.team.TeamUtilService; +import de.tum.cit.aet.artemis.exercise.test_repository.SubmissionTestRepository; +import de.tum.cit.aet.artemis.lecture.repository.AttachmentUnitRepository; +import de.tum.cit.aet.artemis.lecture.repository.ExerciseUnitRepository; +import de.tum.cit.aet.artemis.lecture.repository.LectureRepository; +import de.tum.cit.aet.artemis.lecture.repository.LectureUnitRepository; +import de.tum.cit.aet.artemis.lecture.repository.TextUnitRepository; +import de.tum.cit.aet.artemis.lecture.service.LectureUnitService; +import de.tum.cit.aet.artemis.lecture.util.LectureUtilService; +import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseBuildConfigRepository; +import de.tum.cit.aet.artemis.programming.test_repository.ProgrammingExerciseTestRepository; +import de.tum.cit.aet.artemis.shared.base.AbstractSpringIntegrationIndependentTest; +import de.tum.cit.aet.artemis.text.util.TextExerciseUtilService; + +public abstract class AbstractAtlasIntegrationTest extends AbstractSpringIntegrationIndependentTest { + + // Repositories + @Autowired + protected CompetencyRepository competencyRepository; + + @Autowired + protected CourseCompetencyRepository courseCompetencyRepository; + + @Autowired + protected CompetencyRelationRepository competencyRelationRepository; + + @Autowired + protected CompetencyProgressTestRepository competencyProgressRepository; + + @Autowired + protected KnowledgeAreaRepository knowledgeAreaRepository; + + @Autowired + protected StandardizedCompetencyRepository standardizedCompetencyRepository; + + @Autowired + protected SourceRepository sourceRepository; + + @Autowired + protected LearningPathTestRepository learningPathRepository; + + @Autowired + protected ScienceSettingRepository scienceSettingRepository; + + @Autowired + protected ScienceEventTestRepository scienceEventRepository; + + @Autowired + protected PrerequisiteRepository prerequisiteRepository; + + @Autowired + protected CompetencyJolRepository competencyJolRepository; + + // External Repositories + @Autowired + protected LectureRepository lectureRepository; + + @Autowired + protected LectureUnitRepository lectureUnitRepository; + + @Autowired + protected GradingCriterionRepository gradingCriterionRepository; + + @Autowired + protected TextUnitRepository textUnitRepository; + + @Autowired + protected AttachmentUnitRepository attachmentUnitRepository; + + @Autowired + protected ExerciseUnitRepository exerciseUnitRepository; + + @Autowired + protected SubmissionTestRepository submissionRepository; + + @Autowired + protected ProgrammingExerciseTestRepository programmingExerciseRepository; + + @Autowired + protected ProgrammingExerciseBuildConfigRepository programmingExerciseBuildConfigRepository; + + // Services + + @Autowired + protected CompetencyProgressService competencyProgressService; + + @Autowired + protected FeatureToggleService featureToggleService; + + // External Services + @Autowired + protected LectureUnitService lectureUnitService; + + @Autowired + protected ParticipationService participationService; + + // Util Services + @Autowired + protected CompetencyProgressUtilService competencyProgressUtilService; + + @Autowired + protected CompetencyUtilService competencyUtilService; + + @Autowired + protected PrerequisiteUtilService prerequisiteUtilService; + + @Autowired + protected StandardizedCompetencyUtilService standardizedCompetencyUtilService; + + @Autowired + protected LearningPathUtilService learningPathUtilService; + + // External Util Services + @Autowired + protected PageableSearchUtilService pageableSearchUtilService; + + @Autowired + protected TextExerciseUtilService textExerciseUtilService; + + @Autowired + protected LectureUtilService lectureUtilService; + + @Autowired + protected StudentScoreUtilService studentScoreUtilService; + + @Autowired + protected ParticipationUtilService participationUtilService; + + @Autowired + protected TeamUtilService teamUtilService; +} diff --git a/src/test/java/de/tum/cit/aet/artemis/atlas/architecture/AtlasTestArchitectureTest.java b/src/test/java/de/tum/cit/aet/artemis/atlas/architecture/AtlasTestArchitectureTest.java new file mode 100644 index 000000000000..ae9fb5afc2cc --- /dev/null +++ b/src/test/java/de/tum/cit/aet/artemis/atlas/architecture/AtlasTestArchitectureTest.java @@ -0,0 +1,17 @@ +package de.tum.cit.aet.artemis.atlas.architecture; + +import de.tum.cit.aet.artemis.atlas.AbstractAtlasIntegrationTest; +import de.tum.cit.aet.artemis.shared.architecture.AbstractModuleTestArchitectureTest; + +class AtlasTestArchitectureTest extends AbstractModuleTestArchitectureTest { + + @Override + protected String getModulePackageName() { + return "atlas"; + } + + @Override + protected Class getAbstractModuleIntegrationTestClass() { + return AbstractAtlasIntegrationTest.class; + } +} diff --git a/src/test/java/de/tum/cit/aet/artemis/atlas/architecture/TestArchitectureTest.java b/src/test/java/de/tum/cit/aet/artemis/atlas/architecture/TestArchitectureTest.java new file mode 100644 index 000000000000..f806880d2c53 --- /dev/null +++ b/src/test/java/de/tum/cit/aet/artemis/atlas/architecture/TestArchitectureTest.java @@ -0,0 +1,26 @@ +package de.tum.cit.aet.artemis.atlas.architecture; + +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noMembers; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import de.tum.cit.aet.artemis.atlas.AbstractAtlasIntegrationTest; +import de.tum.cit.aet.artemis.shared.architecture.AbstractArchitectureTest; + +class TestArchitectureTest extends AbstractArchitectureTest { + + @Test + void integrationTestsShouldExtendAbstractAtlasIntegrationTest() { + classes().that().resideInAPackage(ARTEMIS_PACKAGE + ".atlas").and().haveSimpleNameEndingWith("IntegrationTest").should().beAssignableTo(AbstractAtlasIntegrationTest.class) + .because("All integration tests should extend AbstractAtlasIntegrationTest").check(testClasses); + } + + @Test + void integrationTestsShouldNotAutowireMembers() { + noMembers().that().areAnnotatedWith(Autowired.class).should().beDeclaredInClassesThat().areAssignableTo(AbstractAtlasIntegrationTest.class).andShould() + .notBeDeclaredIn(AbstractAtlasIntegrationTest.class) + .because("Integration tests should not autowire members in any class that inherits from AbstractAtlasIntegrationTest").check(testClasses); + } +} diff --git a/src/test/java/de/tum/cit/aet/artemis/atlas/competency/AbstractCompetencyPrerequisiteIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/atlas/competency/AbstractCompetencyPrerequisiteIntegrationTest.java index ca8b71398771..7da0846fcec5 100644 --- a/src/test/java/de/tum/cit/aet/artemis/atlas/competency/AbstractCompetencyPrerequisiteIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/atlas/competency/AbstractCompetencyPrerequisiteIntegrationTest.java @@ -15,12 +15,9 @@ import java.util.function.Function; import java.util.stream.Collectors; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; -import de.tum.cit.aet.artemis.atlas.competency.util.CompetencyProgressUtilService; -import de.tum.cit.aet.artemis.atlas.competency.util.PrerequisiteUtilService; -import de.tum.cit.aet.artemis.atlas.competency.util.StandardizedCompetencyUtilService; +import de.tum.cit.aet.artemis.atlas.AbstractAtlasIntegrationTest; import de.tum.cit.aet.artemis.atlas.domain.competency.CompetencyRelation; import de.tum.cit.aet.artemis.atlas.domain.competency.CompetencyTaxonomy; import de.tum.cit.aet.artemis.atlas.domain.competency.CourseCompetency; @@ -29,9 +26,6 @@ import de.tum.cit.aet.artemis.atlas.dto.CompetencyImportOptionsDTO; import de.tum.cit.aet.artemis.atlas.dto.CompetencyImportResponseDTO; import de.tum.cit.aet.artemis.atlas.dto.CompetencyWithTailRelationDTO; -import de.tum.cit.aet.artemis.atlas.repository.CompetencyRelationRepository; -import de.tum.cit.aet.artemis.atlas.repository.CourseCompetencyRepository; -import de.tum.cit.aet.artemis.atlas.repository.PrerequisiteRepository; import de.tum.cit.aet.artemis.core.domain.Course; import de.tum.cit.aet.artemis.core.domain.DomainObject; import de.tum.cit.aet.artemis.core.domain.User; @@ -43,56 +37,13 @@ import de.tum.cit.aet.artemis.lecture.domain.Lecture; import de.tum.cit.aet.artemis.lecture.domain.LectureUnit; import de.tum.cit.aet.artemis.lecture.domain.TextUnit; -import de.tum.cit.aet.artemis.lecture.repository.AttachmentUnitRepository; -import de.tum.cit.aet.artemis.lecture.repository.ExerciseUnitRepository; -import de.tum.cit.aet.artemis.lecture.repository.LectureRepository; -import de.tum.cit.aet.artemis.lecture.repository.LectureUnitRepository; -import de.tum.cit.aet.artemis.lecture.repository.TextUnitRepository; -import de.tum.cit.aet.artemis.lecture.util.LectureUtilService; import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise; import de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage; import de.tum.cit.aet.artemis.programming.util.ProgrammingExerciseFactory; -import de.tum.cit.aet.artemis.shared.base.AbstractSpringIntegrationLocalCILocalVCTest; import de.tum.cit.aet.artemis.text.domain.TextExercise; import de.tum.cit.aet.artemis.text.util.TextExerciseFactory; -abstract class AbstractCompetencyPrerequisiteIntegrationTest extends AbstractSpringIntegrationLocalCILocalVCTest { - - @Autowired - protected LectureRepository lectureRepository; - - @Autowired - protected TextUnitRepository textUnitRepository; - - @Autowired - protected AttachmentUnitRepository attachmentUnitRepository; - - @Autowired - protected ExerciseUnitRepository exerciseUnitRepository; - - @Autowired - protected CompetencyRelationRepository competencyRelationRepository; - - @Autowired - protected PrerequisiteRepository prerequisiteRepository; - - @Autowired - protected LectureUnitRepository lectureUnitRepository; - - @Autowired - protected PrerequisiteUtilService prerequisiteUtilService; - - @Autowired - protected CompetencyProgressUtilService competencyProgressUtilService; - - @Autowired - protected LectureUtilService lectureUtilService; - - @Autowired - protected StandardizedCompetencyUtilService standardizedCompetencyUtilService; - - @Autowired - protected CourseCompetencyRepository courseCompetencyRepository; +abstract class AbstractCompetencyPrerequisiteIntegrationTest extends AbstractAtlasIntegrationTest { protected Course course; diff --git a/src/test/java/de/tum/cit/aet/artemis/atlas/competency/CompetencyIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/atlas/competency/CompetencyIntegrationTest.java index 0212fe2a621c..9f05ed1eb498 100644 --- a/src/test/java/de/tum/cit/aet/artemis/atlas/competency/CompetencyIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/atlas/competency/CompetencyIntegrationTest.java @@ -8,11 +8,9 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.security.test.context.support.WithMockUser; -import de.tum.cit.aet.artemis.atlas.competency.util.CompetencyUtilService; import de.tum.cit.aet.artemis.atlas.domain.competency.Competency; import de.tum.cit.aet.artemis.atlas.domain.competency.CourseCompetency; import de.tum.cit.aet.artemis.atlas.dto.CompetencyImportOptionsDTO; @@ -24,9 +22,6 @@ class CompetencyIntegrationTest extends AbstractCompetencyPrerequisiteIntegratio private static final String TEST_PREFIX = "competencyintegrationtest"; - @Autowired - private CompetencyUtilService competencyUtilService; - @BeforeEach void setupTestScenario() { super.setupTestScenario(TEST_PREFIX, competencyUtilService::createCompetency); diff --git a/src/test/java/de/tum/cit/aet/artemis/atlas/competency/CompetencyJolIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/atlas/competency/CompetencyJolIntegrationTest.java index 2df138a71271..214b743f5367 100644 --- a/src/test/java/de/tum/cit/aet/artemis/atlas/competency/CompetencyJolIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/atlas/competency/CompetencyJolIntegrationTest.java @@ -9,32 +9,19 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.security.test.context.support.WithMockUser; -import de.tum.cit.aet.artemis.atlas.competency.util.CompetencyProgressUtilService; -import de.tum.cit.aet.artemis.atlas.competency.util.CompetencyUtilService; +import de.tum.cit.aet.artemis.atlas.AbstractAtlasIntegrationTest; import de.tum.cit.aet.artemis.atlas.domain.competency.Competency; import de.tum.cit.aet.artemis.atlas.domain.competency.CompetencyProgress; import de.tum.cit.aet.artemis.atlas.dto.CompetencyJolPairDTO; -import de.tum.cit.aet.artemis.atlas.repository.CompetencyJolRepository; import de.tum.cit.aet.artemis.core.domain.User; -import de.tum.cit.aet.artemis.shared.base.AbstractSpringIntegrationIndependentTest; -class CompetencyJolIntegrationTest extends AbstractSpringIntegrationIndependentTest { +class CompetencyJolIntegrationTest extends AbstractAtlasIntegrationTest { private static final String TEST_PREFIX = "competencyjolintegrationtest"; - @Autowired - private CompetencyUtilService competencyUtilService; - - @Autowired - private CompetencyProgressUtilService competencyProgressUtilService; - - @Autowired - private CompetencyJolRepository competencyJOLRepository; - private final Competency[] competency = new Competency[3]; private CompetencyProgress competencyProgress; @@ -63,7 +50,7 @@ void setup() { @AfterEach void tearDown() { - competencyJOLRepository.deleteAll(); + competencyJolRepository.deleteAll(); } @Nested @@ -100,7 +87,7 @@ void shouldReturnForbiddenForStudentNotInCourse() throws Exception { void shouldCreateJOL() throws Exception { short jolValue = 3; sendRequest(competency[0].getId(), jolValue, HttpStatus.OK); - final var jol = competencyJOLRepository.findLatestByCompetencyIdAndUserId(competency[0].getId(), student.getId()); + final var jol = competencyJolRepository.findLatestByCompetencyIdAndUserId(competency[0].getId(), student.getId()); assertThat(jol).isPresent(); assertThat(jol.get().getValue()).isEqualTo(jolValue); assertThat(jol.get().getCompetencyConfidence()).isEqualTo(competencyProgress.getConfidence()); @@ -113,7 +100,7 @@ void shouldUpdateJOL() throws Exception { competencyUtilService.createJol(competency[0], student, (short) 123, ZonedDateTime.now().minusDays(1), 0.0, 0.0); short jolValue = 3; sendRequest(competency[0].getId(), jolValue, HttpStatus.OK); - final var jol = competencyJOLRepository.findLatestByCompetencyIdAndUserId(competency[0].getId(), student.getId()); + final var jol = competencyJolRepository.findLatestByCompetencyIdAndUserId(competency[0].getId(), student.getId()); assertThat(jol).isPresent(); assertThat(jol.get().getValue()).isEqualTo(jolValue); assertThat(jol.get().getCompetencyConfidence()).isEqualTo(competencyProgress.getConfidence()); diff --git a/src/test/java/de/tum/cit/aet/artemis/atlas/competency/CourseCompetencyIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/atlas/competency/CourseCompetencyIntegrationTest.java index 27db1a95afeb..0c93a02f7277 100644 --- a/src/test/java/de/tum/cit/aet/artemis/atlas/competency/CourseCompetencyIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/atlas/competency/CourseCompetencyIntegrationTest.java @@ -14,7 +14,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.security.test.context.support.WithMockUser; diff --git a/src/test/java/de/tum/cit/aet/artemis/atlas/competency/PrerequisiteIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/atlas/competency/PrerequisiteIntegrationTest.java index 98d384830e98..b7023e0bb5fd 100644 --- a/src/test/java/de/tum/cit/aet/artemis/atlas/competency/PrerequisiteIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/atlas/competency/PrerequisiteIntegrationTest.java @@ -8,11 +8,9 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.security.test.context.support.WithMockUser; -import de.tum.cit.aet.artemis.atlas.competency.util.PrerequisiteUtilService; import de.tum.cit.aet.artemis.atlas.domain.competency.CourseCompetency; import de.tum.cit.aet.artemis.atlas.domain.competency.Prerequisite; import de.tum.cit.aet.artemis.atlas.dto.CompetencyImportOptionsDTO; @@ -24,9 +22,6 @@ class PrerequisiteIntegrationTest extends AbstractCompetencyPrerequisiteIntegrat private static final String TEST_PREFIX = "prerequisiteintegrationtest"; - @Autowired - private PrerequisiteUtilService prerequisiteUtilService; - @BeforeEach void setupTestScenario() { super.setupTestScenario(TEST_PREFIX, prerequisiteUtilService::createPrerequisite); diff --git a/src/test/java/de/tum/cit/aet/artemis/atlas/competency/StandardizedCompetencyIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/atlas/competency/StandardizedCompetencyIntegrationTest.java index 09c050bce269..48ba9e4a1d31 100644 --- a/src/test/java/de/tum/cit/aet/artemis/atlas/competency/StandardizedCompetencyIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/atlas/competency/StandardizedCompetencyIntegrationTest.java @@ -12,11 +12,11 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Sort; import org.springframework.http.HttpStatus; import org.springframework.security.test.context.support.WithMockUser; +import de.tum.cit.aet.artemis.atlas.AbstractAtlasIntegrationTest; import de.tum.cit.aet.artemis.atlas.competency.util.StandardizedCompetencyUtilService; import de.tum.cit.aet.artemis.atlas.domain.competency.Competency; import de.tum.cit.aet.artemis.atlas.domain.competency.CompetencyTaxonomy; @@ -29,13 +29,8 @@ import de.tum.cit.aet.artemis.atlas.dto.standardizedCompetency.StandardizedCompetencyCatalogDTO; import de.tum.cit.aet.artemis.atlas.dto.standardizedCompetency.StandardizedCompetencyRequestDTO; import de.tum.cit.aet.artemis.atlas.dto.standardizedCompetency.StandardizedCompetencyResultDTO; -import de.tum.cit.aet.artemis.atlas.repository.CompetencyRepository; -import de.tum.cit.aet.artemis.atlas.repository.KnowledgeAreaRepository; -import de.tum.cit.aet.artemis.atlas.repository.SourceRepository; -import de.tum.cit.aet.artemis.atlas.repository.StandardizedCompetencyRepository; -import de.tum.cit.aet.artemis.shared.base.AbstractSpringIntegrationIndependentTest; -class StandardizedCompetencyIntegrationTest extends AbstractSpringIntegrationIndependentTest { +class StandardizedCompetencyIntegrationTest extends AbstractAtlasIntegrationTest { private static final String TEST_PREFIX = "stdcompetencyintegrationtest"; @@ -47,21 +42,6 @@ class StandardizedCompetencyIntegrationTest extends AbstractSpringIntegrationInd private Source source; - @Autowired - private KnowledgeAreaRepository knowledgeAreaRepository; - - @Autowired - private StandardizedCompetencyRepository standardizedCompetencyRepository; - - @Autowired - private SourceRepository sourceRepository; - - @Autowired - private StandardizedCompetencyUtilService standardizedCompetencyUtilService; - - @Autowired - private CompetencyRepository competencyRepository; - @BeforeEach void setupTestScenario() { userUtilService.addUsers(TEST_PREFIX, 1, 1, 1, 1); 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 5d8033d0ed50..ae7c283365bf 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 @@ -20,14 +20,11 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.MethodSource; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.security.test.context.support.WithMockUser; import de.tum.cit.aet.artemis.assessment.domain.GradingCriterion; -import de.tum.cit.aet.artemis.assessment.repository.GradingCriterionRepository; -import de.tum.cit.aet.artemis.assessment.util.StudentScoreUtilService; -import de.tum.cit.aet.artemis.atlas.competency.util.CompetencyUtilService; +import de.tum.cit.aet.artemis.atlas.AbstractAtlasIntegrationTest; import de.tum.cit.aet.artemis.atlas.domain.LearningObject; import de.tum.cit.aet.artemis.atlas.domain.competency.Competency; import de.tum.cit.aet.artemis.atlas.domain.competency.CompetencyRelation; @@ -38,79 +35,27 @@ import de.tum.cit.aet.artemis.atlas.dto.CompetencyNameDTO; import de.tum.cit.aet.artemis.atlas.dto.CompetencyWithTailRelationDTO; import de.tum.cit.aet.artemis.atlas.dto.LearningPathCompetencyGraphDTO; +import de.tum.cit.aet.artemis.atlas.dto.LearningPathDTO; import de.tum.cit.aet.artemis.atlas.dto.LearningPathHealthDTO; import de.tum.cit.aet.artemis.atlas.dto.LearningPathInformationDTO; import de.tum.cit.aet.artemis.atlas.dto.LearningPathNavigationDTO; import de.tum.cit.aet.artemis.atlas.dto.LearningPathNavigationObjectDTO; import de.tum.cit.aet.artemis.atlas.dto.LearningPathNavigationOverviewDTO; import de.tum.cit.aet.artemis.atlas.dto.NgxLearningPathDTO; -import de.tum.cit.aet.artemis.atlas.learningpath.util.LearningPathUtilService; -import de.tum.cit.aet.artemis.atlas.repository.CompetencyRelationRepository; import de.tum.cit.aet.artemis.atlas.service.competency.CompetencyProgressService; -import de.tum.cit.aet.artemis.atlas.test_repository.CompetencyProgressTestRepository; -import de.tum.cit.aet.artemis.atlas.test_repository.LearningPathTestRepository; import de.tum.cit.aet.artemis.atlas.web.LearningPathResource; import de.tum.cit.aet.artemis.core.domain.Course; import de.tum.cit.aet.artemis.core.domain.User; -import de.tum.cit.aet.artemis.core.util.PageableSearchUtilService; import de.tum.cit.aet.artemis.exercise.domain.Exercise; import de.tum.cit.aet.artemis.lecture.domain.Lecture; import de.tum.cit.aet.artemis.lecture.domain.LectureUnit; import de.tum.cit.aet.artemis.lecture.domain.TextUnit; -import de.tum.cit.aet.artemis.lecture.repository.LectureRepository; -import de.tum.cit.aet.artemis.lecture.repository.LectureUnitRepository; -import de.tum.cit.aet.artemis.lecture.service.LectureUnitService; -import de.tum.cit.aet.artemis.lecture.util.LectureUtilService; -import de.tum.cit.aet.artemis.shared.base.AbstractSpringIntegrationIndependentTest; import de.tum.cit.aet.artemis.text.domain.TextExercise; -import de.tum.cit.aet.artemis.text.util.TextExerciseUtilService; -class LearningPathIntegrationTest extends AbstractSpringIntegrationIndependentTest { +class LearningPathIntegrationTest extends AbstractAtlasIntegrationTest { private static final String TEST_PREFIX = "learningpathintegration"; - @Autowired - private CompetencyUtilService competencyUtilService; - - @Autowired - private PageableSearchUtilService pageableSearchUtilService; - - @Autowired - private LearningPathTestRepository learningPathRepository; - - @Autowired - private TextExerciseUtilService textExerciseUtilService; - - @Autowired - private LectureRepository lectureRepository; - - @Autowired - private LectureUtilService lectureUtilService; - - @Autowired - private GradingCriterionRepository gradingCriterionRepository; - - @Autowired - private LectureUnitService lectureUnitService; - - @Autowired - private CompetencyProgressService competencyProgressService; - - @Autowired - private LearningPathUtilService learningPathUtilService; - - @Autowired - private CompetencyProgressTestRepository competencyProgressRepository; - - @Autowired - private CompetencyRelationRepository competencyRelationRepository; - - @Autowired - private StudentScoreUtilService studentScoreUtilService; - - @Autowired - private LectureUnitRepository lectureUnitRepository; - private Course course; private Competency[] competencies; @@ -123,7 +68,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"; @@ -160,7 +107,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); @@ -221,10 +168,10 @@ 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); + request.get("/api/courses/" + course.getId() + "/learning-path/me", HttpStatus.BAD_REQUEST, LearningPathDTO.class); } @Test @@ -320,7 +267,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(); @@ -330,7 +277,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); @@ -364,7 +311,7 @@ void addCompetencyToLearningPaths(Function competencyProgressService.updateCompetencyProgress(competency.getId(), student)); @@ -493,10 +440,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); @@ -508,7 +455,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); } @@ -521,10 +468,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); } @@ -572,33 +519,33 @@ 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); } @Nested - class GetLearningPathId { + class GetLearningPath { @Test - @WithMockUser(username = STUDENT_OF_COURSE, roles = "USER") - void shouldReturnExistingId() throws Exception { + @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-id", HttpStatus.OK, Long.class); - assertThat(result).isEqualTo(learningPath.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-id", HttpStatus.NOT_FOUND, Long.class); + request.get("/api/courses/" + course.getId() + "/learning-path/me", HttpStatus.NOT_FOUND, LearningPathDTO.class); } } @@ -606,42 +553,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, Long.class, HttpStatus.BAD_REQUEST); + 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, Long.class, HttpStatus.BAD_REQUEST); + request.postWithResponseBody("/api/courses/" + course.getId() + "/learning-path", null, LearningPathDTO.class, HttpStatus.CONFLICT); } @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, Long.class, HttpStatus.CREATED); + 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(); } } + @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 = userTestRepository.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 = userTestRepository.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.CONFLICT); + } + + @Test + @WithMockUser(username = STUDENT1_OF_COURSE, roles = "USER") + void shouldStartLearningPath() throws Exception { + final var student = userTestRepository.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(); } @@ -653,10 +637,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)); @@ -668,10 +652,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()); @@ -693,10 +677,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()); @@ -741,10 +725,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); @@ -758,16 +742,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); @@ -781,26 +765,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); @@ -814,10 +798,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)); @@ -848,7 +832,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); @@ -859,7 +843,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); diff --git a/src/test/java/de/tum/cit/aet/artemis/atlas/science/ScienceIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/atlas/science/ScienceIntegrationTest.java index a4ac82d52470..2ab78b575e78 100644 --- a/src/test/java/de/tum/cit/aet/artemis/atlas/science/ScienceIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/atlas/science/ScienceIntegrationTest.java @@ -6,25 +6,16 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.test.context.support.WithMockUser; +import de.tum.cit.aet.artemis.atlas.AbstractAtlasIntegrationTest; import de.tum.cit.aet.artemis.atlas.domain.science.ScienceEventType; import de.tum.cit.aet.artemis.atlas.dto.ScienceEventDTO; -import de.tum.cit.aet.artemis.atlas.test_repository.ScienceEventTestRepository; import de.tum.cit.aet.artemis.core.service.feature.Feature; -import de.tum.cit.aet.artemis.core.service.feature.FeatureToggleService; -import de.tum.cit.aet.artemis.shared.base.AbstractSpringIntegrationIndependentTest; -class ScienceIntegrationTest extends AbstractSpringIntegrationIndependentTest { - - @Autowired - private ScienceEventTestRepository scienceEventRepository; - - @Autowired - private FeatureToggleService featureToggleService; +class ScienceIntegrationTest extends AbstractAtlasIntegrationTest { @BeforeEach void enableFeatureToggle() { diff --git a/src/test/java/de/tum/cit/aet/artemis/atlas/science/ScienceSettingsIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/atlas/science/ScienceSettingsIntegrationTest.java index c1657bd2a391..bf186724f0f3 100644 --- a/src/test/java/de/tum/cit/aet/artemis/atlas/science/ScienceSettingsIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/atlas/science/ScienceSettingsIntegrationTest.java @@ -7,22 +7,17 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.security.test.context.support.WithMockUser; +import de.tum.cit.aet.artemis.atlas.AbstractAtlasIntegrationTest; import de.tum.cit.aet.artemis.atlas.domain.science.ScienceSetting; -import de.tum.cit.aet.artemis.atlas.repository.ScienceSettingRepository; import de.tum.cit.aet.artemis.core.domain.User; -import de.tum.cit.aet.artemis.shared.base.AbstractSpringIntegrationIndependentTest; -class ScienceSettingsIntegrationTest extends AbstractSpringIntegrationIndependentTest { +class ScienceSettingsIntegrationTest extends AbstractAtlasIntegrationTest { private static final String TEST_PREFIX = "sciencesettingsintegration"; - @Autowired - private ScienceSettingRepository scienceSettingRepository; - private ScienceSetting settingA; private ScienceSetting settingB; diff --git a/src/test/java/de/tum/cit/aet/artemis/core/management/ManagementResourceIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/core/management/ManagementResourceIntegrationTest.java index af2b1996a62f..02c92f6ea6f5 100644 --- a/src/test/java/de/tum/cit/aet/artemis/core/management/ManagementResourceIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/core/management/ManagementResourceIntegrationTest.java @@ -102,7 +102,7 @@ void toggleFeatures() throws Exception { // Try to access 5 different endpoints with programming feature toggle enabled request.put("/api/exercises/" + programmingExercise1.getId() + "/resume-programming-participation/" + participation.getId(), null, HttpStatus.OK); - request.put("/api/participations/" + participation.getId() + "/cleanupBuildPlan", null, HttpStatus.OK); + request.put("/api/participations/" + participation.getId() + "/cleanup-build-plan", null, HttpStatus.OK); request.postWithoutLocation("/api/programming-submissions/" + participation.getId() + "/trigger-failed-build", null, HttpStatus.OK, null); programmingExercise2.setBuildConfig(programmingExerciseBuildConfigRepository.save(programmingExercise2.getBuildConfig())); programmingExercise2 = programmingExerciseRepository.save(programmingExercise2); @@ -116,7 +116,7 @@ void toggleFeatures() throws Exception { // Try to access 5 different endpoints with programming feature toggle disabled request.put("/api/exercises/" + programmingExercise1.getId() + "/resume-programming-participation/" + participation.getId(), null, HttpStatus.FORBIDDEN); - request.put("/api/participations/" + participation.getId() + "/cleanupBuildPlan", null, HttpStatus.FORBIDDEN); + request.put("/api/participations/" + participation.getId() + "/cleanup-build-plan", null, HttpStatus.FORBIDDEN); request.postWithoutLocation("/api/programming-submissions/" + participation.getId() + "/trigger-failed-build", null, HttpStatus.FORBIDDEN, null); programmingExercise2.setBuildConfig(programmingExerciseBuildConfigRepository.save(programmingExercise2.getBuildConfig())); programmingExercise2 = programmingExerciseRepository.save(programmingExercise2); diff --git a/src/test/java/de/tum/cit/aet/artemis/core/util/CourseTestService.java b/src/test/java/de/tum/cit/aet/artemis/core/util/CourseTestService.java index a66e6d5adc6a..c3c68e44b5e5 100644 --- a/src/test/java/de/tum/cit/aet/artemis/core/util/CourseTestService.java +++ b/src/test/java/de/tum/cit/aet/artemis/core/util/CourseTestService.java @@ -1954,7 +1954,7 @@ public void testGetLockedSubmissionsForCourseAsTutor() throws Exception { Course course = modelingExerciseUtilService.addCourseWithDifferentModelingExercises(); ModelingExercise classExercise = exerciseUtilService.findModelingExerciseWithTitle(course.getExercises(), "ClassDiagram"); - List lockedSubmissions = request.getList("/api/courses/" + course.getId() + "/lockedSubmissions", HttpStatus.OK, Submission.class); + List lockedSubmissions = request.getList("/api/courses/" + course.getId() + "/locked-submissions", HttpStatus.OK, Submission.class); assertThat(lockedSubmissions).as("Locked Submissions is not null").isNotNull(); assertThat(lockedSubmissions).as("Locked Submissions length is 0").isEmpty(); @@ -1969,14 +1969,14 @@ public void testGetLockedSubmissionsForCourseAsTutor() throws Exception { submission = ParticipationFactory.generateModelingSubmission(validModel, true); modelingExerciseUtilService.addModelingSubmissionWithResultAndAssessor(classExercise, submission, userPrefix + "student3", userPrefix + "tutor1"); - lockedSubmissions = request.getList("/api/courses/" + course.getId() + "/lockedSubmissions", HttpStatus.OK, Submission.class); + lockedSubmissions = request.getList("/api/courses/" + course.getId() + "/locked-submissions", HttpStatus.OK, Submission.class); assertThat(lockedSubmissions).as("Locked Submissions is not null").isNotNull(); assertThat(lockedSubmissions).as("Locked Submissions length is 3").hasSize(3); } // Test public void testGetLockedSubmissionsForCourseAsStudent() throws Exception { - List lockedSubmissions = request.getList("/api/courses/1/lockedSubmissions", HttpStatus.FORBIDDEN, Submission.class); + List lockedSubmissions = request.getList("/api/courses/1/locked-submissions", HttpStatus.FORBIDDEN, Submission.class); assertThat(lockedSubmissions).as("Locked Submissions is null").isNull(); } @@ -3338,7 +3338,7 @@ public void testEditCourseRemoveExistingIcon() throws Exception { } private String getUpdateOnlineCourseConfigurationPath(String courseId) { - return "/api/courses/" + courseId + "/onlineCourseConfiguration"; + return "/api/courses/" + courseId + "/online-course-configuration"; } // Test diff --git a/src/test/java/de/tum/cit/aet/artemis/exam/ExamParticipationIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/exam/ExamParticipationIntegrationTest.java index 41cc9016ccac..0483663734ad 100644 --- a/src/test/java/de/tum/cit/aet/artemis/exam/ExamParticipationIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/exam/ExamParticipationIntegrationTest.java @@ -499,7 +499,7 @@ void testGetStatsForExamAssessmentDashboard(int numberOfCorrectionRounds) throws return; } - var lockedSubmissions = request.get("/api/courses/" + course.getId() + "/exams/" + exam.getId() + "/lockedSubmissions", HttpStatus.OK, List.class); + var lockedSubmissions = request.get("/api/courses/" + course.getId() + "/exams/" + exam.getId() + "/locked-submissions", HttpStatus.OK, List.class); assertThat(lockedSubmissions).isEmpty(); log.debug("testGetStatsForExamAssessmentDashboard: step 3 done"); @@ -625,7 +625,7 @@ void testGetStatsForExamAssessmentDashboard(int numberOfCorrectionRounds) throws log.debug("testGetStatsForExamAssessmentDashboard: step 10 done"); userUtilService.changeUser(TEST_PREFIX + "instructor1"); - lockedSubmissions = request.get("/api/courses/" + course.getId() + "/exams/" + exam.getId() + "/lockedSubmissions", HttpStatus.OK, List.class); + lockedSubmissions = request.get("/api/courses/" + course.getId() + "/exams/" + exam.getId() + "/locked-submissions", HttpStatus.OK, List.class); assertThat(lockedSubmissions).hasSize(studentExams.size() * 5); log.debug("testGetStatsForExamAssessmentDashboard: step 11 done"); @@ -656,7 +656,7 @@ void testGetStatsForExamAssessmentDashboard(int numberOfCorrectionRounds) throws log.debug("testGetStatsForExamAssessmentDashboard: step 13 done"); - lockedSubmissions = request.get("/api/courses/" + course.getId() + "/exams/" + exam.getId() + "/lockedSubmissions", HttpStatus.OK, List.class); + lockedSubmissions = request.get("/api/courses/" + course.getId() + "/exams/" + exam.getId() + "/locked-submissions", HttpStatus.OK, List.class); assertThat(lockedSubmissions).isEmpty(); if (numberOfCorrectionRounds == 2) { lockAndAssessForSecondCorrection(exam, course, studentExams, exercisesInExam, numberOfCorrectionRounds); @@ -722,7 +722,7 @@ private void lockAndAssessForSecondCorrection(Exam exam, Course course, List result = request.getList("/api/courses/" + course1.getId() + "/exams/" + exam1.getId() + "/exerciseGroups", HttpStatus.OK, ExerciseGroup.class); + List result = request.getList("/api/courses/" + course1.getId() + "/exams/" + exam1.getId() + "/exercise-groups", HttpStatus.OK, ExerciseGroup.class); verify(examAccessService).checkCourseAndExamAccessForEditorElseThrow(course1.getId(), exam1.getId()); assertThat(result).hasSize(1); assertThat(result.getFirst().getExercises()).hasSize(1); @@ -163,7 +163,7 @@ void testGetExerciseGroupsForExam_asEditor() throws Exception { @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void testDeleteExerciseGroup_asInstructor() throws Exception { - request.delete("/api/courses/" + course1.getId() + "/exams/" + exam1.getId() + "/exerciseGroups/" + exerciseGroup1.getId(), HttpStatus.OK); + request.delete("/api/courses/" + course1.getId() + "/exams/" + exam1.getId() + "/exercise-groups/" + exerciseGroup1.getId(), HttpStatus.OK); verify(examAccessService).checkCourseAndExamAndExerciseGroupAccessElseThrow(Role.INSTRUCTOR, course1.getId(), exam1.getId(), exerciseGroup1); assertThat(textExerciseRepository.findById(textExercise1.getId())).isEmpty(); } @@ -171,7 +171,7 @@ void testDeleteExerciseGroup_asInstructor() throws Exception { @Test @WithMockUser(username = TEST_PREFIX + "editor1", roles = "EDITOR") void testDeleteExerciseGroup_asEditor() throws Exception { - request.delete("/api/courses/" + course1.getId() + "/exams/" + exam1.getId() + "/exerciseGroups/" + exerciseGroup1.getId(), HttpStatus.FORBIDDEN); + request.delete("/api/courses/" + course1.getId() + "/exams/" + exam1.getId() + "/exercise-groups/" + exerciseGroup1.getId(), HttpStatus.FORBIDDEN); } @ParameterizedTest(name = "{displayName} [{index}] {argumentsWithNames}") diff --git a/src/test/java/de/tum/cit/aet/artemis/exercise/participation/ParticipationIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/exercise/participation/ParticipationIntegrationTest.java index 03beb447adad..1162e7b3477d 100644 --- a/src/test/java/de/tum/cit/aet/artemis/exercise/participation/ParticipationIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/exercise/participation/ParticipationIntegrationTest.java @@ -1277,7 +1277,7 @@ void getParticipationWithLatestResult() throws Exception { var result = ParticipationFactory.generateResult(true, 70D); result.participation(participation).setCompletionDate(ZonedDateTime.now().minusHours(2)); resultRepository.save(result); - var actualParticipation = request.get("/api/participations/" + participation.getId() + "/withLatestResult", HttpStatus.OK, StudentParticipation.class); + var actualParticipation = request.get("/api/participations/" + participation.getId() + "/with-latest-result", HttpStatus.OK, StudentParticipation.class); assertThat(actualParticipation).isNotNull(); assertThat(actualParticipation.getResults()).hasSize(1); @@ -1289,7 +1289,7 @@ void getParticipationWithLatestResult() throws Exception { void getParticipationBuildArtifact() throws Exception { var participation = participationUtilService.addStudentParticipationForProgrammingExercise(programmingExercise, TEST_PREFIX + "student1"); doReturn(new ResponseEntity<>(null, HttpStatus.OK)).when(continuousIntegrationService).retrieveLatestArtifact(participation); - request.getNullable("/api/participations/" + participation.getId() + "/buildArtifact", HttpStatus.OK, Object.class); + request.getNullable("/api/participations/" + participation.getId() + "/build-artifact", HttpStatus.OK, Object.class); verify(continuousIntegrationService).retrieveLatestArtifact(participation); } @@ -1316,7 +1316,7 @@ void cleanupBuildPlan(boolean practiceMode, boolean afterDueDate) throws Excepti } jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsServer); mockDeleteBuildPlan(programmingExercise.getProjectKey(), participation.getBuildPlanId(), false); - var actualParticipation = request.putWithResponseBody("/api/participations/" + participation.getId() + "/cleanupBuildPlan", null, Participation.class, HttpStatus.OK); + var actualParticipation = request.putWithResponseBody("/api/participations/" + participation.getId() + "/cleanup-build-plan", null, Participation.class, HttpStatus.OK); assertThat(actualParticipation).isEqualTo(participation); assertThat(actualParticipation.getInitializationState()).isEqualTo(!practiceMode && afterDueDate ? InitializationState.FINISHED : InitializationState.INACTIVE); assertThat(((ProgrammingExerciseStudentParticipation) actualParticipation).getBuildPlanId()).isNull(); diff --git a/src/test/java/de/tum/cit/aet/artemis/lti/AbstractLtiIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/lti/AbstractLtiIntegrationTest.java new file mode 100644 index 000000000000..25ae4e707239 --- /dev/null +++ b/src/test/java/de/tum/cit/aet/artemis/lti/AbstractLtiIntegrationTest.java @@ -0,0 +1,50 @@ +package de.tum.cit.aet.artemis.lti; + +import org.springframework.beans.factory.annotation.Autowired; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import de.tum.cit.aet.artemis.core.util.RequestUtilService; +import de.tum.cit.aet.artemis.exercise.participation.util.ParticipationUtilService; +import de.tum.cit.aet.artemis.exercise.test_repository.SubmissionTestRepository; +import de.tum.cit.aet.artemis.lti.test_repository.OnlineCourseConfigurationTestRepository; +import de.tum.cit.aet.artemis.programming.util.ProgrammingExerciseUtilService; +import de.tum.cit.aet.artemis.quiz.service.QuizExerciseService; +import de.tum.cit.aet.artemis.quiz.service.QuizSubmissionService; +import de.tum.cit.aet.artemis.quiz.test_repository.QuizExerciseTestRepository; +import de.tum.cit.aet.artemis.shared.base.AbstractSpringIntegrationIndependentTest; + +public abstract class AbstractLtiIntegrationTest extends AbstractSpringIntegrationIndependentTest { + + // Repositories + @Autowired + protected OnlineCourseConfigurationTestRepository onlineCourseConfigurationRepository; + + // External Repositories + @Autowired + protected QuizExerciseTestRepository quizExerciseTestRepository; + + @Autowired + protected SubmissionTestRepository submissionRepository; + + // External Services + @Autowired + protected QuizExerciseService quizExerciseService; + + @Autowired + protected QuizSubmissionService quizSubmissionService; + + // External Util Services + @Autowired + protected ProgrammingExerciseUtilService programmingExerciseUtilService; + + @Autowired + protected ParticipationUtilService participationUtilService; + + @Autowired + protected RequestUtilService request; + + // Misc + @Autowired + protected ObjectMapper objectMapper; +} diff --git a/src/test/java/de/tum/cit/aet/artemis/lti/Lti13LaunchIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/lti/Lti13LaunchIntegrationTest.java index cf1b7c35814a..8c861c676c48 100644 --- a/src/test/java/de/tum/cit/aet/artemis/lti/Lti13LaunchIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/lti/Lti13LaunchIntegrationTest.java @@ -21,7 +21,6 @@ import org.springframework.security.test.context.support.WithAnonymousUser; import org.springframework.security.test.context.support.WithMockUser; -import de.tum.cit.aet.artemis.shared.base.AbstractSpringIntegrationIndependentTest; import io.jsonwebtoken.Jwts; /** @@ -35,7 +34,7 @@ * see LTI message general details * see OpenId Connect launch flow */ -class Lti13LaunchIntegrationTest extends AbstractSpringIntegrationIndependentTest { +class Lti13LaunchIntegrationTest extends AbstractLtiIntegrationTest { private static final SecretKey SIGNING_KEY = Jwts.SIG.HS256.key().build(); diff --git a/src/test/java/de/tum/cit/aet/artemis/lti/LtiDeepLinkingIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/lti/LtiDeepLinkingIntegrationTest.java index e1da79c21df6..071b410e7449 100644 --- a/src/test/java/de/tum/cit/aet/artemis/lti/LtiDeepLinkingIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/lti/LtiDeepLinkingIntegrationTest.java @@ -20,7 +20,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.util.LinkedMultiValueMap; @@ -31,17 +30,12 @@ import de.tum.cit.aet.artemis.core.domain.Course; import de.tum.cit.aet.artemis.lti.config.CustomLti13Configurer; import de.tum.cit.aet.artemis.lti.dto.Claims; -import de.tum.cit.aet.artemis.programming.util.ProgrammingExerciseUtilService; -import de.tum.cit.aet.artemis.shared.base.AbstractSpringIntegrationIndependentTest; import io.jsonwebtoken.Jwts; -class LtiDeepLinkingIntegrationTest extends AbstractSpringIntegrationIndependentTest { +class LtiDeepLinkingIntegrationTest extends AbstractLtiIntegrationTest { private static final String TEST_PREFIX = "ltideeplinkingintegrationtest"; - @Autowired - private ProgrammingExerciseUtilService programmingExerciseUtilService; - private Course course; @BeforeEach diff --git a/src/test/java/de/tum/cit/aet/artemis/lti/LtiIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/lti/LtiIntegrationTest.java index aa8fa6d64241..5536be067a6e 100644 --- a/src/test/java/de/tum/cit/aet/artemis/lti/LtiIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/lti/LtiIntegrationTest.java @@ -18,7 +18,6 @@ import org.hibernate.Hibernate; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; @@ -28,19 +27,14 @@ import org.springframework.util.LinkedMultiValueMap; import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; import de.tum.cit.aet.artemis.core.exception.EntityNotFoundException; import de.tum.cit.aet.artemis.lti.domain.LtiPlatformConfiguration; -import de.tum.cit.aet.artemis.shared.base.AbstractSpringIntegrationIndependentTest; -class LtiIntegrationTest extends AbstractSpringIntegrationIndependentTest { +class LtiIntegrationTest extends AbstractLtiIntegrationTest { private static final String TEST_PREFIX = "ltiintegrationtest"; - @Autowired - ObjectMapper objectMapper; - @Test @WithMockUser(username = TEST_PREFIX + "student1", roles = "USER") void dynamicRegistrationFailsAsStudent() throws Exception { diff --git a/src/test/java/de/tum/cit/aet/artemis/lti/LtiQuizIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/lti/LtiQuizIntegrationTest.java index 022dfa6f946d..80ede40edfb2 100644 --- a/src/test/java/de/tum/cit/aet/artemis/lti/LtiQuizIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/lti/LtiQuizIntegrationTest.java @@ -16,7 +16,6 @@ import org.junit.jupiter.api.parallel.Isolated; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -26,50 +25,20 @@ import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import com.fasterxml.jackson.databind.ObjectMapper; - import de.tum.cit.aet.artemis.assessment.domain.AssessmentType; import de.tum.cit.aet.artemis.core.domain.Course; -import de.tum.cit.aet.artemis.core.util.RequestUtilService; import de.tum.cit.aet.artemis.exercise.domain.participation.StudentParticipation; -import de.tum.cit.aet.artemis.exercise.participation.util.ParticipationUtilService; -import de.tum.cit.aet.artemis.exercise.test_repository.SubmissionTestRepository; import de.tum.cit.aet.artemis.quiz.domain.DragAndDropQuestion; import de.tum.cit.aet.artemis.quiz.domain.QuizExercise; import de.tum.cit.aet.artemis.quiz.domain.QuizMode; import de.tum.cit.aet.artemis.quiz.domain.QuizSubmission; -import de.tum.cit.aet.artemis.quiz.service.QuizExerciseService; -import de.tum.cit.aet.artemis.quiz.service.QuizSubmissionService; -import de.tum.cit.aet.artemis.quiz.test_repository.QuizExerciseTestRepository; import de.tum.cit.aet.artemis.quiz.util.QuizExerciseFactory; -import de.tum.cit.aet.artemis.shared.base.AbstractSpringIntegrationIndependentTest; @Isolated -class LtiQuizIntegrationTest extends AbstractSpringIntegrationIndependentTest { +class LtiQuizIntegrationTest extends AbstractLtiIntegrationTest { private static final String TEST_PREFIX = "ltiquizsubmissiontest"; - @Autowired - private QuizExerciseService quizExerciseService; - - @Autowired - private QuizExerciseTestRepository quizExerciseTestRepository; - - @Autowired - private SubmissionTestRepository submissionRepository; - - @Autowired - private ParticipationUtilService participationUtilService; - - @Autowired - private ObjectMapper objectMapper; - - @Autowired - protected RequestUtilService request; - - @Autowired - private QuizSubmissionService quizSubmissionService; - @BeforeEach void init() { doNothing().when(lti13Service).onNewResult(any()); diff --git a/src/test/java/de/tum/cit/aet/artemis/lti/OAuth2JWKSIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/lti/OAuth2JWKSIntegrationTest.java index 86aaf9fc33f9..7523e20af5b5 100644 --- a/src/test/java/de/tum/cit/aet/artemis/lti/OAuth2JWKSIntegrationTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/lti/OAuth2JWKSIntegrationTest.java @@ -3,7 +3,6 @@ import static org.assertj.core.api.Assertions.assertThat; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.security.test.context.support.WithAnonymousUser; @@ -14,16 +13,11 @@ import de.tum.cit.aet.artemis.core.util.CourseFactory; import de.tum.cit.aet.artemis.lti.domain.LtiPlatformConfiguration; import de.tum.cit.aet.artemis.lti.domain.OnlineCourseConfiguration; -import de.tum.cit.aet.artemis.lti.test_repository.OnlineCourseConfigurationTestRepository; -import de.tum.cit.aet.artemis.shared.base.AbstractSpringIntegrationIndependentTest; -class OAuth2JWKSIntegrationTest extends AbstractSpringIntegrationIndependentTest { +class OAuth2JWKSIntegrationTest extends AbstractLtiIntegrationTest { private static final String TEST_PREFIX = "oauth2jwksintegrationtest"; - @Autowired - private OnlineCourseConfigurationTestRepository onlineCourseConfigurationRepository; - @Test @WithAnonymousUser void getKeysetIsPublicAndReturnsJson() throws Exception { diff --git a/src/test/java/de/tum/cit/aet/artemis/lti/architecture/LtiTestArchitectureTest.java b/src/test/java/de/tum/cit/aet/artemis/lti/architecture/LtiTestArchitectureTest.java new file mode 100644 index 000000000000..5ff2636ad04f --- /dev/null +++ b/src/test/java/de/tum/cit/aet/artemis/lti/architecture/LtiTestArchitectureTest.java @@ -0,0 +1,17 @@ +package de.tum.cit.aet.artemis.lti.architecture; + +import de.tum.cit.aet.artemis.lti.AbstractLtiIntegrationTest; +import de.tum.cit.aet.artemis.shared.architecture.AbstractModuleTestArchitectureTest; + +class LtiTestArchitectureTest extends AbstractModuleTestArchitectureTest { + + @Override + protected String getModulePackageName() { + return "lti"; + } + + @Override + protected Class getAbstractModuleIntegrationTestClass() { + return AbstractLtiIntegrationTest.class; + } +} diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/util/ProgrammingExerciseTestService.java b/src/test/java/de/tum/cit/aet/artemis/programming/util/ProgrammingExerciseTestService.java index 1914e2b533f4..63b969fb3954 100644 --- a/src/test/java/de/tum/cit/aet/artemis/programming/util/ProgrammingExerciseTestService.java +++ b/src/test/java/de/tum/cit/aet/artemis/programming/util/ProgrammingExerciseTestService.java @@ -2111,7 +2111,7 @@ public void startProgrammingExerciseStudentRetrieveEmptyArtifactPage() throws Ex mockDelegate.resetMockProvider(); mockDelegate.mockRetrieveArtifacts(participation); - var artifact = request.get(PARTICIPATION_BASE_URL + participation.getId() + "/buildArtifact", HttpStatus.OK, byte[].class); + var artifact = request.get(PARTICIPATION_BASE_URL + participation.getId() + "/build-artifact", HttpStatus.OK, byte[].class); assertThat(participation.getInitializationState()).as("Participation should be initialized").isEqualTo(InitializationState.INITIALIZED); assertThat(artifact).as("No build artifact available for this plan").isEmpty(); diff --git a/src/test/java/de/tum/cit/aet/artemis/shared/architecture/AbstractModuleTestArchitectureTest.java b/src/test/java/de/tum/cit/aet/artemis/shared/architecture/AbstractModuleTestArchitectureTest.java new file mode 100644 index 000000000000..cc4f8c80357c --- /dev/null +++ b/src/test/java/de/tum/cit/aet/artemis/shared/architecture/AbstractModuleTestArchitectureTest.java @@ -0,0 +1,28 @@ +package de.tum.cit.aet.artemis.shared.architecture; + +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noMembers; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +public abstract class AbstractModuleTestArchitectureTest extends AbstractArchitectureTest { + + abstract protected String getModulePackageName(); + + abstract protected Class getAbstractModuleIntegrationTestClass(); + + @Test + void integrationTestsShouldExtendAbstractModuleIntegrationTest() { + classes().that().resideInAPackage(ARTEMIS_PACKAGE + "." + getModulePackageName()).and().haveSimpleNameEndingWith("IntegrationTest").should() + .beAssignableTo(getAbstractModuleIntegrationTestClass()).because("All integration tests should extend %s".formatted(getAbstractModuleIntegrationTestClass())) + .check(testClasses); + } + + @Test + void integrationTestsShouldNotAutowireMembers() { + noMembers().that().areAnnotatedWith(Autowired.class).should().beDeclaredInClassesThat().areAssignableTo(getAbstractModuleIntegrationTestClass()).andShould() + .notBeDeclaredIn(getAbstractModuleIntegrationTestClass()) + .because("Integration tests should not autowire members in any class that inherits from %s".formatted(getAbstractModuleIntegrationTestClass())).check(testClasses); + } +} diff --git a/src/test/java/de/tum/cit/aet/artemis/shared/architecture/ResourceArchitectureTest.java b/src/test/java/de/tum/cit/aet/artemis/shared/architecture/ResourceArchitectureTest.java index a098dbde4535..92ee8cd91c61 100644 --- a/src/test/java/de/tum/cit/aet/artemis/shared/architecture/ResourceArchitectureTest.java +++ b/src/test/java/de/tum/cit/aet/artemis/shared/architecture/ResourceArchitectureTest.java @@ -8,6 +8,7 @@ import java.lang.annotation.Annotation; import java.util.Set; import java.util.function.Consumer; +import java.util.regex.Pattern; import org.junit.jupiter.api.Test; import org.springframework.http.ResponseEntity; @@ -34,6 +35,8 @@ class ResourceArchitectureTest extends AbstractArchitectureTest { + private static final Pattern KEBAB_CASE_PATH_PATTERN = Pattern.compile("^(\\.?[a-z0-9]+(-[a-z0-9]+)*|\\{[^}]+})(/(([a-z0-9]+(-[a-z0-9]+)*|\\{[^}]+})))*(\\.json|/\\*)?$"); + @Test void shouldBeNamedResource() { ArchRule rule = classes().that().areAnnotatedWith(RestController.class).should().haveSimpleNameEndingWith("Resource") @@ -69,6 +72,31 @@ void shouldCorrectlyUseRequestMappingAnnotations() { } } + @Test + void testUseKebabCaseForRestEndpoints() { + for (var annotation : annotationClasses) { + methods().should(useKebabCaseForRestAnnotations(annotation)).check(productionClasses); + } + } + + protected ArchCondition useKebabCaseForRestAnnotations(Class annotationClass) { + return new ArchCondition<>("use kebab case for rest mapping annotations") { + + @Override + public void check(JavaMethod item, ConditionEvents events) { + var restMappingAnnotation = item.getAnnotations().stream() + .filter(annotation -> ((JavaClass) annotation.getType()).getSimpleName().equals(annotationClass.getSimpleName())).findFirst(); + + if (restMappingAnnotation.isPresent()) { + String restURL = ((String[]) restMappingAnnotation.get().tryGetExplicitlyDeclaredProperty("value").get())[0]; + if (!KEBAB_CASE_PATH_PATTERN.matcher(restURL).matches()) { + events.add(violated(item, String.format("\"%s\" violates rule to only use kebab case for REST annotations in %s", restURL, item.getFullName()))); + } + } + } + }; + } + private ArchCondition haveCorrectRequestMappingPathForClasses() { return new ArchCondition<>("correctly use @RequestMapping") { diff --git a/src/test/javascript/spec/component/course/course-management.service.spec.ts b/src/test/javascript/spec/component/course/course-management.service.spec.ts index f98debbcc226..5dd0dd4f48de 100644 --- a/src/test/javascript/spec/component/course/course-management.service.spec.ts +++ b/src/test/javascript/spec/component/course/course-management.service.spec.ts @@ -163,7 +163,7 @@ describe('Course Management Service', () => { .pipe(take(1)) .subscribe((res) => expect(res.body).toEqual(course)); - const req = httpMock.expectOne({ method: 'PUT', url: `${resourceUrl}/1/onlineCourseConfiguration` }); + const req = httpMock.expectOne({ method: 'PUT', url: `${resourceUrl}/1/online-course-configuration` }); req.flush(returnedFromService); tick(); })); @@ -474,7 +474,7 @@ describe('Course Management Service', () => { const submissions = [submission]; returnedFromService = [...submissions]; courseManagementService.findAllLockedSubmissionsOfCourse(course.id!).subscribe((res) => expect(res.body).toEqual(submissions)); - const req = httpMock.expectOne({ method: 'GET', url: `${resourceUrl}/${course.id}/lockedSubmissions` }); + const req = httpMock.expectOne({ method: 'GET', url: `${resourceUrl}/${course.id}/locked-submissions` }); req.flush(returnedFromService); tick(); })); diff --git a/src/test/javascript/spec/component/exam/manage/exam-management.service.spec.ts b/src/test/javascript/spec/component/exam/manage/exam-management.service.spec.ts index 747d87a8a3c5..8ece0517debd 100644 --- a/src/test/javascript/spec/component/exam/manage/exam-management.service.spec.ts +++ b/src/test/javascript/spec/component/exam/manage/exam-management.service.spec.ts @@ -635,7 +635,7 @@ describe('Exam Management Service Tests', () => { // THEN const req = httpMock.expectOne({ method: 'GET', - url: `${service.resourceUrl}/${course.id!}/exams/${mockExam.id!}/lockedSubmissions`, + url: `${service.resourceUrl}/${course.id!}/exams/${mockExam.id!}/locked-submissions`, }); req.flush(mockResponse); tick(); diff --git a/src/test/javascript/spec/component/learning-paths/pages/learning-path-student-page.component.spec.ts b/src/test/javascript/spec/component/learning-paths/pages/learning-path-student-page.component.spec.ts index 6bf9e228b74b..15f0dc14b768 100644 --- a/src/test/javascript/spec/component/learning-paths/pages/learning-path-student-page.component.spec.ts +++ b/src/test/javascript/spec/component/learning-paths/pages/learning-path-student-page.component.spec.ts @@ -13,6 +13,7 @@ import { LearningPathApiService } from 'app/course/learning-paths/services/learn import { AlertService } from 'app/core/util/alert.service'; import { MockAlertService } from '../../../helpers/mocks/service/mock-alert.service'; import { EntityNotFoundError } from 'app/course/learning-paths/exceptions/entity-not-found.error'; +import { LearningPathDTO } from 'app/entities/competency/learning-path.model'; describe('LearningPathStudentPageComponent', () => { let component: LearningPathStudentPageComponent; @@ -20,7 +21,11 @@ describe('LearningPathStudentPageComponent', () => { let learningPathApiService: LearningPathApiService; let alertService: AlertService; - const learningPathId = 1; + const learningPath: LearningPathDTO = { + id: 1, + progress: 0, + startedByStudent: false, + }; const courseId = 2; beforeEach(async () => { @@ -42,6 +47,14 @@ describe('LearningPathStudentPageComponent', () => { }, { provide: TranslateService, useClass: MockTranslateService }, { provide: AlertService, useClass: MockAlertService }, + { + provide: LearningPathApiService, + useValue: { + getLearningPathForCurrentUser: jest.fn().mockResolvedValue(learningPath), + generateLearningPathForCurrentUser: jest.fn().mockResolvedValue(learningPath), + startLearningPathForCurrentUser: jest.fn().mockReturnValue(() => Promise.resolve()), + }, + }, ], }) .overrideComponent(LearningPathStudentPageComponent, { @@ -70,18 +83,23 @@ describe('LearningPathStudentPageComponent', () => { expect(component.courseId()).toBe(courseId); }); - it('should get learning path id', async () => { - const getLearningPathIdSpy = jest.spyOn(learningPathApiService, 'getLearningPathId').mockResolvedValue(learningPathId); + it('should get learning path', async () => { + const getLearningPathIdSpy = jest.spyOn(learningPathApiService, 'getLearningPathForCurrentUser').mockResolvedValue(learningPath); fixture.detectChanges(); await fixture.whenStable(); expect(getLearningPathIdSpy).toHaveBeenCalledWith(courseId); - expect(component.learningPathId()).toEqual(learningPathId); + expect(component.learningPath()).toEqual(learningPath); }); - it('should show navigation on successful load', async () => { - jest.spyOn(learningPathApiService, 'getLearningPathId').mockResolvedValue(learningPathId); + it('should show navigation when learning path has been started', async () => { + const learningPath: LearningPathDTO = { + id: 1, + progress: 0, + startedByStudent: true, + }; + jest.spyOn(learningPathApiService, 'getLearningPathForCurrentUser').mockResolvedValueOnce(learningPath); fixture.detectChanges(); await fixture.whenStable(); @@ -91,8 +109,8 @@ describe('LearningPathStudentPageComponent', () => { expect(navComponent).toBeTruthy(); }); - it('should show error when learning path id could not be loaded', async () => { - const getLearningPathIdSpy = jest.spyOn(learningPathApiService, 'getLearningPathId').mockRejectedValue(new Error()); + it('should show error when learning path could not be loaded', async () => { + const getLearningPathIdSpy = jest.spyOn(learningPathApiService, 'getLearningPathForCurrentUser').mockRejectedValue(new Error()); const alertServiceErrorSpy = jest.spyOn(alertService, 'addAlert'); fixture.detectChanges(); @@ -103,8 +121,8 @@ describe('LearningPathStudentPageComponent', () => { }); it('should set isLoading correctly during learning path load', async () => { - jest.spyOn(learningPathApiService, 'getLearningPathId').mockResolvedValue(learningPathId); - const loadingSpy = jest.spyOn(component.isLearningPathIdLoading, 'set'); + jest.spyOn(learningPathApiService, 'getLearningPathForCurrentUser').mockResolvedValue(learningPath); + const loadingSpy = jest.spyOn(component.isLearningPathLoading, 'set'); fixture.detectChanges(); await fixture.whenStable(); @@ -113,28 +131,34 @@ describe('LearningPathStudentPageComponent', () => { expect(loadingSpy).toHaveBeenNthCalledWith(2, false); }); - it('should generate learning path on click', async () => { - jest.spyOn(learningPathApiService, 'getLearningPathId').mockRejectedValue(new EntityNotFoundError()); - const generateLearningPathSpy = jest.spyOn(learningPathApiService, 'generateLearningPath').mockResolvedValue(learningPathId); + it('should generate learning path on start when not found', async () => { + jest.spyOn(learningPathApiService, 'getLearningPathForCurrentUser').mockReturnValueOnce(Promise.reject(new EntityNotFoundError())); + const generateLearningPathSpy = jest.spyOn(learningPathApiService, 'generateLearningPathForCurrentUser').mockResolvedValue(learningPath); + const startSpy = jest.spyOn(learningPathApiService, 'startLearningPathForCurrentUser'); fixture.detectChanges(); await fixture.whenStable(); - fixture.detectChanges(); - const generateLearningPathButton = fixture.debugElement.query(By.css('#generate-learning-path-button')); - generateLearningPathButton.nativeElement.click(); + await component.startLearningPath(); + + expect(component.learningPath()).toEqual({ ...learningPath, startedByStudent: true }); + expect(generateLearningPathSpy).toHaveBeenCalledExactlyOnceWith(courseId); + expect(startSpy).toHaveBeenCalledExactlyOnceWith(learningPath.id); + }); + it('should set learning path to started', async () => { + const startedSpy = jest.spyOn(learningPathApiService, 'startLearningPathForCurrentUser'); fixture.detectChanges(); await fixture.whenStable(); - expect(component.learningPathId()).toEqual(learningPathId); - expect(generateLearningPathSpy).toHaveBeenCalledWith(courseId); + await component.startLearningPath(); + + expect(component.learningPath()).toEqual({ ...learningPath, startedByStudent: true }); + expect(startedSpy).toHaveBeenCalledExactlyOnceWith(learningPath.id); }); - it('should set isLoading correctly during learning path generation', async () => { - jest.spyOn(learningPathApiService, 'getLearningPathId').mockRejectedValue(new EntityNotFoundError()); - jest.spyOn(learningPathApiService, 'generateLearningPath').mockResolvedValue(learningPathId); - const loadingSpy = jest.spyOn(component.isLearningPathIdLoading, 'set'); + it('should set isLoading correctly during learning path start', async () => { + const loadingSpy = jest.spyOn(component.isLearningPathLoading, 'set'); fixture.detectChanges(); await fixture.whenStable(); diff --git a/src/test/javascript/spec/service/learning-path/learning-path-api.service.spec.ts b/src/test/javascript/spec/service/learning-path/learning-path-api.service.spec.ts index 57d2487cefdb..d7f34259716e 100644 --- a/src/test/javascript/spec/service/learning-path/learning-path-api.service.spec.ts +++ b/src/test/javascript/spec/service/learning-path/learning-path-api.service.spec.ts @@ -26,11 +26,11 @@ describe('LearningPathApiService', () => { httpClient.verify(); }); - it('should get learning path id', async () => { + it('should get learning path for current user', async () => { const courseId = 1; - const methodCall = learningPathApiService.getLearningPathId(courseId); - const response = httpClient.expectOne({ method: 'GET', url: `${baseUrl}/courses/${courseId}/learning-path-id` }); + const methodCall = learningPathApiService.getLearningPathForCurrentUser(courseId); + const response = httpClient.expectOne({ method: 'GET', url: `${baseUrl}/courses/${courseId}/learning-path/me` }); response.flush({}); await methodCall; }); @@ -55,6 +55,13 @@ describe('LearningPathApiService', () => { await methodCall; }); + it('should start learning path for current user', async () => { + const methodCall = learningPathApiService.startLearningPathForCurrentUser(learningPathId); + const response = httpClient.expectOne({ method: 'PATCH', url: `${baseUrl}/learning-path/${learningPathId}/start` }); + response.flush({}); + await methodCall; + }); + it('should get learning path navigation overview', async () => { const methodCall = learningPathApiService.getLearningPathNavigationOverview(learningPathId); const response = httpClient.expectOne({ method: 'GET', url: `${baseUrl}/learning-path/${learningPathId}/navigation-overview` }); @@ -62,8 +69,8 @@ describe('LearningPathApiService', () => { await methodCall; }); - it('should generate learning path', async () => { - const methodCall = learningPathApiService.generateLearningPath(courseId); + it('should generate learning path for current user', async () => { + const methodCall = learningPathApiService.generateLearningPathForCurrentUser(courseId); const response = httpClient.expectOne({ method: 'POST', url: `${baseUrl}/courses/${courseId}/learning-path` }); response.flush({}); await methodCall; diff --git a/src/test/playwright/support/pageobjects/exam/ExamExerciseGroupCreationPage.ts b/src/test/playwright/support/pageobjects/exam/ExamExerciseGroupCreationPage.ts index 58d3557d19f3..435202bf7971 100644 --- a/src/test/playwright/support/pageobjects/exam/ExamExerciseGroupCreationPage.ts +++ b/src/test/playwright/support/pageobjects/exam/ExamExerciseGroupCreationPage.ts @@ -32,14 +32,14 @@ export class ExamExerciseGroupCreationPage { } async clickSave(): Promise { - const responsePromise = this.page.waitForResponse(`${COURSE_BASE}/*/exams/*/exerciseGroups`); + const responsePromise = this.page.waitForResponse(`${COURSE_BASE}/*/exams/*/exercise-groups`); await this.page.locator('#save-group').click(); const response = await responsePromise; return response.json(); } async update() { - const responsePromise = this.page.waitForResponse(`${COURSE_BASE}/*/exams/*/exerciseGroups`); + const responsePromise = this.page.waitForResponse(`${COURSE_BASE}/*/exams/*/exercise-groups`); await this.page.locator('#save-group').click(); await responsePromise; } diff --git a/src/test/playwright/support/requests/ExamAPIRequests.ts b/src/test/playwright/support/requests/ExamAPIRequests.ts index c250415fb2db..3e5e18a04a7d 100644 --- a/src/test/playwright/support/requests/ExamAPIRequests.ts +++ b/src/test/playwright/support/requests/ExamAPIRequests.ts @@ -148,12 +148,12 @@ export class ExamAPIRequests { exerciseGroup.exam = exam; exerciseGroup.title = title; exerciseGroup.isMandatory = mandatory; - const response = await this.page.request.post(`${COURSE_BASE}/${exam.course!.id}/exams/${exam.id}/exerciseGroups`, { data: exerciseGroup }); + const response = await this.page.request.post(`${COURSE_BASE}/${exam.course!.id}/exams/${exam.id}/exercise-groups`, { data: exerciseGroup }); return response.json(); } async deleteExerciseGroupForExam(exam: Exam, exerciseGroup: ExerciseGroup) { - await this.page.request.delete(`${COURSE_BASE}/${exam.course!.id}/exams/${exam.id}/exerciseGroups/${exerciseGroup.id}`); + await this.page.request.delete(`${COURSE_BASE}/${exam.course!.id}/exams/${exam.id}/exercise-groups/${exerciseGroup.id}`); } /**