diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/StudentQuestionAnswerResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/StudentQuestionAnswerResource.java index 5f77d0a19b1e..5a6a6eb28747 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/StudentQuestionAnswerResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/StudentQuestionAnswerResource.java @@ -16,6 +16,7 @@ import de.tum.in.www1.artemis.domain.Course; import de.tum.in.www1.artemis.domain.StudentQuestionAnswer; import de.tum.in.www1.artemis.domain.User; +import de.tum.in.www1.artemis.repository.CourseRepository; import de.tum.in.www1.artemis.repository.StudentQuestionAnswerRepository; import de.tum.in.www1.artemis.service.AuthorizationCheckService; import de.tum.in.www1.artemis.service.GroupNotificationService; @@ -41,6 +42,8 @@ public class StudentQuestionAnswerResource { private final StudentQuestionAnswerRepository studentQuestionAnswerRepository; + private final CourseRepository courseRepository; + private final AuthorizationCheckService authorizationCheckService; private final UserService userService; @@ -50,8 +53,10 @@ public class StudentQuestionAnswerResource { SingleUserNotificationService singleUserNotificationService; public StudentQuestionAnswerResource(StudentQuestionAnswerRepository studentQuestionAnswerRepository, GroupNotificationService groupNotificationService, - SingleUserNotificationService singleUserNotificationService, AuthorizationCheckService authorizationCheckService, UserService userService) { + SingleUserNotificationService singleUserNotificationService, AuthorizationCheckService authorizationCheckService, UserService userService, + CourseRepository courseRepository) { this.studentQuestionAnswerRepository = studentQuestionAnswerRepository; + this.courseRepository = courseRepository; this.groupNotificationService = groupNotificationService; this.singleUserNotificationService = singleUserNotificationService; this.authorizationCheckService = authorizationCheckService; @@ -59,21 +64,32 @@ public StudentQuestionAnswerResource(StudentQuestionAnswerRepository studentQues } /** - * POST /question-answers : Create a new studentQuestionAnswer. + * POST /courses/{courseId}/question-answers : Create a new studentQuestionAnswer. * + * @param courseId the id of the course the answer belongs to * @param studentQuestionAnswer the studentQuestionAnswer to create * @return the ResponseEntity with status 201 (Created) and with body the new studentQuestionAnswer, or with status 400 (Bad Request) if the studentQuestionAnswer has already * an ID * @throws URISyntaxException if the Location URI syntax is incorrect */ - @PostMapping("/student-question-answers") + @PostMapping("courses/{courseId}/student-question-answers") @PreAuthorize("hasAnyRole('USER', 'TA', 'INSTRUCTOR', 'ADMIN')") - // TODO: there are no security checks here. The API endpoint should at least include the course id - public ResponseEntity createStudentQuestionAnswer(@RequestBody StudentQuestionAnswer studentQuestionAnswer) throws URISyntaxException { + public ResponseEntity createStudentQuestionAnswer(@PathVariable Long courseId, @RequestBody StudentQuestionAnswer studentQuestionAnswer) + throws URISyntaxException { log.debug("REST request to save StudentQuestionAnswer : {}", studentQuestionAnswer); + User user = this.userService.getUserWithGroupsAndAuthorities(); if (studentQuestionAnswer.getId() != null) { throw new BadRequestAlertException("A new studentQuestionAnswer cannot already have an ID", ENTITY_NAME, "idexists"); } + Optional optionalCourse = courseRepository.findById(courseId); + if (optionalCourse.isEmpty()) { + return ResponseEntity.notFound().build(); + } + if (!this.authorizationCheckService.isAtLeastStudentInCourse(optionalCourse.get(), user)) { + return forbidden(); + } + // answer to approved if written by an instructor + studentQuestionAnswer.setTutorApproved(this.authorizationCheckService.isAtLeastInstructorInCourse(optionalCourse.get(), user)); StudentQuestionAnswer result = studentQuestionAnswerRepository.save(studentQuestionAnswer); if (result.getQuestion().getExercise() != null) { groupNotificationService.notifyTutorAndInstructorGroupAboutNewAnswerForExercise(result); @@ -83,27 +99,32 @@ public ResponseEntity createStudentQuestionAnswer(@Reques groupNotificationService.notifyTutorAndInstructorGroupAboutNewAnswerForLecture(result); singleUserNotificationService.notifyUserAboutNewAnswerForLecture(result); } - return ResponseEntity.created(new URI("/api/question-answers/" + result.getId())) + return ResponseEntity.created(new URI("/api/courses" + courseId + "/student-question-answers/" + result.getId())) .headers(HeaderUtil.createEntityCreationAlert(applicationName, true, ENTITY_NAME, result.getId().toString())).body(result); } /** - * PUT /question-answers : Updates an existing studentQuestionAnswer. + * PUT /courses/{courseId}/question-answers : Updates an existing studentQuestionAnswer. * + * @param courseId the id of the course the answer belongs to * @param studentQuestionAnswer the studentQuestionAnswer to update * @return the ResponseEntity with status 200 (OK) and with body the updated studentQuestionAnswer, or with status 400 (Bad Request) if the studentQuestionAnswer is not valid, * or with status 500 (Internal Server Error) if the studentQuestionAnswer couldn't be updated * @throws URISyntaxException if the Location URI syntax is incorrect */ - @PutMapping("/student-question-answers") - // TODO: there are no security checks here. The API endpoint should at least include the course id + @PutMapping("courses/{courseId}/student-question-answers") @PreAuthorize("hasAnyRole('USER', 'TA', 'INSTRUCTOR', 'ADMIN')") - public ResponseEntity updateStudentQuestionAnswer(@RequestBody StudentQuestionAnswer studentQuestionAnswer) throws URISyntaxException { + public ResponseEntity updateStudentQuestionAnswer(@PathVariable Long courseId, @RequestBody StudentQuestionAnswer studentQuestionAnswer) + throws URISyntaxException { User user = userService.getUserWithGroupsAndAuthorities(); log.debug("REST request to update StudentQuestionAnswer : {}", studentQuestionAnswer); if (studentQuestionAnswer.getId() == null) { throw new BadRequestAlertException("Invalid id", ENTITY_NAME, "idnull"); } + Optional optionalCourse = courseRepository.findById(courseId); + if (optionalCourse.isEmpty()) { + return ResponseEntity.notFound().build(); + } Optional optionalStudentQuestionAnswer = studentQuestionAnswerRepository.findById(studentQuestionAnswer.getId()); if (optionalStudentQuestionAnswer.isEmpty()) { return ResponseEntity.notFound().build(); @@ -118,35 +139,47 @@ public ResponseEntity updateStudentQuestionAnswer(@Reques } /** - * GET /question-answers/:id : get the "id" questionAnswer. + * GET /courses/{courseId}/question-answers/:id : get the "id" questionAnswer. * + * @param courseId the id of the course the answer belongs to * @param id the id of the questionAnswer to retrieve * @return the ResponseEntity with status 200 (OK) and with body the questionAnswer, or with status 404 (Not Found) */ - @GetMapping("/student-question-answers/{id}") + @GetMapping("courses/{courseId}/student-question-answers/{id}") @PreAuthorize("hasAnyRole('INSTRUCTOR', 'ADMIN')") - // TODO: there are no security checks here. The API endpoint should at least include the course id - public ResponseEntity getStudentQuestionAnswer(@PathVariable Long id) { + public ResponseEntity getStudentQuestionAnswer(@PathVariable Long courseId, @PathVariable Long id) { log.debug("REST request to get StudentQuestionAnswer : {}", id); + User user = this.userService.getUserWithGroupsAndAuthorities(); + Optional optionalCourse = courseRepository.findById(courseId); + if (optionalCourse.isEmpty()) { + return ResponseEntity.notFound().build(); + } + if (!this.authorizationCheckService.isAtLeastStudentInCourse(optionalCourse.get(), user)) { + return forbidden(); + } Optional questionAnswer = studentQuestionAnswerRepository.findById(id); return ResponseUtil.wrapOrNotFound(questionAnswer); } /** - * DELETE /question-answers/:id : delete the "id" questionAnswer. + * DELETE /courses/{courseId}/question-answers/:id : delete the "id" questionAnswer. * + * @param courseId the id of the course the answer belongs to * @param id the id of the questionAnswer to delete * @return the ResponseEntity with status 200 (OK) */ - @DeleteMapping("/student-question-answers/{id}") - // TODO: there are no security checks here. The API endpoint should at least include the course id + @DeleteMapping("courses/{courseId}/student-question-answers/{id}") @PreAuthorize("hasAnyRole('USER', 'TA', 'INSTRUCTOR', 'ADMIN')") - public ResponseEntity deleteStudentQuestionAnswer(@PathVariable Long id) { + public ResponseEntity deleteStudentQuestionAnswer(@PathVariable Long courseId, @PathVariable Long id) { User user = userService.getUserWithGroupsAndAuthorities(); Optional optionalStudentQuestionAnswer = studentQuestionAnswerRepository.findById(id); if (optionalStudentQuestionAnswer.isEmpty()) { return ResponseEntity.notFound().build(); } + Optional optionalCourse = courseRepository.findById(courseId); + if (optionalCourse.isEmpty()) { + return ResponseEntity.notFound().build(); + } StudentQuestionAnswer studentQuestionAnswer = optionalStudentQuestionAnswer.get(); Course course = studentQuestionAnswer.getQuestion().getCourse(); String entity = ""; diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/StudentQuestionResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/StudentQuestionResource.java index 34c909ea28e7..f14366c81ca9 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/StudentQuestionResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/StudentQuestionResource.java @@ -72,20 +72,28 @@ public StudentQuestionResource(StudentQuestionRepository studentQuestionReposito } /** - * POST /student-questions : Create a new studentQuestion. + * POST /courses/{courseId}/student-questions : Create a new studentQuestion. * + * @param courseId course the question belongs to * @param studentQuestion the studentQuestion to create * @return the ResponseEntity with status 201 (Created) and with body the new studentQuestion, or with status 400 (Bad Request) if the studentQuestion has already an ID * @throws URISyntaxException if the Location URI syntax is incorrect */ - @PostMapping("/student-questions") + @PostMapping("courses/{courseId}/student-questions") @PreAuthorize("hasAnyRole('USER', 'TA', 'INSTRUCTOR', 'ADMIN')") - // TODO: there are no security checks here. The API endpoint should at least include the course id - public ResponseEntity createStudentQuestion(@RequestBody StudentQuestion studentQuestion) throws URISyntaxException { + public ResponseEntity createStudentQuestion(@PathVariable Long courseId, @RequestBody StudentQuestion studentQuestion) throws URISyntaxException { log.debug("REST request to save StudentQuestion : {}", studentQuestion); + User user = this.userService.getUserWithGroupsAndAuthorities(); if (studentQuestion.getId() != null) { throw new BadRequestAlertException("A new studentQuestion cannot already have an ID", ENTITY_NAME, "idexists"); } + Optional optionalCourse = courseRepository.findById(courseId); + if (optionalCourse.isEmpty()) { + return ResponseEntity.notFound().build(); + } + if (!this.authorizationCheckService.isAtLeastStudentInCourse(optionalCourse.get(), user)) { + return forbidden(); + } StudentQuestion question = studentQuestionRepository.save(studentQuestion); if (question.getExercise() != null) { groupNotificationService.notifyTutorAndInstructorGroupAboutNewQuestionForExercise(question); @@ -93,27 +101,31 @@ public ResponseEntity createStudentQuestion(@RequestBody Studen if (question.getLecture() != null) { groupNotificationService.notifyTutorAndInstructorGroupAboutNewQuestionForLecture(question); } - return ResponseEntity.created(new URI("/api/student-questions/" + question.getId())) + return ResponseEntity.created(new URI("/api/courses/" + courseId + "/student-questions/" + question.getId())) .headers(HeaderUtil.createEntityCreationAlert(applicationName, true, ENTITY_NAME, question.getId().toString())).body(question); } /** - * PUT /student-questions : Updates an existing studentQuestion. + * PUT /courses/{courseId}/student-questions : Updates an existing studentQuestion. * + * @param courseId course the question belongs to * @param studentQuestion the studentQuestion to update * @return the ResponseEntity with status 200 (OK) and with body the updated studentQuestion, or with status 400 (Bad Request) if the studentQuestion is not valid, or with * status 500 (Internal Server Error) if the studentQuestion couldn't be updated * @throws URISyntaxException if the Location URI syntax is incorrect */ - @PutMapping("/student-questions") + @PutMapping("courses/{courseId}/student-questions") @PreAuthorize("hasAnyRole('USER', 'TA', 'INSTRUCTOR', 'ADMIN')") - // TODO: there are no security checks here. The API endpoint should at least include the course id - public ResponseEntity updateStudentQuestion(@RequestBody StudentQuestion studentQuestion) throws URISyntaxException { + public ResponseEntity updateStudentQuestion(@PathVariable Long courseId, @RequestBody StudentQuestion studentQuestion) throws URISyntaxException { User user = userService.getUserWithGroupsAndAuthorities(); log.debug("REST request to update StudentQuestion : {}", studentQuestion); if (studentQuestion.getId() == null) { throw new BadRequestAlertException("Invalid id", ENTITY_NAME, "idnull"); } + Optional optionalCourse = courseRepository.findById(courseId); + if (optionalCourse.isEmpty()) { + return ResponseEntity.notFound().build(); + } Optional optionalStudentQuestion = studentQuestionRepository.findById(studentQuestion.getId()); if (optionalStudentQuestion.isEmpty()) { return ResponseEntity.notFound().build(); @@ -128,17 +140,19 @@ public ResponseEntity updateStudentQuestion(@RequestBody Studen } /** - * PUT /student-questions/{questionId}/votes : Updates votes for a studentQuestion. + * PUT /courses/{courseId}/student-questions/{questionId}/votes : Updates votes for a studentQuestion. * + * @param courseId course the question belongs to * @param questionId the ID of the question to update * @param voteChange value by which votes are increased / decreased * @return the ResponseEntity with status 200 (OK) and with body the updated studentQuestion, or with status 400 (Bad Request) if the studentQuestion is not valid, or with * status 500 (Internal Server Error) if the studentQuestion couldn't be updated * @throws URISyntaxException if the Location URI syntax is incorrect */ - @PutMapping("/student-questions/{questionId}/votes") + @PutMapping("courses/{courseId}/student-questions/{questionId}/votes") @PreAuthorize("hasAnyRole('USER', 'TA', 'INSTRUCTOR', 'ADMIN')") - public ResponseEntity updateStudentQuestionVotes(@PathVariable Long questionId, @RequestBody Integer voteChange) throws URISyntaxException { + public ResponseEntity updateStudentQuestionVotes(@PathVariable Long courseId, @PathVariable Long questionId, @RequestBody Integer voteChange) + throws URISyntaxException { if (voteChange < -2 || voteChange > 2) { return forbidden(); } @@ -147,6 +161,10 @@ public ResponseEntity updateStudentQuestionVotes(@PathVariable if (optionalStudentQuestion.isEmpty()) { return ResponseEntity.notFound().build(); } + Optional optionalCourse = courseRepository.findById(courseId); + if (optionalCourse.isEmpty()) { + return ResponseEntity.notFound().build(); + } if (mayUpdateStudentQuestionVotes(optionalStudentQuestion.get(), user)) { StudentQuestion updatedStudentQuestion = optionalStudentQuestion.get(); Integer newVotes = updatedStudentQuestion.getVotes() + voteChange; @@ -160,19 +178,24 @@ public ResponseEntity updateStudentQuestionVotes(@PathVariable } /** - * GET /exercises/{exerciseId}/student-questions : get all student questions for exercise. + * GET /courses/{courseId}/exercises/{exerciseId}/student-questions : get all student questions for exercise. * + * @param courseId course the question belongs to * @param exerciseId the exercise that the student questions belong to * @return the ResponseEntity with status 200 (OK) and with body all student questions for exercise */ - @GetMapping("exercises/{exerciseId}/student-questions") + @GetMapping("courses/{courseId}/exercises/{exerciseId}/student-questions") @PreAuthorize("hasAnyRole('USER', 'TA', 'INSTRUCTOR', 'ADMIN')") - public ResponseEntity> getAllQuestionsForExercise(@PathVariable Long exerciseId) { + public ResponseEntity> getAllQuestionsForExercise(@PathVariable Long courseId, @PathVariable Long exerciseId) { final User user = userService.getUserWithGroupsAndAuthorities(); Optional exercise = exerciseRepository.findById(exerciseId); if (exercise.isEmpty()) { throw new EntityNotFoundException("Exercise with exerciseId " + exerciseId + " does not exist!"); } + Optional optionalCourse = courseRepository.findById(courseId); + if (optionalCourse.isEmpty()) { + return ResponseEntity.notFound().build(); + } if (!authorizationCheckService.isAtLeastStudentForExercise(exercise.get(), user)) { return forbidden(); } @@ -183,19 +206,24 @@ public ResponseEntity> getAllQuestionsForExercise(@PathVar } /** + * GET /courses/{courseId}/lectures/{lectureId}/student-questions : get all student questions for lecture. * - * GET /lectures/{lectureId}/student-questions : get all student questions for lecture. + * @param courseId course the question belongs to * @param lectureId the lecture that the student questions belong to * @return the ResponseEntity with status 200 (OK) and with body all student questions for lecture */ - @GetMapping("lectures/{lectureId}/student-questions") + @GetMapping("courses/{courseId}/lectures/{lectureId}/student-questions") @PreAuthorize("hasAnyRole('USER', 'TA', 'INSTRUCTOR', 'ADMIN')") - public ResponseEntity> getAllQuestionsForLecture(@PathVariable Long lectureId) { + public ResponseEntity> getAllQuestionsForLecture(@PathVariable Long courseId, @PathVariable Long lectureId) { final User user = userService.getUserWithGroupsAndAuthorities(); Optional lecture = lectureRepository.findById(lectureId); if (lecture.isEmpty()) { throw new EntityNotFoundException("Lecture with lectureId " + lectureId + " does not exist!"); } + Optional optionalCourse = courseRepository.findById(courseId); + if (optionalCourse.isEmpty()) { + return ResponseEntity.notFound().build(); + } if (!authorizationCheckService.isAtLeastStudentInCourse(lecture.get().getCourse(), user)) { return forbidden(); } @@ -239,16 +267,20 @@ private void hideSensitiveInformation(List studentQuestions) { } /** - * DELETE /student-questions/:id : delete the "id" studentQuestion. + * DELETE /courses/{courseId}/student-questions/:id : delete the "id" studentQuestion. * + * @param courseId course the question belongs to * @param id the id of the studentQuestion to delete * @return the ResponseEntity with status 200 (OK) */ - @DeleteMapping("/student-questions/{id}") - // TODO: there are no security checks here. The API endpoint should at least include the course id + @DeleteMapping("courses/{courseId}/student-questions/{id}") @PreAuthorize("hasAnyRole('USER', 'TA', 'INSTRUCTOR', 'ADMIN')") - public ResponseEntity deleteStudentQuestion(@PathVariable Long id) { + public ResponseEntity deleteStudentQuestion(@PathVariable Long courseId, @PathVariable Long id) { User user = userService.getUserWithGroupsAndAuthorities(); + Optional optionalCourse = courseRepository.findById(courseId); + if (optionalCourse.isEmpty()) { + return ResponseEntity.notFound().build(); + } Optional optionalStudentQuestion = studentQuestionRepository.findById(id); if (optionalStudentQuestion.isEmpty()) { return ResponseEntity.notFound().build(); diff --git a/src/main/webapp/app/overview/student-questions/student-question-answer/student-question-answer.component.ts b/src/main/webapp/app/overview/student-questions/student-question-answer/student-question-answer.component.ts index 7b1fd5486c9c..919645671378 100644 --- a/src/main/webapp/app/overview/student-questions/student-question-answer/student-question-answer.component.ts +++ b/src/main/webapp/app/overview/student-questions/student-question-answer/student-question-answer.component.ts @@ -1,4 +1,5 @@ import { Component, EventEmitter, Input, Output, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; import { User } from 'app/core/user/user.model'; import { StudentQuestionAnswer } from 'app/entities/student-question-answer.model'; import { StudentQuestionAnswerService } from 'app/overview/student-questions/student-question-answer/student-question-answer.service'; @@ -28,14 +29,16 @@ export class StudentQuestionAnswerComponent implements OnInit { editText?: string; isEditMode: boolean; EditorMode = EditorMode; + courseId: number; - constructor(private studentQuestionAnswerService: StudentQuestionAnswerService) {} + constructor(private studentQuestionAnswerService: StudentQuestionAnswerService, private route: ActivatedRoute) {} /** * Sets the text of the answer as the editor text */ ngOnInit(): void { this.editText = this.studentQuestionAnswer.answerText; + this.courseId = Number(this.route.snapshot.paramMap.get('courseId')); } /** @@ -51,7 +54,7 @@ export class StudentQuestionAnswerComponent implements OnInit { * Deletes this studentQuestionAnswer */ deleteAnswer(): void { - this.studentQuestionAnswerService.delete(this.studentQuestionAnswer.id!).subscribe(() => { + this.studentQuestionAnswerService.delete(this.courseId, this.studentQuestionAnswer.id!).subscribe(() => { this.interactAnswer.emit({ name: QuestionAnswerActionName.DELETE, studentQuestionAnswer: this.studentQuestionAnswer, @@ -64,7 +67,7 @@ export class StudentQuestionAnswerComponent implements OnInit { */ saveAnswer(): void { this.studentQuestionAnswer.answerText = this.editText; - this.studentQuestionAnswerService.update(this.studentQuestionAnswer).subscribe(() => { + this.studentQuestionAnswerService.update(this.courseId, this.studentQuestionAnswer).subscribe(() => { this.isEditMode = false; }); } @@ -74,7 +77,7 @@ export class StudentQuestionAnswerComponent implements OnInit { */ toggleAnswerTutorApproved(): void { this.studentQuestionAnswer.tutorApproved = !this.studentQuestionAnswer.tutorApproved; - this.studentQuestionAnswerService.update(this.studentQuestionAnswer).subscribe(() => { + this.studentQuestionAnswerService.update(this.courseId, this.studentQuestionAnswer).subscribe(() => { this.interactAnswer.emit({ name: QuestionAnswerActionName.APPROVE, studentQuestionAnswer: this.studentQuestionAnswer, diff --git a/src/main/webapp/app/overview/student-questions/student-question-answer/student-question-answer.service.ts b/src/main/webapp/app/overview/student-questions/student-question-answer/student-question-answer.service.ts index b4d47241fe21..61a1933459b9 100644 --- a/src/main/webapp/app/overview/student-questions/student-question-answer/student-question-answer.service.ts +++ b/src/main/webapp/app/overview/student-questions/student-question-answer/student-question-answer.service.ts @@ -5,7 +5,6 @@ import * as moment from 'moment'; import { map } from 'rxjs/operators'; import { SERVER_API_URL } from 'app/app.constants'; -import { createRequestOption } from 'app/shared/util/request-util'; import { StudentQuestionAnswer } from 'app/entities/student-question-answer.model'; type EntityResponseType = HttpResponse; @@ -13,64 +12,56 @@ type EntityArrayResponseType = HttpResponse; @Injectable({ providedIn: 'root' }) export class StudentQuestionAnswerService { - public resourceUrl = SERVER_API_URL + 'api/student-question-answers'; + public resourceUrl = SERVER_API_URL + 'api/courses/'; constructor(protected http: HttpClient) {} /** * create studentQuestionAnswer + * @param {number} courseId * @param {StudentQuestionAnswer} studentQuestionAnswer * @return {Observable} */ - create(studentQuestionAnswer: StudentQuestionAnswer): Observable { + create(courseId: number, studentQuestionAnswer: StudentQuestionAnswer): Observable { const copy = this.convertDateFromClient(studentQuestionAnswer); return this.http - .post(this.resourceUrl, copy, { observe: 'response' }) + .post(`${this.resourceUrl}${courseId}/student-question-answers`, copy, { observe: 'response' }) .pipe(map((res: EntityResponseType) => this.convertDateFromServer(res))); } /** * update studentQuestionAnswer + * @param {number} courseId * @param {StudentQuestionAnswer} studentQuestionAnswer * @return {Observable} */ - update(studentQuestionAnswer: StudentQuestionAnswer): Observable { + update(courseId: number, studentQuestionAnswer: StudentQuestionAnswer): Observable { const copy = this.convertDateFromClient(studentQuestionAnswer); return this.http - .put(this.resourceUrl, copy, { observe: 'response' }) + .put(`${this.resourceUrl}${courseId}/student-question-answers`, copy, { observe: 'response' }) .pipe(map((res: EntityResponseType) => this.convertDateFromServer(res))); } /** * find studentQuestionAnswer by id + * @param {number} courseId * @param {number} id * @return {Observable} */ - find(id: number): Observable { + find(courseId: number, id: number): Observable { return this.http - .get(`${this.resourceUrl}/${id}`, { observe: 'response' }) + .get(`${this.resourceUrl}${courseId}/student-question-answers/${id}`, { observe: 'response' }) .pipe(map((res: EntityResponseType) => this.convertDateFromServer(res))); } - /** - * get studentQuestionAnswers for query - * @param {any} req? - * @return Observable - */ - query(req?: any): Observable { - const options = createRequestOption(req); - return this.http - .get(this.resourceUrl, { params: options, observe: 'response' }) - .pipe(map((res: EntityArrayResponseType) => this.convertDateArrayFromServer(res))); - } - /** * delete studentQuestionAnswer by id + * @param {number} courseId * @param {number} id * @return {Observable>} */ - delete(id: number): Observable> { - return this.http.delete(`${this.resourceUrl}/${id}`, { observe: 'response' }); + delete(courseId: number, id: number): Observable> { + return this.http.delete(`${this.resourceUrl}${courseId}/student-question-answers/${id}`, { observe: 'response' }); } /** @@ -79,15 +70,14 @@ export class StudentQuestionAnswerService { * @return {StudentQuestionAnswer} */ protected convertDateFromClient(studentQuestionAnswer: StudentQuestionAnswer): StudentQuestionAnswer { - const copy: StudentQuestionAnswer = Object.assign({}, studentQuestionAnswer, { + return Object.assign({}, studentQuestionAnswer, { answerDate: studentQuestionAnswer.answerDate && moment(studentQuestionAnswer.answerDate).isValid() ? moment(studentQuestionAnswer.answerDate).toJSON() : undefined, }); - return copy; } /** * Takes a studentQuestionAnswer and converts the date from the server - * @param {StudentQuestionAnswer} studentQuestionAnswer + * @param {EntityResponseType} res * @return {StudentQuestionAnswer} */ protected convertDateFromServer(res: EntityResponseType): EntityResponseType { diff --git a/src/main/webapp/app/overview/student-questions/student-question-row/student-question-row.component.ts b/src/main/webapp/app/overview/student-questions/student-question-row/student-question-row.component.ts index 1cacf49eaecd..81f4fbc3d6c5 100644 --- a/src/main/webapp/app/overview/student-questions/student-question-row/student-question-row.component.ts +++ b/src/main/webapp/app/overview/student-questions/student-question-row/student-question-row.component.ts @@ -1,4 +1,5 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; import { User } from 'app/core/user/user.model'; import * as moment from 'moment'; import { HttpResponse } from '@angular/common/http'; @@ -39,17 +40,20 @@ export class StudentQuestionRowComponent implements OnInit { sortedQuestionAnswers: StudentQuestionAnswer[]; approvedQuestionAnswers: StudentQuestionAnswer[]; EditorMode = EditorMode; + courseId: number; constructor( private studentQuestionAnswerService: StudentQuestionAnswerService, private studentQuestionService: StudentQuestionService, private localStorage: LocalStorageService, + private route: ActivatedRoute, ) {} /** * sort answers when component is initialized */ ngOnInit(): void { + this.courseId = Number(this.route.snapshot.paramMap.get('courseId')); this.sortQuestionAnswers(); } @@ -123,7 +127,7 @@ export class StudentQuestionRowComponent implements OnInit { * deletes the studentQuestion */ deleteQuestion(): void { - this.studentQuestionService.delete(this.studentQuestion.id!).subscribe(() => { + this.studentQuestionService.delete(this.courseId, this.studentQuestion.id!).subscribe(() => { this.localStorage.clear(`q${this.studentQuestion.id}u${this.user.id}`); this.interactQuestionRow.emit({ name: QuestionRowActionName.DELETE, @@ -143,7 +147,7 @@ export class StudentQuestionRowComponent implements OnInit { studentQuestionAnswer.question = this.studentQuestion; studentQuestionAnswer.tutorApproved = false; studentQuestionAnswer.answerDate = moment(); - this.studentQuestionAnswerService.create(studentQuestionAnswer).subscribe((studentQuestionResponse: HttpResponse) => { + this.studentQuestionAnswerService.create(this.courseId, studentQuestionAnswer).subscribe((studentQuestionResponse: HttpResponse) => { if (!this.studentQuestion.answers) { this.studentQuestion.answers = []; } diff --git a/src/main/webapp/app/overview/student-questions/student-question/student-question.component.ts b/src/main/webapp/app/overview/student-questions/student-question/student-question.component.ts index 2774b36c517f..9b3dae7ef207 100644 --- a/src/main/webapp/app/overview/student-questions/student-question/student-question.component.ts +++ b/src/main/webapp/app/overview/student-questions/student-question/student-question.component.ts @@ -30,6 +30,7 @@ export class StudentQuestionComponent implements OnInit { editText?: string; isEditMode: boolean; EditorMode = EditorMode; + courseId: number; constructor(private studentQuestionService: StudentQuestionService) {} @@ -40,6 +41,7 @@ export class StudentQuestionComponent implements OnInit { ngOnInit(): void { if (this.user) { this.isQuestionAuthor = this.studentQuestion.author!.id === this.user.id; + this.courseId = this.studentQuestion.exercise ? this.studentQuestion.exercise.course!.id! : this.studentQuestion.lecture!.course!.id!; } this.editText = this.studentQuestion.questionText; } @@ -59,7 +61,7 @@ export class StudentQuestionComponent implements OnInit { */ saveQuestion(): void { this.studentQuestion.questionText = this.editText; - this.studentQuestionService.update(this.studentQuestion).subscribe(() => { + this.studentQuestionService.update(this.courseId, this.studentQuestion).subscribe(() => { this.isEditMode = false; }); } @@ -87,10 +89,10 @@ export class StudentQuestionComponent implements OnInit { /** * update the number of votes for this studentQuestion - * @param {number} votes + * @param {number} voteChange */ updateVotes(voteChange: number): void { - this.studentQuestionService.updateVotes(this.studentQuestion.id!, voteChange).subscribe((res) => { + this.studentQuestionService.updateVotes(this.courseId, this.studentQuestion.id!, voteChange).subscribe((res) => { this.studentQuestion = res.body!; this.interactQuestion.emit({ name: QuestionActionName.VOTE_CHANGE, diff --git a/src/main/webapp/app/overview/student-questions/student-question/student-question.service.ts b/src/main/webapp/app/overview/student-questions/student-question/student-question.service.ts index d504fb7c4a6c..48cfe5255ee2 100644 --- a/src/main/webapp/app/overview/student-questions/student-question/student-question.service.ts +++ b/src/main/webapp/app/overview/student-questions/student-question/student-question.service.ts @@ -11,65 +11,70 @@ type EntityArrayResponseType = HttpResponse; @Injectable({ providedIn: 'root' }) export class StudentQuestionService { - public resourceUrl = SERVER_API_URL + 'api/student-questions'; + public resourceUrl = SERVER_API_URL + 'api/courses/'; constructor(protected http: HttpClient) {} /** * create a studentQuestion + * @param {number} courseId * @param {StudentQuestion} studentQuestion * @return {Observable} */ - create(studentQuestion: StudentQuestion): Observable { + create(courseId: number, studentQuestion: StudentQuestion): Observable { const copy = this.convertDateFromClient(studentQuestion); return this.http - .post(this.resourceUrl, copy, { observe: 'response' }) + .post(`${this.resourceUrl}${courseId}/student-questions`, copy, { observe: 'response' }) .pipe(map((res: EntityResponseType) => this.convertDateFromServer(res))); } /** * update the studentQuestion + * @param {number} courseId * @param {StudentQuestion} studentQuestion * @return {Observable} */ - update(studentQuestion: StudentQuestion): Observable { + update(courseId: number, studentQuestion: StudentQuestion): Observable { const copy = this.convertDateFromClient(studentQuestion); return this.http - .put(this.resourceUrl, copy, { observe: 'response' }) + .put(`${this.resourceUrl}${courseId}/student-questions`, copy, { observe: 'response' }) .pipe(map((res: EntityResponseType) => this.convertDateFromServer(res))); } /** * update the votes of a studentQuestion + * @param {number} courseId * @param {number} questionId - * @param {number} votes + * @param {number} voteChange * @return {Observable} */ - updateVotes(questionId: number, voteChange: number): Observable { + updateVotes(courseId: number, questionId: number, voteChange: number): Observable { return this.http - .put(`${this.resourceUrl}/${questionId}/votes`, voteChange, { observe: 'response' }) + .put(`${this.resourceUrl}${courseId}/student-questions}/${questionId}/votes`, voteChange, { observe: 'response' }) .pipe(map((res: EntityResponseType) => this.convertDateFromServer(res))); } /** * find all questions for id of exercise + * @param {number} courseId * @param {number} exerciseId * @return {Observable} */ - findQuestionsForExercise(exerciseId: number): Observable { + findQuestionsForExercise(courseId: number, exerciseId: number): Observable { return this.http - .get(`api/exercises/${exerciseId}/student-questions`, { observe: 'response' }) + .get(`${this.resourceUrl}${courseId}/exercises/${exerciseId}/student-questions`, { observe: 'response' }) .pipe(map((res: EntityArrayResponseType) => this.convertDateArrayFromServer(res))); } /** * find all questions for id of lecture + * @param {number} courseId * @param {number} lectureId * @return {Observable} */ - findQuestionsForLecture(lectureId: number): Observable { + findQuestionsForLecture(courseId: number, lectureId: number): Observable { return this.http - .get(`api/lectures/${lectureId}/student-questions`, { observe: 'response' }) + .get(`${this.resourceUrl}${courseId}/lectures/${lectureId}/student-questions`, { observe: 'response' }) .pipe(map((res: EntityArrayResponseType) => this.convertDateArrayFromServer(res))); } @@ -86,11 +91,12 @@ export class StudentQuestionService { /** * delete studentQuestion by id + * @param {number} courseId * @param {number} studentQuestionId * @return {Observable>} */ - delete(studentQuestionId: number): Observable> { - return this.http.delete(`${this.resourceUrl}/${studentQuestionId}`, { observe: 'response' }); + delete(courseId: number, studentQuestionId: number): Observable> { + return this.http.delete(`${this.resourceUrl}${courseId}/student-questions/${studentQuestionId}`, { observe: 'response' }); } /** @@ -99,15 +105,14 @@ export class StudentQuestionService { * @return {StudentQuestion} */ protected convertDateFromClient(studentQuestion: StudentQuestion): StudentQuestion { - const copy: StudentQuestion = Object.assign({}, studentQuestion, { + return Object.assign({}, studentQuestion, { creationDate: studentQuestion.creationDate && moment(studentQuestion.creationDate).isValid() ? moment(studentQuestion.creationDate).toJSON() : undefined, }); - return copy; } /** * Takes a studentQuestion and converts the date from the server - * @param {StudentQuestion} studentQuestion + * @param {EntityResponseType} res * @return {StudentQuestion} */ protected convertDateFromServer(res: EntityResponseType): EntityResponseType { diff --git a/src/main/webapp/app/overview/student-questions/student-questions.component.ts b/src/main/webapp/app/overview/student-questions/student-questions.component.ts index 98a0ffaa3669..d9c99eb136f6 100644 --- a/src/main/webapp/app/overview/student-questions/student-questions.component.ts +++ b/src/main/webapp/app/overview/student-questions/student-questions.component.ts @@ -30,6 +30,7 @@ export class StudentQuestionsComponent implements OnInit, AfterViewInit { isAtLeastTutorInCourse: boolean; EditorMode = EditorMode; domainCommands = [new KatexCommand()]; + courseId: number; constructor(private accountService: AccountService, private studentQuestionService: StudentQuestionService, private exerciseService: ExerciseService) {} @@ -42,12 +43,14 @@ export class StudentQuestionsComponent implements OnInit, AfterViewInit { }); if (this.exercise) { // in this case the student questions are preloaded - this.studentQuestions = this.sortStudentQuestionsByVote(this.exercise.studentQuestions!); + this.studentQuestions = StudentQuestionsComponent.sortStudentQuestionsByVote(this.exercise.studentQuestions!); this.isAtLeastTutorInCourse = this.accountService.isAtLeastTutorInCourse(this.exercise.course!); + this.courseId = this.exercise.course!.id!; } else { // in this case the student questions are preloaded - this.studentQuestions = this.sortStudentQuestionsByVote(this.lecture.studentQuestions!); + this.studentQuestions = StudentQuestionsComponent.sortStudentQuestionsByVote(this.lecture.studentQuestions!); this.isAtLeastTutorInCourse = this.accountService.isAtLeastTutorInCourse(this.lecture.course!); + this.courseId = this.lecture.course!.id!; } } @@ -117,14 +120,14 @@ export class StudentQuestionsComponent implements OnInit, AfterViewInit { delete studentQuestion.lecture.attachments; } studentQuestion.creationDate = moment(); - this.studentQuestionService.create(studentQuestion).subscribe((studentQuestionResponse: HttpResponse) => { + this.studentQuestionService.create(this.courseId, studentQuestion).subscribe((studentQuestionResponse: HttpResponse) => { this.studentQuestions.push(studentQuestionResponse.body!); this.studentQuestionText = undefined; this.isEditMode = false; }); } - private sortStudentQuestionsByVote(studentQuestions: StudentQuestion[]): StudentQuestion[] { + private static sortStudentQuestionsByVote(studentQuestions: StudentQuestion[]): StudentQuestion[] { return studentQuestions.sort((a, b) => { return b.votes! - a.votes!; }); @@ -135,6 +138,6 @@ export class StudentQuestionsComponent implements OnInit, AfterViewInit { return question.id === studentQuestion.id; }); this.studentQuestions[indexToUpdate] = studentQuestion; - this.studentQuestions = this.sortStudentQuestionsByVote(this.studentQuestions); + this.studentQuestions = StudentQuestionsComponent.sortStudentQuestionsByVote(this.studentQuestions); } } diff --git a/src/test/java/de/tum/in/www1/artemis/StudentQuestionAnswerIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/StudentQuestionAnswerIntegrationTest.java index 1d712fffa4fd..57a91faec398 100644 --- a/src/test/java/de/tum/in/www1/artemis/StudentQuestionAnswerIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/StudentQuestionAnswerIntegrationTest.java @@ -49,18 +49,56 @@ public void tearDown() { @Test @WithMockUser(username = "instructor1", roles = "INSTRUCTOR") - public void createStudentQuestionAnswer() throws Exception { + public void createStudentQuestionAnswerAsInstructor() throws Exception { StudentQuestion studentQuestion = database.createCourseWithExerciseAndStudentQuestions().get(0); StudentQuestionAnswer studentQuestionAnswer = new StudentQuestionAnswer(); - studentQuestionAnswer.setAuthor(database.getUserByLogin("tutor1")); + studentQuestionAnswer.setAuthor(database.getUserByLogin("instructor1")); studentQuestionAnswer.setAnswerText("Test Answer"); studentQuestionAnswer.setAnswerDate(ZonedDateTime.now()); studentQuestionAnswer.setQuestion(studentQuestion); - StudentQuestionAnswer response = request.postWithResponseBody("/api/student-question-answers", studentQuestionAnswer, StudentQuestionAnswer.class, HttpStatus.CREATED); + StudentQuestionAnswer response = request.postWithResponseBody("/api/courses/" + studentQuestion.getCourse().getId() + "/student-question-answers", studentQuestionAnswer, + StudentQuestionAnswer.class, HttpStatus.CREATED); + // should be automatically approved + assertThat(response.isTutorApproved()).isTrue(); // trying to create same studentQuestionAnswer again --> bad request - request.postWithResponseBody("/api/student-question-answers", response, StudentQuestionAnswer.class, HttpStatus.BAD_REQUEST); + request.postWithResponseBody("/api/courses/" + studentQuestion.getCourse().getId() + "/student-question-answers", response, StudentQuestionAnswer.class, + HttpStatus.BAD_REQUEST); + } + + @Test + @WithMockUser(username = "tutor1", roles = "TA") + public void createStudentQuestionAnswerAsTA() throws Exception { + StudentQuestion studentQuestion = database.createCourseWithExerciseAndStudentQuestions().get(0); + + StudentQuestionAnswer studentQuestionAnswer = new StudentQuestionAnswer(); + studentQuestionAnswer.setAuthor(database.getUserByLogin("tutor1")); + studentQuestionAnswer.setAnswerText("Test Answer"); + studentQuestionAnswer.setAnswerDate(ZonedDateTime.now()); + studentQuestionAnswer.setQuestion(studentQuestion); + StudentQuestionAnswer response = request.postWithResponseBody("/api/courses/" + studentQuestion.getCourse().getId() + "/student-question-answers", studentQuestionAnswer, + StudentQuestionAnswer.class, HttpStatus.CREATED); + + // shouldn't be automatically approved + assertThat(response.isTutorApproved()).isFalse(); + } + + @Test + @WithMockUser(username = "student1", roles = "USER") + public void createStudentQuestionAnswerAsStudent() throws Exception { + StudentQuestion studentQuestion = database.createCourseWithExerciseAndStudentQuestions().get(0); + + StudentQuestionAnswer studentQuestionAnswer = new StudentQuestionAnswer(); + studentQuestionAnswer.setAuthor(database.getUserByLogin("student1")); + studentQuestionAnswer.setAnswerText("Test Answer"); + studentQuestionAnswer.setAnswerDate(ZonedDateTime.now()); + studentQuestionAnswer.setQuestion(studentQuestion); + StudentQuestionAnswer response = request.postWithResponseBody("/api/courses/" + studentQuestion.getCourse().getId() + "/student-question-answers", studentQuestionAnswer, + StudentQuestionAnswer.class, HttpStatus.CREATED); + + // shouldn't be automatically approved + assertThat(response.isTutorApproved()).isFalse(); } @Test @@ -71,13 +109,15 @@ public void editStudentQuestionAnswer_asInstructor() throws Exception { studentQuestionAnswer.setAuthor(database.getUserByLogin("tutor2")); studentQuestionAnswer.setAnswerText("New Answer Text"); studentQuestionAnswer.setAnswerDate(ZonedDateTime.now().minusHours(1)); - StudentQuestionAnswer updatedStudentQuestionAnswerServer = request.putWithResponseBody("/api/student-question-answers", studentQuestionAnswer, StudentQuestionAnswer.class, + StudentQuestionAnswer updatedStudentQuestionAnswerServer = request.putWithResponseBody( + "/api/courses/" + studentQuestionAnswer.getQuestion().getCourse().getId() + "/student-question-answers", studentQuestionAnswer, StudentQuestionAnswer.class, HttpStatus.OK); assertThat(updatedStudentQuestionAnswerServer).isEqualTo(studentQuestionAnswer); // try to update answer which is not yet on the server (no id) --> bad request StudentQuestionAnswer newStudentQuestionAnswer = new StudentQuestionAnswer(); - StudentQuestionAnswer newStudentQuestionAnswerServer = request.putWithResponseBody("/api/student-question-answers", newStudentQuestionAnswer, StudentQuestionAnswer.class, + StudentQuestionAnswer newStudentQuestionAnswerServer = request.putWithResponseBody( + "/api/courses/" + studentQuestionAnswer.getQuestion().getCourse().getId() + "/student-question-answers", newStudentQuestionAnswer, StudentQuestionAnswer.class, HttpStatus.BAD_REQUEST); assertThat(newStudentQuestionAnswerServer).isNull(); } @@ -93,21 +133,24 @@ public void editStudentQuestionAnswer_asTA() throws Exception { // edit own answer --> OK studentQuestionAnswer_tutor1.setAnswerText("New Answer Text"); studentQuestionAnswer_tutor1.setAnswerDate(ZonedDateTime.now().minusHours(1)); - StudentQuestionAnswer updatedStudentQuestionAnswerServer1 = request.putWithResponseBody("/api/student-question-answers", studentQuestionAnswer_tutor1, + StudentQuestionAnswer updatedStudentQuestionAnswerServer1 = request.putWithResponseBody( + "/api/courses/" + studentQuestionAnswer_tutor1.getQuestion().getCourse().getId() + "/student-question-answers", studentQuestionAnswer_tutor1, StudentQuestionAnswer.class, HttpStatus.OK); assertThat(updatedStudentQuestionAnswerServer1).isEqualTo(studentQuestionAnswer_tutor1); // edit answer of other TA --> OK studentQuestionAnswer_tutor2.setAnswerText("New Answer Text"); studentQuestionAnswer_tutor2.setAnswerDate(ZonedDateTime.now().minusHours(1)); - StudentQuestionAnswer updatedStudentQuestionAnswerServer2 = request.putWithResponseBody("/api/student-question-answers", studentQuestionAnswer_tutor2, + StudentQuestionAnswer updatedStudentQuestionAnswerServer2 = request.putWithResponseBody( + "/api/courses/" + studentQuestionAnswer_tutor2.getQuestion().getCourse().getId() + "/student-question-answers", studentQuestionAnswer_tutor2, StudentQuestionAnswer.class, HttpStatus.OK); assertThat(updatedStudentQuestionAnswerServer2).isEqualTo(studentQuestionAnswer_tutor2); // edit answer of other student --> OK studentQuestionAnswer_student1.setAnswerText("New Answer Text"); studentQuestionAnswer_student1.setAnswerDate(ZonedDateTime.now().minusHours(1)); - StudentQuestionAnswer updatedStudentQuestionAnswerServer3 = request.putWithResponseBody("/api/student-question-answers", studentQuestionAnswer_student1, + StudentQuestionAnswer updatedStudentQuestionAnswerServer3 = request.putWithResponseBody( + "/api/courses/" + studentQuestionAnswer_student1.getQuestion().getCourse().getId() + "/student-question-answers", studentQuestionAnswer_student1, StudentQuestionAnswer.class, HttpStatus.OK); assertThat(updatedStudentQuestionAnswerServer3).isEqualTo(studentQuestionAnswer_student1); } @@ -122,14 +165,16 @@ public void editStudentQuestionAnswer_asStudent() throws Exception { // update own answer --> OK studentQuestionAnswer_student1.setAnswerText("New Answer Text"); studentQuestionAnswer_student1.setAnswerDate(ZonedDateTime.now().minusHours(1)); - StudentQuestionAnswer updatedStudentQuestionAnswerServer1 = request.putWithResponseBody("/api/student-question-answers", studentQuestionAnswer_student1, + StudentQuestionAnswer updatedStudentQuestionAnswerServer1 = request.putWithResponseBody( + "/api/courses/" + studentQuestionAnswer_student1.getQuestion().getCourse().getId() + "/student-question-answers", studentQuestionAnswer_student1, StudentQuestionAnswer.class, HttpStatus.OK); assertThat(updatedStudentQuestionAnswerServer1).isEqualTo(studentQuestionAnswer_student1); // update answer of other user --> forbidden studentQuestionAnswer_tutor1.setAnswerText("New Answer Text"); studentQuestionAnswer_tutor1.setAnswerDate(ZonedDateTime.now().minusHours(1)); - StudentQuestionAnswer updatedStudentQuestionAnswerServer = request.putWithResponseBody("/api/student-question-answers", studentQuestionAnswer_tutor1, + StudentQuestionAnswer updatedStudentQuestionAnswerServer = request.putWithResponseBody( + "/api/courses/" + studentQuestionAnswer_tutor1.getQuestion().getCourse().getId() + "/student-question-answers", studentQuestionAnswer_tutor1, StudentQuestionAnswer.class, HttpStatus.FORBIDDEN); assertThat(updatedStudentQuestionAnswerServer).isNull(); } @@ -139,7 +184,8 @@ public void editStudentQuestionAnswer_asStudent() throws Exception { public void getStudentQuestionAnswer() throws Exception { StudentQuestionAnswer studentQuestionAnswer = createStudentQuestionAnswersOnServer().get(0); - StudentQuestionAnswer returnedStudentQuestionAnswer = request.get("/api/student-question-answers/" + studentQuestionAnswer.getId(), HttpStatus.OK, + StudentQuestionAnswer returnedStudentQuestionAnswer = request.get( + "/api/courses/" + studentQuestionAnswer.getQuestion().getCourse().getId() + "/student-question-answers/" + studentQuestionAnswer.getId(), HttpStatus.OK, StudentQuestionAnswer.class); assertThat(returnedStudentQuestionAnswer).isEqualTo(studentQuestionAnswer); } @@ -151,17 +197,19 @@ public void deleteStudentQuestionAnswer_asInstructor() throws Exception { StudentQuestionAnswer studentQuestionAnswer_tutor1 = answers.get(0); StudentQuestionAnswer studentQuestionAnswer_tutor2 = answers.get(1); - request.delete("/api/student-question-answers/" + studentQuestionAnswer_tutor1.getId(), HttpStatus.OK); + request.delete("/api/courses/" + studentQuestionAnswer_tutor1.getQuestion().getCourse().getId() + "/student-question-answers/" + studentQuestionAnswer_tutor1.getId(), + HttpStatus.OK); assertThat(studentQuestionAnswerRepository.findById(studentQuestionAnswer_tutor1.getId())).isEmpty(); // try to delete not existing answer --> not found - request.delete("/api/student-question-answers/999", HttpStatus.NOT_FOUND); + request.delete("/api/courses/" + studentQuestionAnswer_tutor1.getQuestion().getCourse().getId() + "/student-question-answers/999", HttpStatus.NOT_FOUND); // delete answer without lecture id --> OK StudentQuestion question = studentQuestionAnswer_tutor2.getQuestion(); question.setLecture(null); studentQuestionRepository.save(question); - request.delete("/api/student-question-answers/" + studentQuestionAnswer_tutor2.getId(), HttpStatus.OK); + request.delete("/api/courses/" + studentQuestionAnswer_tutor2.getQuestion().getCourse().getId() + "/student-question-answers/" + studentQuestionAnswer_tutor2.getId(), + HttpStatus.OK); assertThat(studentQuestionAnswerRepository.findById(studentQuestionAnswer_tutor2.getId())).isEmpty(); } @@ -173,11 +221,13 @@ public void deleteStudentQuestionAnswer_AsTA() throws Exception { StudentQuestionAnswer studentQuestionAnswer_student2 = answers.get(3); // delete own answer --> OK - request.delete("/api/student-question-answers/" + studentQuestionAnswer_tutor1.getId(), HttpStatus.OK); + request.delete("/api/courses/" + studentQuestionAnswer_tutor1.getQuestion().getCourse().getId() + "/student-question-answers/" + studentQuestionAnswer_tutor1.getId(), + HttpStatus.OK); assertThat(studentQuestionAnswerRepository.findById(studentQuestionAnswer_tutor1.getId())).isEmpty(); // delete answer of other student --> OK - request.delete("/api/student-question-answers/" + studentQuestionAnswer_student2.getId(), HttpStatus.OK); + request.delete("/api/courses/" + studentQuestionAnswer_student2.getQuestion().getCourse().getId() + "/student-question-answers/" + studentQuestionAnswer_student2.getId(), + HttpStatus.OK); assertThat(studentQuestionAnswerRepository.findById(studentQuestionAnswer_student2.getId())).isEmpty(); } @@ -189,11 +239,13 @@ public void deleteStudentQuestionAnswer_AsStudent() throws Exception { StudentQuestionAnswer studentQuestionAnswer_student2 = answers.get(3); // delete own answer --> OK - request.delete("/api/student-question-answers/" + studentQuestionAnswer_student1.getId(), HttpStatus.OK); + request.delete("/api/courses/" + studentQuestionAnswer_student1.getQuestion().getCourse().getId() + "/student-question-answers/" + studentQuestionAnswer_student1.getId(), + HttpStatus.OK); assertThat(studentQuestionAnswerRepository.findById(studentQuestionAnswer_student1.getId())).isEmpty(); // delete answer of other student --> forbidden - request.delete("/api/student-question-answers/" + studentQuestionAnswer_student2.getId(), HttpStatus.FORBIDDEN); + request.delete("/api/courses/" + studentQuestionAnswer_student2.getQuestion().getCourse().getId() + "/student-question-answers/" + studentQuestionAnswer_student2.getId(), + HttpStatus.FORBIDDEN); assertThat(studentQuestionAnswerRepository.findById(studentQuestionAnswer_student2.getId())).isNotEmpty(); } @@ -205,13 +257,15 @@ public void toggleStudentQuestionAnswerApproved() throws Exception { // approve answer studentQuestionAnswer.setTutorApproved(true); - StudentQuestionAnswer updatedStudentQuestionAnswer1 = request.putWithResponseBody("/api/student-question-answers", studentQuestionAnswer, StudentQuestionAnswer.class, + StudentQuestionAnswer updatedStudentQuestionAnswer1 = request.putWithResponseBody( + "/api/courses/" + studentQuestionAnswer.getQuestion().getCourse().getId() + "/student-question-answers", studentQuestionAnswer, StudentQuestionAnswer.class, HttpStatus.OK); assertThat(updatedStudentQuestionAnswer1).isEqualTo(studentQuestionAnswer); // unapprove answer studentQuestionAnswer.setTutorApproved(false); - StudentQuestionAnswer updatedStudentQuestionAnswer2 = request.putWithResponseBody("/api/student-question-answers", studentQuestionAnswer, StudentQuestionAnswer.class, + StudentQuestionAnswer updatedStudentQuestionAnswer2 = request.putWithResponseBody( + "/api/courses/" + studentQuestionAnswer.getQuestion().getCourse().getId() + "/student-question-answers", studentQuestionAnswer, StudentQuestionAnswer.class, HttpStatus.OK); assertThat(updatedStudentQuestionAnswer2).isEqualTo(studentQuestionAnswer); } diff --git a/src/test/java/de/tum/in/www1/artemis/StudentQuestionIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/StudentQuestionIntegrationTest.java index 0dd6a7de2f82..3ff85b456d4e 100644 --- a/src/test/java/de/tum/in/www1/artemis/StudentQuestionIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/StudentQuestionIntegrationTest.java @@ -54,11 +54,13 @@ public void tearDown() { @Test @WithMockUser(username = "instructor1", roles = "INSTRUCTOR") public void createStudentQuestion() throws Exception { - StudentQuestion studentQuestion = new StudentQuestion(); - studentQuestion.setQuestionText("Test Student Question 1"); - studentQuestion.setVisibleForStudents(true); + StudentQuestion studentQuestion = database.createCourseWithExerciseAndStudentQuestions().get(0); + StudentQuestion studentQuestionToSave = new StudentQuestion(); + studentQuestionToSave.setQuestionText("Test Student Question 1"); + studentQuestionToSave.setVisibleForStudents(true); - StudentQuestion createdStudentQuestion = request.postWithResponseBody("/api/student-questions", studentQuestion, StudentQuestion.class, HttpStatus.CREATED); + StudentQuestion createdStudentQuestion = request.postWithResponseBody("/api/courses/" + studentQuestion.getCourse().getId() + "/student-questions", studentQuestionToSave, + StudentQuestion.class, HttpStatus.CREATED); assertThat(createdStudentQuestion).isNotNull(); } @@ -68,7 +70,7 @@ public void createStudentQuestion() throws Exception { public void createExistingStudentQuestion() throws Exception { StudentQuestion studentQuestion = database.createCourseWithExerciseAndStudentQuestions().get(0); - request.postWithResponseBody("/api/student-questions", studentQuestion, StudentQuestion.class, HttpStatus.BAD_REQUEST); + request.postWithResponseBody("/api/courses/" + studentQuestion.getCourse().getId() + "/student-questions", studentQuestion, StudentQuestion.class, HttpStatus.BAD_REQUEST); } @Test @@ -79,7 +81,8 @@ public void editStudentQuestion_asInstructor() throws Exception { studentQuestion.setVisibleForStudents(false); studentQuestion.setQuestionText("New Test Student Question"); - StudentQuestion updatedStudentQuestion = request.putWithResponseBody("/api/student-questions", studentQuestion, StudentQuestion.class, HttpStatus.OK); + StudentQuestion updatedStudentQuestion = request.putWithResponseBody("/api/courses/" + studentQuestion.getCourse().getId() + "/student-questions", studentQuestion, + StudentQuestion.class, HttpStatus.OK); assertThat(updatedStudentQuestion.getQuestionText().equals("New Test Student Question")); assertThat(updatedStudentQuestion.isVisibleForStudents()).isFalse(); } @@ -92,7 +95,8 @@ public void editStudentQuestion_asTA() throws Exception { studentQuestion.setVisibleForStudents(false); studentQuestion.setQuestionText("New Test Student Question"); - StudentQuestion updatedStudentQuestion = request.putWithResponseBody("/api/student-questions", studentQuestion, StudentQuestion.class, HttpStatus.OK); + StudentQuestion updatedStudentQuestion = request.putWithResponseBody("/api/courses/" + studentQuestion.getCourse().getId() + "/student-questions", studentQuestion, + StudentQuestion.class, HttpStatus.OK); assertThat(updatedStudentQuestion.getQuestionText().equals("New Test Student Question")); assertThat(updatedStudentQuestion.isVisibleForStudents()).isFalse(); } @@ -107,14 +111,16 @@ public void editStudentQuestion_asStudent() throws Exception { // update own question --> OK studentQuestion_student1.setVisibleForStudents(false); studentQuestion_student1.setQuestionText("New Test Student Question"); - StudentQuestion updatedStudentQuestion1 = request.putWithResponseBody("/api/student-questions", studentQuestion_student1, StudentQuestion.class, HttpStatus.OK); + StudentQuestion updatedStudentQuestion1 = request.putWithResponseBody("/api/courses/" + studentQuestion_student1.getCourse().getId() + "/student-questions", + studentQuestion_student1, StudentQuestion.class, HttpStatus.OK); assertThat(updatedStudentQuestion1.getQuestionText().equals("New Test Student Question")); assertThat(updatedStudentQuestion1.isVisibleForStudents()).isFalse(); // update question from another student --> forbidden studentQuestion_student2.setVisibleForStudents(false); studentQuestion_student2.setQuestionText("New Test Student Question"); - StudentQuestion updatedStudentQuestion2 = request.putWithResponseBody("/api/student-questions", studentQuestion_student2, StudentQuestion.class, HttpStatus.FORBIDDEN); + StudentQuestion updatedStudentQuestion2 = request.putWithResponseBody("/api/courses/" + studentQuestion_student2.getCourse().getId() + "/student-questions", + studentQuestion_student2, StudentQuestion.class, HttpStatus.FORBIDDEN); assertThat(updatedStudentQuestion2).isNull(); } @@ -124,7 +130,8 @@ public void getAllStudentQuestionsForExercise() throws Exception { StudentQuestion studentQuestion = database.createCourseWithExerciseAndStudentQuestions().get(0); Long exerciseID = studentQuestion.getExercise().getId(); - List returnedStudentQuestions = request.getList("/api/exercises/" + exerciseID + "/student-questions", HttpStatus.OK, StudentQuestion.class); + List returnedStudentQuestions = request.getList("/api/courses/" + studentQuestion.getCourse().getId() + "/exercises/" + exerciseID + "/student-questions", + HttpStatus.OK, StudentQuestion.class); assertThat(returnedStudentQuestions.size()).isEqualTo(2); } @@ -151,7 +158,8 @@ public void getAllStudentQuestionsForLecture() throws Exception { studentQuestionRepository.save(studentQuestion1); studentQuestionRepository.save(studentQuestion2); - List returnedStudentQuestions = request.getList("/api/lectures/" + lecture1.getId() + "/student-questions", HttpStatus.OK, StudentQuestion.class); + List returnedStudentQuestions = request + .getList("/api/courses/" + studentQuestion1.getCourse().getId() + "/lectures/" + lecture1.getId() + "/student-questions", HttpStatus.OK, StudentQuestion.class); assertThat(returnedStudentQuestions.size()).isEqualTo(2); } @@ -162,16 +170,16 @@ public void deleteStudentQuestions_asInstructor() throws Exception { StudentQuestion studentQuestion = studentQuestions.get(0); StudentQuestion studentQuestion1 = studentQuestions.get(1); - request.delete("/api/student-questions/" + studentQuestion.getId(), HttpStatus.OK); + request.delete("/api/courses/" + studentQuestion.getCourse().getId() + "/student-questions/" + studentQuestion.getId(), HttpStatus.OK); assertThat(studentQuestionRepository.count()).isEqualTo(1); // try to delete not existing question - request.delete("/api/student-questions/999", HttpStatus.NOT_FOUND); + request.delete("/api/courses/" + studentQuestion.getCourse().getId() + "/student-questions/999", HttpStatus.NOT_FOUND); // delete question with no lecture id --> OK studentQuestion1.setLecture(null); studentQuestionRepository.save(studentQuestion1); - request.delete("/api/student-questions/" + studentQuestion1.getId(), HttpStatus.OK); + request.delete("/api/courses/" + studentQuestion1.getCourse().getId() + "/student-questions/" + studentQuestion1.getId(), HttpStatus.OK); assertThat(studentQuestionRepository.count()).isEqualTo(0); } @@ -183,11 +191,11 @@ public void deleteStudentQuestions_asTA() throws Exception { StudentQuestion studentQuestion1_student2 = studentQuestions.get(1); // delete own question --> OK - request.delete("/api/student-questions/" + studentQuestion_student1.getId(), HttpStatus.OK); + request.delete("/api/courses/" + studentQuestion_student1.getCourse().getId() + "/student-questions/" + studentQuestion_student1.getId(), HttpStatus.OK); assertThat(studentQuestionRepository.count()).isEqualTo(1); // delete question from another user --> OK - request.delete("/api/student-questions/" + studentQuestion1_student2.getId(), HttpStatus.OK); + request.delete("/api/courses/" + studentQuestion1_student2.getCourse().getId() + "/student-questions/" + studentQuestion1_student2.getId(), HttpStatus.OK); assertThat(studentQuestionRepository.count()).isEqualTo(0); } @@ -199,11 +207,11 @@ public void deleteStudentQuestions_asStudent() throws Exception { StudentQuestion studentQuestion1_student2 = studentQuestions.get(1); // delete own question --> OK - request.delete("/api/student-questions/" + studentQuestion_student1.getId(), HttpStatus.OK); + request.delete("/api/courses/" + studentQuestion_student1.getCourse().getId() + "/student-questions/" + studentQuestion_student1.getId(), HttpStatus.OK); assertThat(studentQuestionRepository.count()).isEqualTo(1); // delete question from another student --> forbidden - request.delete("/api/student-questions/" + studentQuestion1_student2.getId(), HttpStatus.FORBIDDEN); + request.delete("/api/courses/" + studentQuestion1_student2.getCourse().getId() + "/student-questions/" + studentQuestion1_student2.getId(), HttpStatus.FORBIDDEN); assertThat(studentQuestionRepository.count()).isEqualTo(1); } @@ -212,8 +220,8 @@ public void deleteStudentQuestions_asStudent() throws Exception { public void editStudentQuestionVotes_asInstructor() throws Exception { StudentQuestion studentQuestion = database.createCourseWithExerciseAndStudentQuestions().get(0); - StudentQuestion updatedStudentQuestion = request.putWithResponseBody("/api/student-questions/" + studentQuestion.getId() + "/votes", 1, StudentQuestion.class, - HttpStatus.OK); + StudentQuestion updatedStudentQuestion = request.putWithResponseBody( + "/api/courses/" + studentQuestion.getCourse().getId() + "/student-questions/" + studentQuestion.getId() + "/votes", 1, StudentQuestion.class, HttpStatus.OK); assertThat(updatedStudentQuestion.getVotes().equals(1)); } @@ -222,8 +230,8 @@ public void editStudentQuestionVotes_asInstructor() throws Exception { public void editStudentQuestionVotes_asTA() throws Exception { StudentQuestion studentQuestion = database.createCourseWithExerciseAndStudentQuestions().get(0); - StudentQuestion updatedStudentQuestion = request.putWithResponseBody("/api/student-questions/" + studentQuestion.getId() + "/votes", -1, StudentQuestion.class, - HttpStatus.OK); + StudentQuestion updatedStudentQuestion = request.putWithResponseBody( + "/api/courses/" + studentQuestion.getCourse().getId() + "/student-questions/" + studentQuestion.getId() + "/votes", -1, StudentQuestion.class, HttpStatus.OK); assertThat(updatedStudentQuestion.getVotes().equals(-1)); } @@ -233,8 +241,8 @@ public void editStudentQuestionVotes_asStudent() throws Exception { List questions = database.createCourseWithExerciseAndStudentQuestions(); StudentQuestion studentQuestion = questions.get(0); - StudentQuestion updatedStudentQuestion = request.putWithResponseBody("/api/student-questions/" + studentQuestion.getId() + "/votes", 2, StudentQuestion.class, - HttpStatus.OK); + StudentQuestion updatedStudentQuestion = request.putWithResponseBody( + "/api/courses/" + studentQuestion.getCourse().getId() + "/student-questions/" + studentQuestion.getId() + "/votes", 2, StudentQuestion.class, HttpStatus.OK); assertThat(updatedStudentQuestion.getVotes().equals(2)); } diff --git a/src/test/java/de/tum/in/www1/artemis/util/DatabaseUtilService.java b/src/test/java/de/tum/in/www1/artemis/util/DatabaseUtilService.java index 30fa8cda2b52..0ea9495d1021 100644 --- a/src/test/java/de/tum/in/www1/artemis/util/DatabaseUtilService.java +++ b/src/test/java/de/tum/in/www1/artemis/util/DatabaseUtilService.java @@ -530,7 +530,7 @@ public List createCourseWithExerciseAndStudentQuestions() { ZonedDateTime futureTimestamp = ZonedDateTime.now().plusDays(5); ZonedDateTime futureFutureTimestamp = ZonedDateTime.now().plusDays(8); - Course course1 = ModelFactory.generateCourse(null, pastTimestamp, futureTimestamp, new HashSet<>(), "tumuser", "tutor", "instructor"); + Course course1 = createCourse(); TextExercise textExercise = ModelFactory.generateTextExercise(pastTimestamp, futureTimestamp, futureFutureTimestamp, course1); textExercise.setGradingInstructions("some grading instructions"); diff --git a/src/test/javascript/spec/component/student-questions/student-question-answer.component.spec.ts b/src/test/javascript/spec/component/student-questions/student-question-answer.component.spec.ts index b7d21fecb5b3..ef82774ad989 100644 --- a/src/test/javascript/spec/component/student-questions/student-question-answer.component.spec.ts +++ b/src/test/javascript/spec/component/student-questions/student-question-answer.component.spec.ts @@ -1,5 +1,6 @@ import * as chai from 'chai'; import * as sinonChai from 'sinon-chai'; +import { ActivatedRoute } from '@angular/router'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { TranslateModule } from '@ngx-translate/core'; import { StudentQuestionAnswerComponent } from 'app/overview/student-questions/student-question-answer/student-question-answer.component'; @@ -7,6 +8,7 @@ import { StudentQuestionAnswer } from 'app/entities/student-question-answer.mode import { User } from 'app/core/user/user.model'; import { ArtemisTestModule } from '../../test.module'; import { ArtemisSharedModule } from 'app/shared/shared.module'; +import { MockActivatedRouteWithSubjects } from '../../helpers/mocks/activated-route/mock-activated-route-with-subjects'; chai.use(sinonChai); const expect = chai.expect; @@ -43,6 +45,7 @@ describe('StudentQuestionAnswerComponent', () => { return TestBed.configureTestingModule({ imports: [TranslateModule.forRoot(), ArtemisTestModule, ArtemisSharedModule], declarations: [StudentQuestionAnswerComponent], + providers: [{ provide: ActivatedRoute, useClass: MockActivatedRouteWithSubjects }], }) .overrideTemplate(StudentQuestionAnswerComponent, '') .compileComponents() @@ -54,61 +57,44 @@ describe('StudentQuestionAnswerComponent', () => { it('should be author of answer', () => { component.studentQuestionAnswer = approvedStudentQuestionAnswer; - componentFixture.detectChanges(); component.user = user2; - componentFixture.detectChanges(); expect(component.isAuthorOfAnswer(approvedStudentQuestionAnswer)).to.be.true; }); it('should not be author of answer', () => { component.studentQuestionAnswer = approvedStudentQuestionAnswer; - componentFixture.detectChanges(); component.user = user2; - componentFixture.detectChanges(); expect(component.isAuthorOfAnswer(unApprovedStudentQuestionAnswer)).to.be.false; }); it('should approve answer', () => { component.studentQuestionAnswer = unApprovedStudentQuestionAnswer; - componentFixture.detectChanges(); component.toggleAnswerTutorApproved(); - componentFixture.detectChanges(); expect(component.studentQuestionAnswer.tutorApproved).to.be.true; }); it('should unapprove answer', () => { component.studentQuestionAnswer = approvedStudentQuestionAnswer; - componentFixture.detectChanges(); component.toggleAnswerTutorApproved(); - componentFixture.detectChanges(); expect(component.studentQuestionAnswer.tutorApproved).to.be.false; }); it('should toggle edit mode and reset editor Text', () => { component.studentQuestionAnswer = approvedStudentQuestionAnswer; - componentFixture.detectChanges(); component.isEditMode = true; - componentFixture.detectChanges(); component.editText = 'test'; - componentFixture.detectChanges(); component.toggleEditMode(); - componentFixture.detectChanges(); expect(component.editText).to.deep.equal('approved'); expect(component.isEditMode).to.be.false; component.toggleEditMode(); - componentFixture.detectChanges(); expect(component.isEditMode).to.be.true; }); it('should update answerText', () => { component.studentQuestionAnswer = approvedStudentQuestionAnswer; - componentFixture.detectChanges(); component.isEditMode = true; - componentFixture.detectChanges(); component.editText = 'test'; - componentFixture.detectChanges(); component.saveAnswer(); - componentFixture.detectChanges(); expect(component.studentQuestionAnswer.answerText).to.deep.equal('test'); }); }); diff --git a/src/test/javascript/spec/component/student-questions/student-question-row.component.spec.ts b/src/test/javascript/spec/component/student-questions/student-question-row.component.spec.ts index f92e18d40dd0..cf133c531a31 100644 --- a/src/test/javascript/spec/component/student-questions/student-question-row.component.spec.ts +++ b/src/test/javascript/spec/component/student-questions/student-question-row.component.spec.ts @@ -1,5 +1,6 @@ import * as chai from 'chai'; import * as sinonChai from 'sinon-chai'; +import { ActivatedRoute } from '@angular/router'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { TranslateModule } from '@ngx-translate/core'; import { StudentQuestionRowComponent } from 'app/overview/student-questions/student-question-row/student-question-row.component'; @@ -10,6 +11,7 @@ import { ArtemisTestModule } from '../../test.module'; import { ArtemisSharedModule } from 'app/shared/shared.module'; import { MockSyncStorage } from '../../helpers/mocks/service/mock-sync-storage.service'; import { LocalStorageService } from 'ngx-webstorage'; +import { MockActivatedRouteWithSubjects } from '../../helpers/mocks/activated-route/mock-activated-route-with-subjects'; chai.use(sinonChai); const expect = chai.expect; @@ -51,7 +53,10 @@ describe('StudentQuestionRowComponent', () => { beforeEach(async () => { return TestBed.configureTestingModule({ imports: [TranslateModule.forRoot(), ArtemisTestModule, ArtemisSharedModule], - providers: [{ provide: LocalStorageService, useClass: MockSyncStorage }], + providers: [ + { provide: LocalStorageService, useClass: MockSyncStorage }, + { provide: ActivatedRoute, useClass: MockActivatedRouteWithSubjects }, + ], declarations: [StudentQuestionRowComponent], }) .overrideTemplate(StudentQuestionRowComponent, '') @@ -64,32 +69,23 @@ describe('StudentQuestionRowComponent', () => { it('should sort in approved and not approved answers', () => { component.studentQuestion = studentQuestion; - componentFixture.detectChanges(); - component.ngOnInit(); - componentFixture.detectChanges(); + component.sortQuestionAnswers(); expect(component.approvedQuestionAnswers).to.deep.equal([approvedStudentQuestionAnswer]); expect(component.sortedQuestionAnswers).to.deep.equal([unApprovedStudentQuestionAnswer]); }); it('should delete studentQuestionAnswer from list', () => { component.studentQuestion = studentQuestion; - componentFixture.detectChanges(); - component.ngOnInit(); - componentFixture.detectChanges(); + component.sortQuestionAnswers(); component.deleteAnswerFromList(unApprovedStudentQuestionAnswer); - componentFixture.detectChanges(); expect(component.studentQuestion.answers).to.deep.equal([approvedStudentQuestionAnswer]); }); it('should add studentQuestionAnswer to list', () => { component.studentQuestion = studentQuestion; - componentFixture.detectChanges(); - component.ngOnInit(); - componentFixture.detectChanges(); + component.sortQuestionAnswers(); component.studentQuestion.answers = [approvedStudentQuestionAnswer]; - componentFixture.detectChanges(); component.addAnswerToList(unApprovedStudentQuestionAnswer); - componentFixture.detectChanges(); expect(component.studentQuestion.answers).to.deep.equal([approvedStudentQuestionAnswer, unApprovedStudentQuestionAnswer]); }); }); diff --git a/src/test/javascript/spec/component/student-questions/student-questions.component.spec.ts b/src/test/javascript/spec/component/student-questions/student-questions.component.spec.ts index 6907da134d35..67fe2b0a96e0 100644 --- a/src/test/javascript/spec/component/student-questions/student-questions.component.spec.ts +++ b/src/test/javascript/spec/component/student-questions/student-questions.component.spec.ts @@ -1,5 +1,6 @@ import * as chai from 'chai'; import * as sinonChai from 'sinon-chai'; +import { ActivatedRoute } from '@angular/router'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { TranslateModule } from '@ngx-translate/core'; import { AccountService } from 'app/core/auth/account.service'; @@ -10,6 +11,8 @@ import { StudentQuestionAnswer } from 'app/entities/student-question-answer.mode import { StudentQuestion } from 'app/entities/student-question.model'; import { ArtemisTestModule } from '../../test.module'; import { ArtemisSharedModule } from 'app/shared/shared.module'; +import { MockActivatedRouteWithSubjects } from '../../helpers/mocks/activated-route/mock-activated-route-with-subjects'; +import { Course } from 'app/entities/course.model'; chai.use(sinonChai); const expect = chai.expect; @@ -18,6 +21,10 @@ describe('StudentQuestionRowComponent', () => { let component: StudentQuestionsComponent; let componentFixture: ComponentFixture; + const course = { + id: 1, + } as Course; + const unApprovedStudentQuestionAnswer = { id: 1, answerDate: undefined, @@ -52,12 +59,16 @@ describe('StudentQuestionRowComponent', () => { endDate: undefined, studentQuestions: [studentQuestion1, studentQuestion2], isAtLeastInstructor: true, + course, } as Lecture; beforeEach(async () => { return TestBed.configureTestingModule({ imports: [TranslateModule.forRoot(), ArtemisTestModule, ArtemisSharedModule], - providers: [{ provide: AccountService, useClass: MockAccountService }], + providers: [ + { provide: AccountService, useClass: MockAccountService }, + { provide: ActivatedRoute, useClass: MockActivatedRouteWithSubjects }, + ], declarations: [StudentQuestionsComponent], }) .overrideTemplate(StudentQuestionsComponent, '') @@ -70,25 +81,14 @@ describe('StudentQuestionRowComponent', () => { it('should set student questions correctly', () => { component.lecture = lectureDefault; - componentFixture.detectChanges(); component.ngOnInit(); - componentFixture.detectChanges(); expect(component.studentQuestions).to.deep.equal([studentQuestion1, studentQuestion2]); }); it('should delete studentQuestion from list', () => { component.lecture = lectureDefault; - componentFixture.detectChanges(); component.ngOnInit(); - componentFixture.detectChanges(); component.deleteQuestionFromList(studentQuestion1); - componentFixture.detectChanges(); expect(component.studentQuestions).to.deep.equal([studentQuestion2]); }); - - it('should at least be instructor', () => { - component.lecture = lectureDefault; - componentFixture.detectChanges(); - expect(component.isAtLeastTutorInCourse).to.be.true; - }); }); diff --git a/src/test/javascript/spec/service/student-question-answer.service.spec.ts b/src/test/javascript/spec/service/student-question-answer.service.spec.ts index fa0dd998ec2f..2faba91108f8 100644 --- a/src/test/javascript/spec/service/student-question-answer.service.spec.ts +++ b/src/test/javascript/spec/service/student-question-answer.service.spec.ts @@ -8,7 +8,7 @@ import { StudentQuestionAnswer } from 'app/entities/student-question-answer.mode const expect = chai.expect; -describe('ExerciseHint Service', () => { +describe('StudentQuestionAnswer Service', () => { let injector: TestBed; let service: StudentQuestionAnswerService; let httpMock: HttpTestingController; @@ -34,7 +34,7 @@ describe('ExerciseHint Service', () => { it('should find a StudentQuestionAnswer', async () => { const returnedFromService = { ...elemDefault }; service - .find(123) + .find(1, 123) .pipe(take(1)) .subscribe((resp) => (expectedResult = resp)); @@ -47,7 +47,7 @@ describe('ExerciseHint Service', () => { const returnedFromService = { ...elemDefault, id: 0 }; const expected = { ...returnedFromService }; service - .create(new StudentQuestionAnswer()) + .create(1, new StudentQuestionAnswer()) .pipe(take(1)) .subscribe((resp) => (expectedResult = resp)); const req = httpMock.expectOne({ method: 'POST' }); @@ -59,7 +59,7 @@ describe('ExerciseHint Service', () => { const returnedFromService = { ...elemDefault, answerText: 'This is another test answer' }; const expected = { ...returnedFromService }; service - .update(expected) + .update(1, expected) .pipe(take(1)) .subscribe((resp) => (expectedResult = resp)); const req = httpMock.expectOne({ method: 'PUT' }); @@ -71,7 +71,7 @@ describe('ExerciseHint Service', () => { const returnedFromService = { ...elemDefault, tutorApproved: true }; const expected = { ...returnedFromService }; service - .update(expected) + .update(1, expected) .pipe(take(1)) .subscribe((resp) => (expectedResult = resp)); const req = httpMock.expectOne({ method: 'PUT' }); @@ -80,7 +80,7 @@ describe('ExerciseHint Service', () => { }); it('should delete a StudentQuestionAnswer', async () => { - service.delete(123).subscribe((resp) => (expectedResult = resp.ok)); + service.delete(1, 123).subscribe((resp) => (expectedResult = resp.ok)); const req = httpMock.expectOne({ method: 'DELETE' }); req.flush({ status: 200 }); diff --git a/src/test/javascript/spec/service/student-question.service.spec.ts b/src/test/javascript/spec/service/student-question.service.spec.ts index ab0ead1a597e..4a042f107cbc 100644 --- a/src/test/javascript/spec/service/student-question.service.spec.ts +++ b/src/test/javascript/spec/service/student-question.service.spec.ts @@ -64,7 +64,7 @@ describe('StudentQuestion Service', () => { const returnedFromService = { ...elemDefault, id: 0 }; const expected = { ...returnedFromService }; service - .create(new StudentQuestion()) + .create(1, new StudentQuestion()) .pipe(take(1)) .subscribe((resp) => (expectedResult = resp)); const req = httpMock.expectOne({ method: 'POST' }); @@ -77,7 +77,7 @@ describe('StudentQuestion Service', () => { const expected = { ...returnedFromService }; service - .update(expected) + .update(1, expected) .pipe(take(1)) .subscribe((resp) => (expectedResult = resp)); const req = httpMock.expectOne({ method: 'PUT' }); @@ -86,7 +86,7 @@ describe('StudentQuestion Service', () => { }); it('should delete a StudentQuestion', async () => { - service.delete(123).subscribe((resp) => (expectedResult = resp.ok)); + service.delete(1, 123).subscribe((resp) => (expectedResult = resp.ok)); const req = httpMock.expectOne({ method: 'DELETE' }); req.flush({ status: 200 }); @@ -98,7 +98,7 @@ describe('StudentQuestion Service', () => { const expected = { ...returnedFromService }; service - .updateVotes(expected.id!, 0) + .updateVotes(1, expected.id!, 0) .pipe(take(1)) .subscribe((resp) => (expectedResult = resp)); const req = httpMock.expectOne({ method: 'PUT' });