From ad160ff95354e13794bde767cf3553c2a036b8bc Mon Sep 17 00:00:00 2001 From: Simon Entholzer <33342534+SimonEntholzer@users.noreply.github.com> Date: Thu, 3 Oct 2024 20:49:52 +0200 Subject: [PATCH] Development: Add check for REST endpoints to be in kebab case (#9210) --- .../aet/artemis/core/web/CourseResource.java | 8 +++--- .../aet/artemis/exam/web/ExamResource.java | 4 +-- .../exam/web/ExerciseGroupResource.java | 22 +++++++-------- .../exercise/web/ParticipationResource.java | 8 +++--- .../web/localci/AeolusTemplateResource.java | 6 ++-- .../manage/course-management.service.ts | 4 +-- .../exam/manage/exam-management.service.ts | 2 +- .../exercise-groups/exercise-group.service.ts | 10 +++---- .../shared/service/aeolus.service.ts | 2 +- .../participation/participation.service.ts | 4 +-- .../ManagementResourceIntegrationTest.java | 4 +-- .../artemis/core/util/CourseTestService.java | 8 +++--- .../ExamParticipationIntegrationTest.java | 10 +++---- ...ciseGroupIntegrationJenkinsGitlabTest.java | 28 +++++++++---------- .../ParticipationIntegrationTest.java | 6 ++-- .../util/ProgrammingExerciseTestService.java | 2 +- .../ResourceArchitectureTest.java | 28 +++++++++++++++++++ .../course/course-management.service.spec.ts | 4 +-- .../manage/exam-management.service.spec.ts | 2 +- .../exam/ExamExerciseGroupCreationPage.ts | 4 +-- .../support/requests/ExamAPIRequests.ts | 4 +-- 21 files changed, 99 insertions(+), 71 deletions(-) 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/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/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/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/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/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/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}`); } /**