From fdb569029b0e43a691a5ebd247c4bfcbacc59488 Mon Sep 17 00:00:00 2001 From: tendryAxel Date: Fri, 30 May 2025 14:15:01 +0300 Subject: [PATCH 1/2] chore: implement exams grade stats for a student in a course --- .../endpoint/rest/controller/ExamController.java | 8 ++++++++ .../haapi/endpoint/rest/security/SecurityConf.java | 6 ++++++ .../java/school/hei/haapi/service/ExamService.java | 12 ++++++++++++ .../java/school/hei/haapi/service/GradeService.java | 9 +++++++++ .../java/school/hei/haapi/integration/ExamIT.java | 12 ++++++++++++ 5 files changed, 47 insertions(+) diff --git a/src/main/java/school/hei/haapi/endpoint/rest/controller/ExamController.java b/src/main/java/school/hei/haapi/endpoint/rest/controller/ExamController.java index 0eb26487ae..86e3654fa9 100644 --- a/src/main/java/school/hei/haapi/endpoint/rest/controller/ExamController.java +++ b/src/main/java/school/hei/haapi/endpoint/rest/controller/ExamController.java @@ -14,6 +14,7 @@ import school.hei.haapi.endpoint.rest.mapper.ExamMapper; import school.hei.haapi.endpoint.rest.model.CrupdateExam; import school.hei.haapi.endpoint.rest.model.ExamInfo; +import school.hei.haapi.endpoint.rest.model.StudentCourseGradeStats; import school.hei.haapi.endpoint.rest.model.StudentExamGrade; import school.hei.haapi.model.BoundedPageSize; import school.hei.haapi.model.PageFromOne; @@ -118,4 +119,11 @@ public List getStudentExamsGrade( .map(exam -> examMapper.toRestStudentExamGrade(studentId, exam)) .toList(); } + + @GetMapping("/courses/{course_id}/student/{student_id}/grade/stats") + public StudentCourseGradeStats getStudentCourseGradeStats( + @PathVariable(value = "course_id") String courseId, + @PathVariable(value = "student_id") String studentId) { + return examService.getStudentCourseGradeStats(courseId, studentId); + } } diff --git a/src/main/java/school/hei/haapi/endpoint/rest/security/SecurityConf.java b/src/main/java/school/hei/haapi/endpoint/rest/security/SecurityConf.java index 69b3177130..b1ddc348ee 100644 --- a/src/main/java/school/hei/haapi/endpoint/rest/security/SecurityConf.java +++ b/src/main/java/school/hei/haapi/endpoint/rest/security/SecurityConf.java @@ -204,6 +204,7 @@ req, res, null, forbiddenWithRemoteInfo(req)))) antMatcher(GET, "/courses/*/exams/*/details"), antMatcher(GET, "/courses/*/exams/*/participants/*"), antMatcher(GET, "/courses/*/student/*/exams/grades"), + antMatcher(GET, "/courses/*/student/*/grade/stats"), antMatcher(GET, STUDENT_COURSE), antMatcher(GET, "/comments"), antMatcher(GET, "/students/*/comments"), @@ -862,6 +863,11 @@ req, res, null, forbiddenWithRemoteInfo(req)))) .hasAnyRole(STUDENT.getRole()) .requestMatchers(GET, "/courses/*/student/*/exams/grades") .hasAnyRole(TEACHER.getRole(), MANAGER.getRole(), ADMIN.getRole()) + .requestMatchers( + new SelfMatcher(GET, "/courses/*/student/*/grade/stats", "student")) + .hasAnyRole(STUDENT.getRole()) + .requestMatchers(GET, "/courses/*/student/*/grade/stats") + .hasAnyRole(TEACHER.getRole(), MANAGER.getRole(), ADMIN.getRole()) .requestMatchers(new SelfMatcher(GET, STUDENT_COURSE, "students")) .hasAnyRole(STUDENT.getRole(), ADMIN.getRole()) .requestMatchers(GET, STUDENT_COURSE) diff --git a/src/main/java/school/hei/haapi/service/ExamService.java b/src/main/java/school/hei/haapi/service/ExamService.java index 79bdd0016f..d777309f57 100644 --- a/src/main/java/school/hei/haapi/service/ExamService.java +++ b/src/main/java/school/hei/haapi/service/ExamService.java @@ -11,6 +11,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; +import school.hei.haapi.endpoint.rest.model.StudentCourseGradeStats; import school.hei.haapi.model.BoundedPageSize; import school.hei.haapi.model.Exam; import school.hei.haapi.model.Grade; @@ -93,4 +94,15 @@ public List getAllExams( public List getExamsByCourseId(String courseId) { return examRepository.findExamsByCourseId(courseId); } + + public StudentCourseGradeStats getStudentCourseGradeStats(String courseId, String studentId) { + List exams = getExamsByCourseId(courseId); + double sumScore = + gradeService.scoreSumWithCoefficient( + exams.stream() + .map(exam -> gradeService.getGradeByExamIdAndStudentId(exam.getId(), studentId)) + .toList()); + double sumCoefficient = exams.stream().mapToDouble(Exam::getCoefficient).sum(); + return new StudentCourseGradeStats().average(sumScore / sumCoefficient); + } } diff --git a/src/main/java/school/hei/haapi/service/GradeService.java b/src/main/java/school/hei/haapi/service/GradeService.java index 704e0a4fc3..30a8c58eed 100644 --- a/src/main/java/school/hei/haapi/service/GradeService.java +++ b/src/main/java/school/hei/haapi/service/GradeService.java @@ -83,4 +83,13 @@ private double getExamAverageGrade(String examId) { public ExamGradeStats getExamGradeStats(String examId) { return new ExamGradeStats().average(getExamAverageGrade(examId)); } + + public double scoreSumWithCoefficient(List grades) { + return grades.stream() + .mapToDouble( + grade -> + Optional.ofNullable(grade.getScore()).orElse(0.) + * Optional.ofNullable(grade.getExam().getCoefficient()).orElse(0)) + .sum(); + } } diff --git a/src/test/java/school/hei/haapi/integration/ExamIT.java b/src/test/java/school/hei/haapi/integration/ExamIT.java index a6e7cc0fdc..b9efb8b869 100644 --- a/src/test/java/school/hei/haapi/integration/ExamIT.java +++ b/src/test/java/school/hei/haapi/integration/ExamIT.java @@ -22,6 +22,8 @@ import static school.hei.haapi.integration.conf.TestUtils.exam5; import static school.hei.haapi.integration.conf.TestUtils.grade1; import static school.hei.haapi.integration.conf.TestUtils.grade2; +import static school.hei.haapi.integration.conf.TestUtils.grade3; +import static school.hei.haapi.integration.conf.TestUtils.grade4; import static school.hei.haapi.integration.conf.TestUtils.group1; import static school.hei.haapi.integration.conf.TestUtils.setUpCasdoor; import static school.hei.haapi.integration.conf.TestUtils.setUpCognito; @@ -39,6 +41,7 @@ import school.hei.haapi.endpoint.rest.client.ApiClient; import school.hei.haapi.endpoint.rest.client.ApiException; import school.hei.haapi.endpoint.rest.model.ExamInfo; +import school.hei.haapi.endpoint.rest.model.StudentCourseGradeStats; import school.hei.haapi.endpoint.rest.model.StudentExamGrade; import school.hei.haapi.endpoint.rest.model.StudentGrade; import school.hei.haapi.integration.conf.FacadeITMockedThirdParties; @@ -208,10 +211,19 @@ void manager_create_or_update_exam_ok() throws ApiException { void student_get_grade_for_each_exams_in_cours() throws ApiException { TeachingApi api = new TeachingApi(anApiClient(STUDENT1_TOKEN)); List studentExamsGrade = api.getStudentExamsGrade(COURSE1_ID, STUDENT1_ID); + StudentCourseGradeStats student1Course1GradeStats = + api.getStudentCourseGradeStats(COURSE1_ID, STUDENT1_ID); + // Assert grades assertEquals(grade1().getScore(), studentExamsGrade.getFirst().getScore()); assertEquals(exam1(), studentExamsGrade.getFirst().getExam()); assertEquals(grade2().getScore(), studentExamsGrade.get(1).getScore()); assertEquals(exam2(), studentExamsGrade.get(1).getExam()); + assertEquals(grade3().getScore(), studentExamsGrade.get(2).getScore()); + assertEquals(exam3(), studentExamsGrade.get(2).getExam()); + assertEquals(grade4().getScore(), studentExamsGrade.get(3).getScore()); + assertEquals(exam4(), studentExamsGrade.get(3).getExam()); + // Assert grade stats + assertEquals(9.8, student1Course1GradeStats.getAverage()); } } From add30fc7f556eb5ed90a29ebab4324eeb22ac5c8 Mon Sep 17 00:00:00 2001 From: tendryAxel Date: Fri, 30 May 2025 15:41:16 +0300 Subject: [PATCH 2/2] chore: test ExamService --- .../java/school/hei/haapi/model/Grade.java | 11 +++++ .../hei/haapi/repository/GradeRepository.java | 7 +++ .../school/hei/haapi/service/ExamService.java | 6 +-- .../hei/haapi/service/GradeService.java | 4 ++ .../school/hei/haapi/integration/ExamIT.java | 12 ----- .../hei/haapi/service/ExamServiceTest.java | 48 +++++++++++++++++++ 6 files changed, 72 insertions(+), 16 deletions(-) create mode 100644 src/test/java/school/hei/haapi/service/ExamServiceTest.java diff --git a/src/main/java/school/hei/haapi/model/Grade.java b/src/main/java/school/hei/haapi/model/Grade.java index d1dd0ff219..c0e9dfca53 100644 --- a/src/main/java/school/hei/haapi/model/Grade.java +++ b/src/main/java/school/hei/haapi/model/Grade.java @@ -9,6 +9,8 @@ import jakarta.persistence.Table; import java.io.Serializable; import java.time.Instant; +import java.util.List; +import java.util.Optional; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.EqualsAndHashCode; @@ -49,4 +51,13 @@ public Grade(Exam exam, User student) { this.student = student; this.exam = exam; } + + public static double scoreSumWithCoefficient(List grades) { + return grades.stream() + .mapToDouble( + grade -> + Optional.ofNullable(grade.getScore()).orElse(0.) + * Optional.ofNullable(grade.getExam().getCoefficient()).orElse(0)) + .sum(); + } } diff --git a/src/main/java/school/hei/haapi/repository/GradeRepository.java b/src/main/java/school/hei/haapi/repository/GradeRepository.java index 408f0a5b2a..71f0505135 100644 --- a/src/main/java/school/hei/haapi/repository/GradeRepository.java +++ b/src/main/java/school/hei/haapi/repository/GradeRepository.java @@ -1,5 +1,6 @@ package school.hei.haapi.repository; +import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; @@ -18,4 +19,10 @@ Optional getGradeByExamIdAndStudentRef( @Param("exam_id") String examId, @Param("student_ref") String studentRef); Optional findByExamIdAndStudentId(String examId, String studentId); + + @Query( + "select g from Grade g where g.exam.awardedCourse.course.id = :course_id" + + " and g.student = :user_id ") + List findGradesByCourseIdAndStudentId( + @Param("course_id") String courseId, @Param("user_id") String studentId); } diff --git a/src/main/java/school/hei/haapi/service/ExamService.java b/src/main/java/school/hei/haapi/service/ExamService.java index d777309f57..1a6606f79e 100644 --- a/src/main/java/school/hei/haapi/service/ExamService.java +++ b/src/main/java/school/hei/haapi/service/ExamService.java @@ -2,6 +2,7 @@ import static java.util.stream.Collectors.toUnmodifiableList; import static org.springframework.data.domain.Sort.Direction.DESC; +import static school.hei.haapi.model.Grade.scoreSumWithCoefficient; import java.time.Instant; import java.util.ArrayList; @@ -98,10 +99,7 @@ public List getExamsByCourseId(String courseId) { public StudentCourseGradeStats getStudentCourseGradeStats(String courseId, String studentId) { List exams = getExamsByCourseId(courseId); double sumScore = - gradeService.scoreSumWithCoefficient( - exams.stream() - .map(exam -> gradeService.getGradeByExamIdAndStudentId(exam.getId(), studentId)) - .toList()); + scoreSumWithCoefficient(gradeService.getGradeByCourseIdAndStudentId(courseId, studentId)); double sumCoefficient = exams.stream().mapToDouble(Exam::getCoefficient).sum(); return new StudentCourseGradeStats().average(sumScore / sumCoefficient); } diff --git a/src/main/java/school/hei/haapi/service/GradeService.java b/src/main/java/school/hei/haapi/service/GradeService.java index 30a8c58eed..7e26d0383a 100644 --- a/src/main/java/school/hei/haapi/service/GradeService.java +++ b/src/main/java/school/hei/haapi/service/GradeService.java @@ -92,4 +92,8 @@ public double scoreSumWithCoefficient(List grades) { * Optional.ofNullable(grade.getExam().getCoefficient()).orElse(0)) .sum(); } + + public List getGradeByCourseIdAndStudentId(String courseId, String studentId) { + return gradeRepository.findGradesByCourseIdAndStudentId(courseId, studentId); + } } diff --git a/src/test/java/school/hei/haapi/integration/ExamIT.java b/src/test/java/school/hei/haapi/integration/ExamIT.java index b9efb8b869..a6e7cc0fdc 100644 --- a/src/test/java/school/hei/haapi/integration/ExamIT.java +++ b/src/test/java/school/hei/haapi/integration/ExamIT.java @@ -22,8 +22,6 @@ import static school.hei.haapi.integration.conf.TestUtils.exam5; import static school.hei.haapi.integration.conf.TestUtils.grade1; import static school.hei.haapi.integration.conf.TestUtils.grade2; -import static school.hei.haapi.integration.conf.TestUtils.grade3; -import static school.hei.haapi.integration.conf.TestUtils.grade4; import static school.hei.haapi.integration.conf.TestUtils.group1; import static school.hei.haapi.integration.conf.TestUtils.setUpCasdoor; import static school.hei.haapi.integration.conf.TestUtils.setUpCognito; @@ -41,7 +39,6 @@ import school.hei.haapi.endpoint.rest.client.ApiClient; import school.hei.haapi.endpoint.rest.client.ApiException; import school.hei.haapi.endpoint.rest.model.ExamInfo; -import school.hei.haapi.endpoint.rest.model.StudentCourseGradeStats; import school.hei.haapi.endpoint.rest.model.StudentExamGrade; import school.hei.haapi.endpoint.rest.model.StudentGrade; import school.hei.haapi.integration.conf.FacadeITMockedThirdParties; @@ -211,19 +208,10 @@ void manager_create_or_update_exam_ok() throws ApiException { void student_get_grade_for_each_exams_in_cours() throws ApiException { TeachingApi api = new TeachingApi(anApiClient(STUDENT1_TOKEN)); List studentExamsGrade = api.getStudentExamsGrade(COURSE1_ID, STUDENT1_ID); - StudentCourseGradeStats student1Course1GradeStats = - api.getStudentCourseGradeStats(COURSE1_ID, STUDENT1_ID); - // Assert grades assertEquals(grade1().getScore(), studentExamsGrade.getFirst().getScore()); assertEquals(exam1(), studentExamsGrade.getFirst().getExam()); assertEquals(grade2().getScore(), studentExamsGrade.get(1).getScore()); assertEquals(exam2(), studentExamsGrade.get(1).getExam()); - assertEquals(grade3().getScore(), studentExamsGrade.get(2).getScore()); - assertEquals(exam3(), studentExamsGrade.get(2).getExam()); - assertEquals(grade4().getScore(), studentExamsGrade.get(3).getScore()); - assertEquals(exam4(), studentExamsGrade.get(3).getExam()); - // Assert grade stats - assertEquals(9.8, student1Course1GradeStats.getAverage()); } } diff --git a/src/test/java/school/hei/haapi/service/ExamServiceTest.java b/src/test/java/school/hei/haapi/service/ExamServiceTest.java new file mode 100644 index 0000000000..c71a27f418 --- /dev/null +++ b/src/test/java/school/hei/haapi/service/ExamServiceTest.java @@ -0,0 +1,48 @@ +package school.hei.haapi.service; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; +import static school.hei.haapi.integration.conf.TestUtils.COURSE1_ID; +import static school.hei.haapi.integration.conf.TestUtils.STUDENT1_ID; +import static school.hei.haapi.integration.conf.TestUtils.exam1; +import static school.hei.haapi.integration.conf.TestUtils.exam2; +import static school.hei.haapi.integration.conf.TestUtils.grade1; +import static school.hei.haapi.integration.conf.TestUtils.grade2; + +import java.util.List; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import school.hei.haapi.endpoint.rest.mapper.ExamMapper; +import school.hei.haapi.endpoint.rest.mapper.GradeMapper; +import school.hei.haapi.endpoint.rest.model.StudentCourseGradeStats; +import school.hei.haapi.integration.conf.FacadeITMockedThirdParties; +import school.hei.haapi.repository.ExamRepository; +import school.hei.haapi.repository.GradeRepository; + +class ExamServiceTest extends FacadeITMockedThirdParties { + @Autowired private ExamService subject; + @MockBean private ExamRepository examRepository; + @Autowired private ExamMapper examMapper; + @MockBean private GradeRepository gradeRepository; + @Autowired private GradeMapper gradeMapper; + + @Test + void student_get_grade_for_each_exams_in_cours() { + var exam1 = examMapper.toDomain(exam1(), null); + var exam2 = examMapper.toDomain(exam2(), null); + var grade1 = gradeMapper.toDomain(grade1()); + var grade2 = gradeMapper.toDomain(grade2()); + grade1.setExam(exam1); + grade2.setExam(exam2); + when(examRepository.findExamsByCourseId(anyString())).thenReturn(List.of(exam1, exam2)); + when(gradeRepository.findGradesByCourseIdAndStudentId(anyString(), anyString())) + .thenReturn(List.of(grade1, grade2)); + + StudentCourseGradeStats studentCourseGradeStats = + subject.getStudentCourseGradeStats(COURSE1_ID, STUDENT1_ID); + + assertEquals(6.2, studentCourseGradeStats.getAverage()); + } +}