From 304b9e2dbac2c5b7c5fb2cc25d2e76d9bdb3b5f7 Mon Sep 17 00:00:00 2001 From: Patryk Mazurek Date: Thu, 10 Jul 2025 19:51:32 +0200 Subject: [PATCH 001/116] endpoints schema --- .../java/org/pkwmtt/exam/ExamController.java | 48 +++++++++++++++++++ src/main/java/org/pkwmtt/exam/ExamEntity.java | 4 ++ .../java/org/pkwmtt/exam/ExamService.java | 7 +++ .../java/org/pkwmtt/exam/dto/ExamDto.java | 4 ++ 4 files changed, 63 insertions(+) create mode 100644 src/main/java/org/pkwmtt/exam/ExamController.java create mode 100644 src/main/java/org/pkwmtt/exam/ExamEntity.java create mode 100644 src/main/java/org/pkwmtt/exam/ExamService.java create mode 100644 src/main/java/org/pkwmtt/exam/dto/ExamDto.java diff --git a/src/main/java/org/pkwmtt/exam/ExamController.java b/src/main/java/org/pkwmtt/exam/ExamController.java new file mode 100644 index 0000000..53a4863 --- /dev/null +++ b/src/main/java/org/pkwmtt/exam/ExamController.java @@ -0,0 +1,48 @@ +package org.pkwmtt.exam; + +import lombok.RequiredArgsConstructor; +import org.pkwmtt.exam.dto.ExamDto; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RequiredArgsConstructor +@RequestMapping("/pkwmtt/api/v1") +@RestController +public class ExamController { + + private final ExamService examService; + + @PostMapping("/exams") + public ResponseEntity addExam(@RequestBody ExamDto examDto){ + throw new UnsupportedOperationException("Not supported yet."); +// return ResponseEntity.created().build(); + } + + @PutMapping("/exams") + public ResponseEntity modifyExam(@RequestBody ExamEntity exam){ + throw new UnsupportedOperationException("Not supported yet."); +// return ResponseEntity.noContent().build(); + } + + @DeleteMapping("/exams/{id}") + public ResponseEntity deleteExam(@PathVariable long id){ + throw new UnsupportedOperationException("Not supported yet."); +// return ResponseEntity.noContent().build(); + } + + @GetMapping("/singleExam/{id}") + public ResponseEntity getExam(@PathVariable long id){ + throw new UnsupportedOperationException("Not supported yet."); +// return ResponseEntity.ok(); + } + +// groups format {12K2K04P04L04} + @GetMapping("/exams/{groups}") + public ResponseEntity> getExams(@PathVariable String groups){ + throw new UnsupportedOperationException("Not supported yet."); +// return ResponseEntity.ok(); + } + +} \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/exam/ExamEntity.java b/src/main/java/org/pkwmtt/exam/ExamEntity.java new file mode 100644 index 0000000..248006a --- /dev/null +++ b/src/main/java/org/pkwmtt/exam/ExamEntity.java @@ -0,0 +1,4 @@ +package org.pkwmtt.exam; + +public class ExamEntity { +} diff --git a/src/main/java/org/pkwmtt/exam/ExamService.java b/src/main/java/org/pkwmtt/exam/ExamService.java new file mode 100644 index 0000000..8d1921b --- /dev/null +++ b/src/main/java/org/pkwmtt/exam/ExamService.java @@ -0,0 +1,7 @@ +package org.pkwmtt.exam; + +import org.springframework.stereotype.Service; + +@Service +public class ExamService { +} diff --git a/src/main/java/org/pkwmtt/exam/dto/ExamDto.java b/src/main/java/org/pkwmtt/exam/dto/ExamDto.java new file mode 100644 index 0000000..0e5e6c2 --- /dev/null +++ b/src/main/java/org/pkwmtt/exam/dto/ExamDto.java @@ -0,0 +1,4 @@ +package org.pkwmtt.exam.dto; + +public class ExamDto { +} From f4e1844f282a5e3dbaa0d543d9b079071200debf Mon Sep 17 00:00:00 2001 From: Patryk Mazurek Date: Fri, 11 Jul 2025 19:03:20 +0200 Subject: [PATCH 002/116] add javadocs to endpoints and small fix --- .../java/org/pkwmtt/exam/ExamController.java | 52 ++++++++++++++----- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/pkwmtt/exam/ExamController.java b/src/main/java/org/pkwmtt/exam/ExamController.java index 53a4863..f9eca8f 100644 --- a/src/main/java/org/pkwmtt/exam/ExamController.java +++ b/src/main/java/org/pkwmtt/exam/ExamController.java @@ -8,39 +8,67 @@ import java.util.List; @RequiredArgsConstructor -@RequestMapping("/pkwmtt/api/v1") +@RequestMapping("/pkwmtt/api/v1/exams") @RestController public class ExamController { private final ExamService examService; - @PostMapping("/exams") - public ResponseEntity addExam(@RequestBody ExamDto examDto){ + /** + * @param exam details of exam + * @return + */ + @PostMapping("") + public ResponseEntity addExam(@RequestBody ExamDto exam) { throw new UnsupportedOperationException("Not supported yet."); // return ResponseEntity.created().build(); } - @PutMapping("/exams") - public ResponseEntity modifyExam(@RequestBody ExamEntity exam){ + /** + * @param id of exam or test + * @param exam new details of exam or test + * @return + */ + @PutMapping("/{id}") + public ResponseEntity modifyExam(@PathVariable long id, @RequestBody ExamEntity exam) { throw new UnsupportedOperationException("Not supported yet."); // return ResponseEntity.noContent().build(); } - @DeleteMapping("/exams/{id}") - public ResponseEntity deleteExam(@PathVariable long id){ + /** + * @param id of exam or test + * @return + */ + @DeleteMapping("/{id}") + public ResponseEntity deleteExam(@PathVariable long id) { throw new UnsupportedOperationException("Not supported yet."); // return ResponseEntity.noContent().build(); } - @GetMapping("/singleExam/{id}") - public ResponseEntity getExam(@PathVariable long id){ + /** + * @param id of exam or test + * @return single exam or test details + */ + @GetMapping("/{id}") + public ResponseEntity getExam(@PathVariable long id) { throw new UnsupportedOperationException("Not supported yet."); // return ResponseEntity.ok(); } -// groups format {12K2K04P04L04} - @GetMapping("/exams/{groups}") - public ResponseEntity> getExams(@PathVariable String groups){ + /** + * @param generalGroup symbol that identify exercise group of specific field of study (for example 12K2) + * @param k computer laboratory group + * @param l laboratory group + * @param p project group + * @return + */ + @GetMapping("/by-groups/{generalGroup}") + public ResponseEntity> getExams( + @PathVariable String generalGroup, + @RequestParam(name = "k", required = false) String k, + @RequestParam(name = "l", required = false) String l, + @RequestParam(name = "p", required = false) String p + ) { throw new UnsupportedOperationException("Not supported yet."); // return ResponseEntity.ok(); } From 26492747466ba15d489e24f29cf8b27b082d27d6 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Thu, 31 Jul 2025 22:10:42 +0200 Subject: [PATCH 003/116] update project structure + addExam implementation --- src/main/java/org/pkwmtt/exam/ExamEntity.java | 4 --- .../java/org/pkwmtt/exam/ExamService.java | 7 ----- .../java/org/pkwmtt/exam/dto/ExamDto.java | 4 --- .../ExamController.java | 29 ++++++++++++------- .../org/pkwmtt/examCalendar/ExamService.java | 20 +++++++++++++ .../org/pkwmtt/examCalendar/dto/ExamDto.java | 19 ++++++++++++ .../{ => examCalendar}/entity/Exam.java | 7 +++-- .../{ => examCalendar}/entity/ExamType.java | 2 +- .../entity/GeneralGroup.java | 2 +- .../{ => examCalendar}/entity/Group.java | 2 +- .../{ => examCalendar}/entity/OTPCode.java | 2 +- .../{ => examCalendar}/entity/User.java | 2 +- .../mapper/ExamDtoToExamMapper.java | 17 +++++++++++ .../repository/ExamRepository.java | 4 +-- .../repository/ExamTypeRepository.java | 4 +-- .../repository/GeneralGroupRepository.java | 4 +-- .../repository/GroupRepository.java | 4 +-- .../repository/OTPCodeRepository.java | 4 +-- .../repository/UserRepository.java | 4 +-- 19 files changed, 97 insertions(+), 44 deletions(-) delete mode 100644 src/main/java/org/pkwmtt/exam/ExamEntity.java delete mode 100644 src/main/java/org/pkwmtt/exam/ExamService.java delete mode 100644 src/main/java/org/pkwmtt/exam/dto/ExamDto.java rename src/main/java/org/pkwmtt/{exam => examCalendar}/ExamController.java (70%) create mode 100644 src/main/java/org/pkwmtt/examCalendar/ExamService.java create mode 100644 src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java rename src/main/java/org/pkwmtt/{ => examCalendar}/entity/Exam.java (78%) rename src/main/java/org/pkwmtt/{ => examCalendar}/entity/ExamType.java (90%) rename src/main/java/org/pkwmtt/{ => examCalendar}/entity/GeneralGroup.java (92%) rename src/main/java/org/pkwmtt/{ => examCalendar}/entity/Group.java (92%) rename src/main/java/org/pkwmtt/{ => examCalendar}/entity/OTPCode.java (93%) rename src/main/java/org/pkwmtt/{ => examCalendar}/entity/User.java (93%) create mode 100644 src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java rename src/main/java/org/pkwmtt/{ => examCalendar}/repository/ExamRepository.java (60%) rename src/main/java/org/pkwmtt/{ => examCalendar}/repository/ExamTypeRepository.java (61%) rename src/main/java/org/pkwmtt/{ => examCalendar}/repository/GeneralGroupRepository.java (61%) rename src/main/java/org/pkwmtt/{ => examCalendar}/repository/GroupRepository.java (60%) rename src/main/java/org/pkwmtt/{ => examCalendar}/repository/OTPCodeRepository.java (60%) rename src/main/java/org/pkwmtt/{ => examCalendar}/repository/UserRepository.java (60%) diff --git a/src/main/java/org/pkwmtt/exam/ExamEntity.java b/src/main/java/org/pkwmtt/exam/ExamEntity.java deleted file mode 100644 index 248006a..0000000 --- a/src/main/java/org/pkwmtt/exam/ExamEntity.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.pkwmtt.exam; - -public class ExamEntity { -} diff --git a/src/main/java/org/pkwmtt/exam/ExamService.java b/src/main/java/org/pkwmtt/exam/ExamService.java deleted file mode 100644 index 8d1921b..0000000 --- a/src/main/java/org/pkwmtt/exam/ExamService.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.pkwmtt.exam; - -import org.springframework.stereotype.Service; - -@Service -public class ExamService { -} diff --git a/src/main/java/org/pkwmtt/exam/dto/ExamDto.java b/src/main/java/org/pkwmtt/exam/dto/ExamDto.java deleted file mode 100644 index 0e5e6c2..0000000 --- a/src/main/java/org/pkwmtt/exam/dto/ExamDto.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.pkwmtt.exam.dto; - -public class ExamDto { -} diff --git a/src/main/java/org/pkwmtt/exam/ExamController.java b/src/main/java/org/pkwmtt/examCalendar/ExamController.java similarity index 70% rename from src/main/java/org/pkwmtt/exam/ExamController.java rename to src/main/java/org/pkwmtt/examCalendar/ExamController.java index f9eca8f..a860c88 100644 --- a/src/main/java/org/pkwmtt/exam/ExamController.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamController.java @@ -1,10 +1,13 @@ -package org.pkwmtt.exam; +package org.pkwmtt.examCalendar; import lombok.RequiredArgsConstructor; -import org.pkwmtt.exam.dto.ExamDto; +import org.pkwmtt.examCalendar.dto.ExamDto; +import org.pkwmtt.examCalendar.entity.Exam; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; +import java.net.URI; import java.util.List; @RequiredArgsConstructor @@ -15,13 +18,19 @@ public class ExamController { private final ExamService examService; /** - * @param exam details of exam - * @return + * @param examDto details of exam + * @return 201 created with URI to GET method which returns created resource */ @PostMapping("") - public ResponseEntity addExam(@RequestBody ExamDto exam) { - throw new UnsupportedOperationException("Not supported yet."); -// return ResponseEntity.created().build(); + public ResponseEntity addExam(@RequestBody ExamDto examDto) { + int id = examService.addExam(examDto); + URI uri = ServletUriComponentsBuilder + .fromCurrentRequest() + .path("/{id}") + .buildAndExpand(id) + .toUri(); + return ResponseEntity.created(uri).build(); +// TODO: add data verification } /** @@ -30,7 +39,7 @@ public ResponseEntity addExam(@RequestBody ExamDto exam) { * @return */ @PutMapping("/{id}") - public ResponseEntity modifyExam(@PathVariable long id, @RequestBody ExamEntity exam) { + public ResponseEntity modifyExam(@PathVariable long id, @RequestBody Exam exam) { throw new UnsupportedOperationException("Not supported yet."); // return ResponseEntity.noContent().build(); } @@ -50,7 +59,7 @@ public ResponseEntity deleteExam(@PathVariable long id) { * @return single exam or test details */ @GetMapping("/{id}") - public ResponseEntity getExam(@PathVariable long id) { + public ResponseEntity getExam(@PathVariable long id) { throw new UnsupportedOperationException("Not supported yet."); // return ResponseEntity.ok(); } @@ -63,7 +72,7 @@ public ResponseEntity getExam(@PathVariable long id) { * @return */ @GetMapping("/by-groups/{generalGroup}") - public ResponseEntity> getExams( + public ResponseEntity> getExams( @PathVariable String generalGroup, @RequestParam(name = "k", required = false) String k, @RequestParam(name = "l", required = false) String l, diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamService.java b/src/main/java/org/pkwmtt/examCalendar/ExamService.java new file mode 100644 index 0000000..60490f3 --- /dev/null +++ b/src/main/java/org/pkwmtt/examCalendar/ExamService.java @@ -0,0 +1,20 @@ +package org.pkwmtt.examCalendar; + +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.pkwmtt.examCalendar.dto.ExamDto; +import org.pkwmtt.examCalendar.mapper.ExamDtoToExamMapper; +import org.pkwmtt.examCalendar.repository.ExamRepository; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Transactional +public class ExamService { + + private final ExamRepository examRepository; + + public int addExam(ExamDto examDto) { + return examRepository.save(ExamDtoToExamMapper.mapToExam(examDto)).getExam_id(); + } +} diff --git a/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java b/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java new file mode 100644 index 0000000..e5e7330 --- /dev/null +++ b/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java @@ -0,0 +1,19 @@ +package org.pkwmtt.examCalendar.dto; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.pkwmtt.examCalendar.entity.ExamType; + +import java.time.LocalDateTime; +import java.util.Date; + +@Getter +@RequiredArgsConstructor +public class ExamDto { + + private final String title; + private final String description; + private final LocalDateTime date; + private final String exam_group; + private final ExamType exam_type; +} diff --git a/src/main/java/org/pkwmtt/entity/Exam.java b/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java similarity index 78% rename from src/main/java/org/pkwmtt/entity/Exam.java rename to src/main/java/org/pkwmtt/examCalendar/entity/Exam.java index 68d376c..34dd790 100644 --- a/src/main/java/org/pkwmtt/entity/Exam.java +++ b/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java @@ -1,8 +1,9 @@ -package org.pkwmtt.entity; +package org.pkwmtt.examCalendar.entity; import jakarta.persistence.*; import lombok.*; +import java.time.LocalDateTime; import java.util.Date; @Entity @@ -20,7 +21,7 @@ public class Exam { private String description; - private Date date; + private LocalDateTime date; @Column(name = "`groups`") private String exam_group; @@ -28,4 +29,6 @@ public class Exam { @ManyToOne @JoinColumn(name = "exam_type_id") private ExamType exam_type; + +// TODO: add exam builder } diff --git a/src/main/java/org/pkwmtt/entity/ExamType.java b/src/main/java/org/pkwmtt/examCalendar/entity/ExamType.java similarity index 90% rename from src/main/java/org/pkwmtt/entity/ExamType.java rename to src/main/java/org/pkwmtt/examCalendar/entity/ExamType.java index 9ec4a4f..e994b9b 100644 --- a/src/main/java/org/pkwmtt/entity/ExamType.java +++ b/src/main/java/org/pkwmtt/examCalendar/entity/ExamType.java @@ -1,4 +1,4 @@ -package org.pkwmtt.entity; +package org.pkwmtt.examCalendar.entity; import jakarta.persistence.*; import lombok.AllArgsConstructor; diff --git a/src/main/java/org/pkwmtt/entity/GeneralGroup.java b/src/main/java/org/pkwmtt/examCalendar/entity/GeneralGroup.java similarity index 92% rename from src/main/java/org/pkwmtt/entity/GeneralGroup.java rename to src/main/java/org/pkwmtt/examCalendar/entity/GeneralGroup.java index f03ff42..9e8537a 100644 --- a/src/main/java/org/pkwmtt/entity/GeneralGroup.java +++ b/src/main/java/org/pkwmtt/examCalendar/entity/GeneralGroup.java @@ -1,4 +1,4 @@ -package org.pkwmtt.entity; +package org.pkwmtt.examCalendar.entity; import jakarta.persistence.*; import lombok.AllArgsConstructor; diff --git a/src/main/java/org/pkwmtt/entity/Group.java b/src/main/java/org/pkwmtt/examCalendar/entity/Group.java similarity index 92% rename from src/main/java/org/pkwmtt/entity/Group.java rename to src/main/java/org/pkwmtt/examCalendar/entity/Group.java index cba5eaf..ee5a596 100644 --- a/src/main/java/org/pkwmtt/entity/Group.java +++ b/src/main/java/org/pkwmtt/examCalendar/entity/Group.java @@ -1,4 +1,4 @@ -package org.pkwmtt.entity; +package org.pkwmtt.examCalendar.entity; import jakarta.persistence.*; import lombok.AllArgsConstructor; diff --git a/src/main/java/org/pkwmtt/entity/OTPCode.java b/src/main/java/org/pkwmtt/examCalendar/entity/OTPCode.java similarity index 93% rename from src/main/java/org/pkwmtt/entity/OTPCode.java rename to src/main/java/org/pkwmtt/examCalendar/entity/OTPCode.java index 76d8110..9146c96 100644 --- a/src/main/java/org/pkwmtt/entity/OTPCode.java +++ b/src/main/java/org/pkwmtt/examCalendar/entity/OTPCode.java @@ -1,4 +1,4 @@ -package org.pkwmtt.entity; +package org.pkwmtt.examCalendar.entity; import jakarta.persistence.*; import lombok.AllArgsConstructor; diff --git a/src/main/java/org/pkwmtt/entity/User.java b/src/main/java/org/pkwmtt/examCalendar/entity/User.java similarity index 93% rename from src/main/java/org/pkwmtt/entity/User.java rename to src/main/java/org/pkwmtt/examCalendar/entity/User.java index 448f135..ca28cef 100644 --- a/src/main/java/org/pkwmtt/entity/User.java +++ b/src/main/java/org/pkwmtt/examCalendar/entity/User.java @@ -1,4 +1,4 @@ -package org.pkwmtt.entity; +package org.pkwmtt.examCalendar.entity; import jakarta.persistence.*; import lombok.AllArgsConstructor; diff --git a/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java new file mode 100644 index 0000000..1b6c6c8 --- /dev/null +++ b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java @@ -0,0 +1,17 @@ +package org.pkwmtt.examCalendar.mapper; + +import org.pkwmtt.examCalendar.dto.ExamDto; +import org.pkwmtt.examCalendar.entity.Exam; + +public class ExamDtoToExamMapper { + private ExamDtoToExamMapper() {} + public static Exam mapToExam(ExamDto examDto) { + return Exam.builder() + .title(examDto.getTitle()) + .description(examDto.getDescription()) + .date(examDto.getDate()) + .exam_group(examDto.getExam_group()) + .exam_type(examDto.getExam_type()) + .build(); + } +} diff --git a/src/main/java/org/pkwmtt/repository/ExamRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java similarity index 60% rename from src/main/java/org/pkwmtt/repository/ExamRepository.java rename to src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java index 2faafaa..96f3bca 100644 --- a/src/main/java/org/pkwmtt/repository/ExamRepository.java +++ b/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java @@ -1,6 +1,6 @@ -package org.pkwmtt.repository; +package org.pkwmtt.examCalendar.repository; -import org.pkwmtt.entity.Exam; +import org.pkwmtt.examCalendar.entity.Exam; import org.springframework.data.jpa.repository.JpaRepository; public interface ExamRepository extends JpaRepository { diff --git a/src/main/java/org/pkwmtt/repository/ExamTypeRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/ExamTypeRepository.java similarity index 61% rename from src/main/java/org/pkwmtt/repository/ExamTypeRepository.java rename to src/main/java/org/pkwmtt/examCalendar/repository/ExamTypeRepository.java index 1b7d38c..dfa23e1 100644 --- a/src/main/java/org/pkwmtt/repository/ExamTypeRepository.java +++ b/src/main/java/org/pkwmtt/examCalendar/repository/ExamTypeRepository.java @@ -1,6 +1,6 @@ -package org.pkwmtt.repository; +package org.pkwmtt.examCalendar.repository; -import org.pkwmtt.entity.ExamType; +import org.pkwmtt.examCalendar.entity.ExamType; import org.springframework.data.jpa.repository.JpaRepository; public interface ExamTypeRepository extends JpaRepository { diff --git a/src/main/java/org/pkwmtt/repository/GeneralGroupRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/GeneralGroupRepository.java similarity index 61% rename from src/main/java/org/pkwmtt/repository/GeneralGroupRepository.java rename to src/main/java/org/pkwmtt/examCalendar/repository/GeneralGroupRepository.java index a4c1c55..62f4fbb 100644 --- a/src/main/java/org/pkwmtt/repository/GeneralGroupRepository.java +++ b/src/main/java/org/pkwmtt/examCalendar/repository/GeneralGroupRepository.java @@ -1,6 +1,6 @@ -package org.pkwmtt.repository; +package org.pkwmtt.examCalendar.repository; -import org.pkwmtt.entity.GeneralGroup; +import org.pkwmtt.examCalendar.entity.GeneralGroup; import org.springframework.data.jpa.repository.JpaRepository; public interface GeneralGroupRepository extends JpaRepository { diff --git a/src/main/java/org/pkwmtt/repository/GroupRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/GroupRepository.java similarity index 60% rename from src/main/java/org/pkwmtt/repository/GroupRepository.java rename to src/main/java/org/pkwmtt/examCalendar/repository/GroupRepository.java index ae46110..98bb7a3 100644 --- a/src/main/java/org/pkwmtt/repository/GroupRepository.java +++ b/src/main/java/org/pkwmtt/examCalendar/repository/GroupRepository.java @@ -1,6 +1,6 @@ -package org.pkwmtt.repository; +package org.pkwmtt.examCalendar.repository; -import org.pkwmtt.entity.Group; +import org.pkwmtt.examCalendar.entity.Group; import org.springframework.data.jpa.repository.JpaRepository; public interface GroupRepository extends JpaRepository { diff --git a/src/main/java/org/pkwmtt/repository/OTPCodeRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/OTPCodeRepository.java similarity index 60% rename from src/main/java/org/pkwmtt/repository/OTPCodeRepository.java rename to src/main/java/org/pkwmtt/examCalendar/repository/OTPCodeRepository.java index 4f79485..848b4d4 100644 --- a/src/main/java/org/pkwmtt/repository/OTPCodeRepository.java +++ b/src/main/java/org/pkwmtt/examCalendar/repository/OTPCodeRepository.java @@ -1,6 +1,6 @@ -package org.pkwmtt.repository; +package org.pkwmtt.examCalendar.repository; -import org.pkwmtt.entity.OTPCode; +import org.pkwmtt.examCalendar.entity.OTPCode; import org.springframework.data.jpa.repository.JpaRepository; public interface OTPCodeRepository extends JpaRepository { diff --git a/src/main/java/org/pkwmtt/repository/UserRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/UserRepository.java similarity index 60% rename from src/main/java/org/pkwmtt/repository/UserRepository.java rename to src/main/java/org/pkwmtt/examCalendar/repository/UserRepository.java index 71ccd75..acdf767 100644 --- a/src/main/java/org/pkwmtt/repository/UserRepository.java +++ b/src/main/java/org/pkwmtt/examCalendar/repository/UserRepository.java @@ -1,6 +1,6 @@ -package org.pkwmtt.repository; +package org.pkwmtt.examCalendar.repository; -import org.pkwmtt.entity.User; +import org.pkwmtt.examCalendar.entity.User; import org.springframework.data.jpa.repository.JpaRepository; public interface UserRepository extends JpaRepository { From 3d10cce770e3128764f02c68e1c2487d21c41188 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Fri, 1 Aug 2025 16:05:50 +0200 Subject: [PATCH 004/116] fix everything --- init.sql | 38 ++++--------------- .../org/pkwmtt/examCalendar/ExamService.java | 7 +++- .../org/pkwmtt/examCalendar/dto/ExamDto.java | 6 +-- .../org/pkwmtt/examCalendar/entity/Exam.java | 10 ++--- .../pkwmtt/examCalendar/entity/ExamType.java | 8 ++-- .../examCalendar/entity/GeneralGroup.java | 8 ++-- .../org/pkwmtt/examCalendar/entity/Group.java | 8 ++-- .../pkwmtt/examCalendar/entity/OTPCode.java | 8 ++-- .../org/pkwmtt/examCalendar/entity/User.java | 8 ++-- .../mapper/ExamDtoToExamMapper.java | 18 +++++++-- .../repository/ExamTypeRepository.java | 3 ++ 11 files changed, 52 insertions(+), 70 deletions(-) diff --git a/init.sql b/init.sql index df5e8a1..ee4fcb3 100644 --- a/init.sql +++ b/init.sql @@ -3,7 +3,7 @@ -- https://www.phpmyadmin.net/ -- -- Host: db --- Generation Time: Lip 31, 2025 at 06:56 PM +-- Generation Time: Aug 01, 2025 at 01:55 PM -- Wersja serwera: 9.3.0 -- Wersja PHP: 8.2.27 @@ -39,11 +39,6 @@ CREATE TABLE `exams` ( `exam_type_id` int NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; --- --- Tabela Truncate przed wstawieniem `exams` --- - -TRUNCATE TABLE `exams`; -- -- Zrzut danych tabeli `exams` -- @@ -65,11 +60,6 @@ CREATE TABLE `exam_type` ( `name` varchar(255) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; --- --- Tabela Truncate przed wstawieniem `exam_type` --- - -TRUNCATE TABLE `exam_type`; -- -- Zrzut danych tabeli `exam_type` -- @@ -91,11 +81,6 @@ CREATE TABLE `general_group` ( `name` varchar(255) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; --- --- Tabela Truncate przed wstawieniem `general_group` --- - -TRUNCATE TABLE `general_group`; -- -- Zrzut danych tabeli `general_group` -- @@ -121,11 +106,6 @@ CREATE TABLE `groups` ( `name` varchar(255) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; --- --- Tabela Truncate przed wstawieniem `groups` --- - -TRUNCATE TABLE `groups`; -- -- Zrzut danych tabeli `groups` -- @@ -156,11 +136,6 @@ CREATE TABLE `otp_codes` ( `timestamp` datetime(6) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; --- --- Tabela Truncate przed wstawieniem `otp_codes` --- - -TRUNCATE TABLE `otp_codes`; -- -------------------------------------------------------- -- @@ -176,11 +151,6 @@ CREATE TABLE `users` ( `role` enum('ADMIN','REPRESENTATIVE') NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; --- --- Tabela Truncate przed wstawieniem `users` --- - -TRUNCATE TABLE `users`; -- -- Zrzut danych tabeli `users` -- @@ -241,6 +211,12 @@ ALTER TABLE `users` -- AUTO_INCREMENT dla zrzuconych tabel -- +-- +-- AUTO_INCREMENT dla tabeli `exams` +-- +ALTER TABLE `exams` + MODIFY `exam_id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=9; + -- -- AUTO_INCREMENT dla tabeli `exam_type` -- diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamService.java b/src/main/java/org/pkwmtt/examCalendar/ExamService.java index 60490f3..4787ef8 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamService.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamService.java @@ -13,8 +13,13 @@ public class ExamService { private final ExamRepository examRepository; + private final ExamDtoToExamMapper examMapper; + /** + * @param examDto details of exam + * @return id of exam added to database + */ public int addExam(ExamDto examDto) { - return examRepository.save(ExamDtoToExamMapper.mapToExam(examDto)).getExam_id(); + return examRepository.save(examMapper.mapToExam(examDto)).getExamId(); } } diff --git a/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java b/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java index e5e7330..3490c91 100644 --- a/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java +++ b/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java @@ -2,10 +2,8 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; -import org.pkwmtt.examCalendar.entity.ExamType; import java.time.LocalDateTime; -import java.util.Date; @Getter @RequiredArgsConstructor @@ -14,6 +12,6 @@ public class ExamDto { private final String title; private final String description; private final LocalDateTime date; - private final String exam_group; - private final ExamType exam_type; + private final String examGroup; + private final String examType; } diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java b/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java index 34dd790..80d7ec1 100644 --- a/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java +++ b/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java @@ -10,12 +10,12 @@ @Getter @Builder @RequiredArgsConstructor -@Table(name = "`exams`") +@Table(name = "exams") @AllArgsConstructor public class Exam { @Id - @GeneratedValue(strategy = GenerationType.AUTO) - private Integer exam_id; + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer examId; private String title; @@ -24,11 +24,11 @@ public class Exam { private LocalDateTime date; @Column(name = "`groups`") - private String exam_group; + private String examGroups; @ManyToOne @JoinColumn(name = "exam_type_id") - private ExamType exam_type; + private ExamType examType; // TODO: add exam builder } diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/ExamType.java b/src/main/java/org/pkwmtt/examCalendar/entity/ExamType.java index e994b9b..14a8549 100644 --- a/src/main/java/org/pkwmtt/examCalendar/entity/ExamType.java +++ b/src/main/java/org/pkwmtt/examCalendar/entity/ExamType.java @@ -4,20 +4,18 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; +import lombok.RequiredArgsConstructor; @Entity @Getter @Builder @AllArgsConstructor +@RequiredArgsConstructor @Table(name = "`exam_type`") public class ExamType { @Id - @GeneratedValue(strategy = GenerationType.AUTO) + @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer exam_type_id; private String name; - - public ExamType() { - - } } \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/GeneralGroup.java b/src/main/java/org/pkwmtt/examCalendar/entity/GeneralGroup.java index 9e8537a..3eb27af 100644 --- a/src/main/java/org/pkwmtt/examCalendar/entity/GeneralGroup.java +++ b/src/main/java/org/pkwmtt/examCalendar/entity/GeneralGroup.java @@ -4,6 +4,7 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import java.util.Set; @@ -11,18 +12,15 @@ @Getter @Builder @AllArgsConstructor +@NoArgsConstructor @Table(name = "`general_group`") public class GeneralGroup { @Id - @GeneratedValue(strategy = GenerationType.AUTO) + @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer general_group_id; private String name; @OneToMany(mappedBy = "general_group") private Set groups; - - public GeneralGroup() { - - } } diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/Group.java b/src/main/java/org/pkwmtt/examCalendar/entity/Group.java index ee5a596..050a5cc 100644 --- a/src/main/java/org/pkwmtt/examCalendar/entity/Group.java +++ b/src/main/java/org/pkwmtt/examCalendar/entity/Group.java @@ -4,15 +4,17 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; @Entity @Getter @Builder @AllArgsConstructor +@NoArgsConstructor @Table(name = "`groups`") public class Group { @Id - @GeneratedValue(strategy = GenerationType.AUTO) + @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer group_id; private String name; @@ -22,8 +24,4 @@ public class Group { @ManyToOne @JoinColumn(name = "general_group_id") private GeneralGroup general_group; - - public Group() { - - } } diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/OTPCode.java b/src/main/java/org/pkwmtt/examCalendar/entity/OTPCode.java index 9146c96..47d3a9a 100644 --- a/src/main/java/org/pkwmtt/examCalendar/entity/OTPCode.java +++ b/src/main/java/org/pkwmtt/examCalendar/entity/OTPCode.java @@ -4,6 +4,7 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import java.time.LocalDateTime; @@ -11,10 +12,11 @@ @Getter @Builder @AllArgsConstructor +@NoArgsConstructor @Table(name = "otp_codes") public class OTPCode { @Id - @GeneratedValue(strategy = GenerationType.AUTO) + @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer otp_code_id; private String code; @@ -26,8 +28,4 @@ public class OTPCode { @OneToOne @JoinColumn(name = "user_id", unique = true) private User user; - - public OTPCode() { - - } } diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/User.java b/src/main/java/org/pkwmtt/examCalendar/entity/User.java index ca28cef..cb90f87 100644 --- a/src/main/java/org/pkwmtt/examCalendar/entity/User.java +++ b/src/main/java/org/pkwmtt/examCalendar/entity/User.java @@ -4,16 +4,18 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import org.pkwmtt.enums.Role; @Entity @Getter @Builder @AllArgsConstructor +@NoArgsConstructor @Table(name = "`users`") public class User { @Id - @GeneratedValue(strategy = GenerationType.AUTO) + @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer user_id; @ManyToOne @@ -28,8 +30,4 @@ public class User { @OneToOne(mappedBy = "user") private OTPCode otp_code; - - public User() { - - } } diff --git a/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java index 1b6c6c8..a67a5f6 100644 --- a/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java +++ b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java @@ -1,17 +1,27 @@ package org.pkwmtt.examCalendar.mapper; +import lombok.RequiredArgsConstructor; import org.pkwmtt.examCalendar.dto.ExamDto; import org.pkwmtt.examCalendar.entity.Exam; +import org.pkwmtt.examCalendar.repository.ExamTypeRepository; +import org.springframework.stereotype.Component; +@Component +@RequiredArgsConstructor public class ExamDtoToExamMapper { - private ExamDtoToExamMapper() {} - public static Exam mapToExam(ExamDto examDto) { + private final ExamTypeRepository examTypeRepository; + + /** + * @param examDto examDto object received from request + * @return Exam entity with examType field converted from String do ExamType + */ + public Exam mapToExam(ExamDto examDto) { return Exam.builder() .title(examDto.getTitle()) .description(examDto.getDescription()) .date(examDto.getDate()) - .exam_group(examDto.getExam_group()) - .exam_type(examDto.getExam_type()) + .examGroups(examDto.getExamGroup()) + .examType(examTypeRepository.findByName(examDto.getExamType()).orElseThrow()) .build(); } } diff --git a/src/main/java/org/pkwmtt/examCalendar/repository/ExamTypeRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/ExamTypeRepository.java index dfa23e1..c14d733 100644 --- a/src/main/java/org/pkwmtt/examCalendar/repository/ExamTypeRepository.java +++ b/src/main/java/org/pkwmtt/examCalendar/repository/ExamTypeRepository.java @@ -3,5 +3,8 @@ import org.pkwmtt.examCalendar.entity.ExamType; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + public interface ExamTypeRepository extends JpaRepository { + Optional findByName(String name); } \ No newline at end of file From 9739267ef5fcfe89b412d6d6711222545209c9e5 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Fri, 1 Aug 2025 16:37:33 +0200 Subject: [PATCH 005/116] modify exam + javadoc --- .../pkwmtt/examCalendar/ExamController.java | 8 +++--- .../org/pkwmtt/examCalendar/ExamService.java | 14 ++++++++++- .../org/pkwmtt/examCalendar/entity/Exam.java | 6 +++-- .../mapper/ExamDtoToExamMapper.java | 25 +++++++++++++++++-- 4 files changed, 44 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamController.java b/src/main/java/org/pkwmtt/examCalendar/ExamController.java index a860c88..790a738 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamController.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamController.java @@ -35,13 +35,13 @@ public ResponseEntity addExam(@RequestBody ExamDto examDto) { /** * @param id of exam or test - * @param exam new details of exam or test + * @param examDto new details of exam or test * @return */ @PutMapping("/{id}") - public ResponseEntity modifyExam(@PathVariable long id, @RequestBody Exam exam) { - throw new UnsupportedOperationException("Not supported yet."); -// return ResponseEntity.noContent().build(); + public ResponseEntity modifyExam(@PathVariable int id, @RequestBody ExamDto examDto) { + examService.modifyExam(examDto, id); + return ResponseEntity.noContent().build(); } /** diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamService.java b/src/main/java/org/pkwmtt/examCalendar/ExamService.java index 4787ef8..df2c545 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamService.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamService.java @@ -7,6 +7,8 @@ import org.pkwmtt.examCalendar.repository.ExamRepository; import org.springframework.stereotype.Service; +import java.util.NoSuchElementException; + @Service @RequiredArgsConstructor @Transactional @@ -20,6 +22,16 @@ public class ExamService { * @return id of exam added to database */ public int addExam(ExamDto examDto) { - return examRepository.save(examMapper.mapToExam(examDto)).getExamId(); + return examRepository.save(examMapper.mapToNewExam(examDto)).getExamId(); + } + + /** + * @param examDto new details of exam that overwrite old ones + * @param id of exam that need to be modified + */ + public void modifyExam(ExamDto examDto, int id) { + examRepository.findById(id).orElseThrow(() -> new NoSuchElementException("Exam not found")); //TODO: change exception type + examRepository.save(examMapper.mapToExistingExam(examDto, id)); } + } diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java b/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java index 80d7ec1..69aacfb 100644 --- a/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java +++ b/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java @@ -1,10 +1,12 @@ package org.pkwmtt.examCalendar.entity; import jakarta.persistence.*; -import lombok.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.RequiredArgsConstructor; import java.time.LocalDateTime; -import java.util.Date; @Entity @Getter diff --git a/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java index a67a5f6..db211bb 100644 --- a/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java +++ b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java @@ -6,6 +6,9 @@ import org.pkwmtt.examCalendar.repository.ExamTypeRepository; import org.springframework.stereotype.Component; +/** + * maps ExamDto to Exam entity. Couldn't be utility class, because needs ExamTypeRepository to validate exam types + */ @Component @RequiredArgsConstructor public class ExamDtoToExamMapper { @@ -13,9 +16,10 @@ public class ExamDtoToExamMapper { /** * @param examDto examDto object received from request - * @return Exam entity with examType field converted from String do ExamType + * @return Exam entity WITHOUT examId which should be assigned by database + * Also contains examType field converted from String do ExamType */ - public Exam mapToExam(ExamDto examDto) { + public Exam mapToNewExam(ExamDto examDto) { return Exam.builder() .title(examDto.getTitle()) .description(examDto.getDescription()) @@ -24,4 +28,21 @@ public Exam mapToExam(ExamDto examDto) { .examType(examTypeRepository.findByName(examDto.getExamType()).orElseThrow()) .build(); } + + /** + * @param examDto examDto object received from request + * @param id of Exam that need to be modified + * @return Exam entity WITH examId that allow to update entity in database instead of creating new one + * Also contains examType field converted from String do ExamType + */ + public Exam mapToExistingExam(ExamDto examDto, int id) { + return Exam.builder() + .examId(id) + .title(examDto.getTitle()) + .description(examDto.getDescription()) + .date(examDto.getDate()) + .examGroups(examDto.getExamGroup()) + .examType(examTypeRepository.findByName(examDto.getExamType()).orElseThrow()) + .build(); + } } From 194b5759af77ce5b6c8dd61bca44c7fec770ecae Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Sat, 2 Aug 2025 14:59:34 +0200 Subject: [PATCH 006/116] implement get exam --- .../pkwmtt/examCalendar/ExamController.java | 34 +++++++++---------- .../org/pkwmtt/examCalendar/ExamService.java | 23 +++++++++++-- .../repository/ExamRepository.java | 7 ++++ 3 files changed, 44 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamController.java b/src/main/java/org/pkwmtt/examCalendar/ExamController.java index 790a738..d115e7c 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamController.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamController.java @@ -8,7 +8,7 @@ import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import java.net.URI; -import java.util.List; +import java.util.Set; @RequiredArgsConstructor @RequestMapping("/pkwmtt/api/v1/exams") @@ -36,7 +36,7 @@ public ResponseEntity addExam(@RequestBody ExamDto examDto) { /** * @param id of exam or test * @param examDto new details of exam or test - * @return + * @return 204 no content */ @PutMapping("/{id}") public ResponseEntity modifyExam(@PathVariable int id, @RequestBody ExamDto examDto) { @@ -46,40 +46,38 @@ public ResponseEntity modifyExam(@PathVariable int id, @RequestBody ExamDt /** * @param id of exam or test - * @return + * @return 204 no content */ @DeleteMapping("/{id}") - public ResponseEntity deleteExam(@PathVariable long id) { - throw new UnsupportedOperationException("Not supported yet."); -// return ResponseEntity.noContent().build(); + public ResponseEntity deleteExam(@PathVariable int id) { + examService.deleteExam(id); + return ResponseEntity.noContent().build(); } /** * @param id of exam or test - * @return single exam or test details + * @return 200 ok with single exam or test details */ @GetMapping("/{id}") - public ResponseEntity getExam(@PathVariable long id) { - throw new UnsupportedOperationException("Not supported yet."); -// return ResponseEntity.ok(); + public ResponseEntity getExam(@PathVariable int id) { + return ResponseEntity.ok(examService.getExamById(id)); } /** * @param generalGroup symbol that identify exercise group of specific field of study (for example 12K2) - * @param k computer laboratory group - * @param l laboratory group - * @param p project group - * @return + * @param k computer laboratory group (non required) + * @param l laboratory group (non required) + * @param p project group (non required) + * @return 200 ok with list of exams for specific group */ @GetMapping("/by-groups/{generalGroup}") - public ResponseEntity> getExams( + public ResponseEntity> getExams( @PathVariable String generalGroup, @RequestParam(name = "k", required = false) String k, @RequestParam(name = "l", required = false) String l, @RequestParam(name = "p", required = false) String p - ) { - throw new UnsupportedOperationException("Not supported yet."); -// return ResponseEntity.ok(); + ){ + return ResponseEntity.ok(examService.getExamByGroup(generalGroup, k, l, p)); } } \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamService.java b/src/main/java/org/pkwmtt/examCalendar/ExamService.java index df2c545..10d132c 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamService.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamService.java @@ -3,11 +3,13 @@ import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.pkwmtt.examCalendar.dto.ExamDto; +import org.pkwmtt.examCalendar.entity.Exam; import org.pkwmtt.examCalendar.mapper.ExamDtoToExamMapper; import org.pkwmtt.examCalendar.repository.ExamRepository; import org.springframework.stereotype.Service; import java.util.NoSuchElementException; +import java.util.Set; @Service @RequiredArgsConstructor @@ -16,7 +18,6 @@ public class ExamService { private final ExamRepository examRepository; private final ExamDtoToExamMapper examMapper; - /** * @param examDto details of exam * @return id of exam added to database @@ -27,11 +28,29 @@ public int addExam(ExamDto examDto) { /** * @param examDto new details of exam that overwrite old ones - * @param id of exam that need to be modified + * @param id of exam that need to be modified */ public void modifyExam(ExamDto examDto, int id) { examRepository.findById(id).orElseThrow(() -> new NoSuchElementException("Exam not found")); //TODO: change exception type examRepository.save(examMapper.mapToExistingExam(examDto, id)); } + public void deleteExam(int id) { + examRepository.findById(id).orElseThrow(() -> new NoSuchElementException("Exam not found")); //TODO: change exception type + examRepository.deleteById(id); + } + + public Exam getExamById(int id) { + return examRepository.findById(id).orElseThrow(() -> new NoSuchElementException("Exam not found")); //TODO: change exception typ + } + + public Set getExamByGroup(String generalGroup, String kGroup, String lGroup, String pGroup){ +// TODO: change to 1 query instead of 4 +// TODO: N + 1 + Set exams = examRepository.findByExamGroupsContaining(generalGroup); + if(kGroup != null ) exams.addAll(examRepository.findByExamGroupsContaining(kGroup)); + if(lGroup != null ) exams.addAll(examRepository.findByExamGroupsContaining(lGroup)); + if(pGroup != null ) exams.addAll(examRepository.findByExamGroupsContaining(pGroup)); + return exams; + } } diff --git a/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java index 96f3bca..a3f0d10 100644 --- a/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java +++ b/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java @@ -2,6 +2,13 @@ import org.pkwmtt.examCalendar.entity.Exam; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.Set; public interface ExamRepository extends JpaRepository { + @Query("select e from Exam e where e.examGroups LIKE %:gs%") + Set findExamsByGroupSignature(@Param("gs")String groupSignature); + Set findByExamGroupsContaining(String groupSignature); } \ No newline at end of file From 81928b9b09d7533fb05f344260587438ae322054 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Sat, 2 Aug 2025 15:04:47 +0200 Subject: [PATCH 007/116] javadoc --- .../org/pkwmtt/examCalendar/ExamService.java | 25 +++++++++++++++---- .../repository/ExamRepository.java | 5 ++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamService.java b/src/main/java/org/pkwmtt/examCalendar/ExamService.java index 10d132c..40886de 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamService.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamService.java @@ -18,6 +18,7 @@ public class ExamService { private final ExamRepository examRepository; private final ExamDtoToExamMapper examMapper; + /** * @param examDto details of exam * @return id of exam added to database @@ -28,29 +29,43 @@ public int addExam(ExamDto examDto) { /** * @param examDto new details of exam that overwrite old ones - * @param id of exam that need to be modified + * @param id of exam that need to be modified */ public void modifyExam(ExamDto examDto, int id) { examRepository.findById(id).orElseThrow(() -> new NoSuchElementException("Exam not found")); //TODO: change exception type examRepository.save(examMapper.mapToExistingExam(examDto, id)); } + /** + * @param id of exam + */ public void deleteExam(int id) { examRepository.findById(id).orElseThrow(() -> new NoSuchElementException("Exam not found")); //TODO: change exception type examRepository.deleteById(id); } + /** + * @param id of exam + * @return exam + */ public Exam getExamById(int id) { return examRepository.findById(id).orElseThrow(() -> new NoSuchElementException("Exam not found")); //TODO: change exception typ } - public Set getExamByGroup(String generalGroup, String kGroup, String lGroup, String pGroup){ + /** + * @param generalGroup symbol that identify exercise group of specific field of study (for example 12K2) + * @param kGroup computer laboratory group (non required) + * @param lGroup laboratory group (non required) + * @param pGroup project group (non required) + * @return Set of exams for specific groups + */ + public Set getExamByGroup(String generalGroup, String kGroup, String lGroup, String pGroup) { // TODO: change to 1 query instead of 4 // TODO: N + 1 Set exams = examRepository.findByExamGroupsContaining(generalGroup); - if(kGroup != null ) exams.addAll(examRepository.findByExamGroupsContaining(kGroup)); - if(lGroup != null ) exams.addAll(examRepository.findByExamGroupsContaining(lGroup)); - if(pGroup != null ) exams.addAll(examRepository.findByExamGroupsContaining(pGroup)); + if (kGroup != null) exams.addAll(examRepository.findByExamGroupsContaining(kGroup)); + if (lGroup != null) exams.addAll(examRepository.findByExamGroupsContaining(lGroup)); + if (pGroup != null) exams.addAll(examRepository.findByExamGroupsContaining(pGroup)); return exams; } } diff --git a/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java index a3f0d10..1cf2127 100644 --- a/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java +++ b/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java @@ -10,5 +10,10 @@ public interface ExamRepository extends JpaRepository { @Query("select e from Exam e where e.examGroups LIKE %:gs%") Set findExamsByGroupSignature(@Param("gs")String groupSignature); + + /** + * @param groupSignature symbol that identifies group + * @return set of Exams for specific group + */ Set findByExamGroupsContaining(String groupSignature); } \ No newline at end of file From f37edb55e4b9b5ed4fddbccad0ed0452cdc4f536 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Mon, 4 Aug 2025 16:47:08 +0200 Subject: [PATCH 008/116] simple mapper tests --- pom.xml | 11 +-- .../org/pkwmtt/examCalendar/dto/ExamDto.java | 2 +- .../mapper/ExamDtoToExamMapper.java | 4 +- .../pkwmtt/examCalendar/ExamServiceTest.java | 39 ++++++++ .../mapper/ExamDtoToExamMapperTest.java | 88 +++++++++++++++++++ 5 files changed, 136 insertions(+), 8 deletions(-) create mode 100644 src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java create mode 100644 src/test/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapperTest.java diff --git a/pom.xml b/pom.xml index fd73cac..ffb247c 100644 --- a/pom.xml +++ b/pom.xml @@ -82,11 +82,12 @@ junit 4.13.1 - - org.mockito - mockito-all - 1.10.19 - + + + + + + org.mockito mockito-core diff --git a/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java b/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java index 3490c91..221f63b 100644 --- a/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java +++ b/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java @@ -12,6 +12,6 @@ public class ExamDto { private final String title; private final String description; private final LocalDateTime date; - private final String examGroup; + private final String examGroups; private final String examType; } diff --git a/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java index db211bb..fe2b726 100644 --- a/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java +++ b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java @@ -24,7 +24,7 @@ public Exam mapToNewExam(ExamDto examDto) { .title(examDto.getTitle()) .description(examDto.getDescription()) .date(examDto.getDate()) - .examGroups(examDto.getExamGroup()) + .examGroups(examDto.getExamGroups()) .examType(examTypeRepository.findByName(examDto.getExamType()).orElseThrow()) .build(); } @@ -41,7 +41,7 @@ public Exam mapToExistingExam(ExamDto examDto, int id) { .title(examDto.getTitle()) .description(examDto.getDescription()) .date(examDto.getDate()) - .examGroups(examDto.getExamGroup()) + .examGroups(examDto.getExamGroups()) .examType(examTypeRepository.findByName(examDto.getExamType()).orElseThrow()) .build(); } diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java new file mode 100644 index 0000000..8bdc7d4 --- /dev/null +++ b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java @@ -0,0 +1,39 @@ +package org.pkwmtt.examCalendar; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.pkwmtt.examCalendar.repository.ExamRepository; + +import static org.junit.jupiter.api.Assertions.*; +@ExtendWith(MockitoExtension.class) +class ExamServiceTest { + + @Mock + private ExamRepository examRepository; + + @InjectMocks + private ExamService examService; + + @Test + void addExam() { + } + + @Test + void modifyExam() { + } + + @Test + void deleteExam() { + } + + @Test + void getExamById() { + } + + @Test + void getExamByGroup() { + } +} \ No newline at end of file diff --git a/src/test/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapperTest.java b/src/test/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapperTest.java new file mode 100644 index 0000000..75ece79 --- /dev/null +++ b/src/test/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapperTest.java @@ -0,0 +1,88 @@ +package org.pkwmtt.examCalendar.mapper; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.pkwmtt.examCalendar.dto.ExamDto; +import org.pkwmtt.examCalendar.entity.Exam; +import org.pkwmtt.examCalendar.entity.ExamType; +import org.pkwmtt.examCalendar.repository.ExamTypeRepository; + +import java.time.LocalDateTime; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class ExamDtoToExamMapperTest { + + @Mock + private ExamTypeRepository examTypeRepository; + + @InjectMocks + private ExamDtoToExamMapper examDtoToExamMapper; + + private ExamDto examDto; + private String examTypeName; + + @BeforeEach + void setup() { + examTypeName = "exam"; + examDto = new ExamDto( + "Math exam", + "Linear algebra", + LocalDateTime.now().plusDays(1), + "12K2, 13S1", + examTypeName + ); + } + + @Test + void mapToNewExam() { +// given + when(examTypeRepository.findByName(examTypeName)).thenReturn( + Optional.of(ExamType.builder() + .name(examTypeName) + .build()) + ); +// when + Exam exam = examDtoToExamMapper.mapToNewExam(examDto); +// then +// test fields + assertEquals(examDto.getTitle(), exam.getTitle()); + assertEquals(examDto.getDescription(), exam.getDescription()); + assertEquals(examDto.getDate(), exam.getDate()); + assertEquals(examDto.getExamGroups(), exam.getExamGroups()); + assertEquals(examTypeName, exam.getExamType().getName()); +// test null id + assertNull(exam.getExamId()); + } + + + @Test + void mapToExistingExam() { + // given + int examId = 1; + when(examTypeRepository.findByName(examTypeName)).thenReturn( + Optional.of(ExamType.builder() + .name(examTypeName) + .build()) + ); +// when + Exam exam = examDtoToExamMapper.mapToExistingExam(examDto, examId); +// then +// test fields + assertEquals(examId, exam.getExamId()); + assertEquals(examDto.getTitle(), exam.getTitle()); + assertEquals(examDto.getDescription(), exam.getDescription()); + assertEquals(examDto.getDate(), exam.getDate()); + assertEquals(examDto.getExamGroups(), exam.getExamGroups()); + assertEquals(examTypeName, exam.getExamType().getName()); +// test not null id + assertNotNull(exam.getExamId()); + } +} From 8a4630ddc123fa7634353dedff7f36e087c0509a Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Mon, 4 Aug 2025 17:33:52 +0200 Subject: [PATCH 009/116] data validation in Exam builder --- .../org/pkwmtt/examCalendar/entity/Exam.java | 23 +++++++++++++++++-- .../mapper/ExamDtoToExamMapperTest.java | 1 + 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java b/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java index 69aacfb..b0771e6 100644 --- a/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java +++ b/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java @@ -7,10 +7,11 @@ import lombok.RequiredArgsConstructor; import java.time.LocalDateTime; +import java.util.Arrays; @Entity @Getter -@Builder +@Builder(builderClassName = "Builder", buildMethodName = "build") @RequiredArgsConstructor @Table(name = "exams") @AllArgsConstructor @@ -32,5 +33,23 @@ public class Exam { @JoinColumn(name = "exam_type_id") private ExamType examType; -// TODO: add exam builder + public static class Builder { + public Exam build() { + if (title == null || title.isEmpty() || title.length() > 255) //TODO: change exception types + throw new RuntimeException("Invalid title"); + if(description.length() > 255) + throw new RuntimeException("Invalid description"); + if(date == null || date.isBefore(LocalDateTime.now())) + throw new RuntimeException("Invalid date"); + if(examGroups == null || examGroups.length() > 255) + throw new RuntimeException("Invalid exam groups String"); + Arrays.stream(examGroups.split(", ")).forEach(group -> { + if(group.length() > 8) + throw new RuntimeException("Invalid exam group: " + group); + }); + if(examType == null || examType.getName() == null || examType.getName().length() > 255) + throw new RuntimeException("Invalid exam type"); + return new Exam(examId, title, description, date, examGroups, examType); + } + } } diff --git a/src/test/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapperTest.java b/src/test/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapperTest.java index 75ece79..b87ba37 100644 --- a/src/test/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapperTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapperTest.java @@ -41,6 +41,7 @@ void setup() { ); } +// TODO: test data validation @Test void mapToNewExam() { // given From 0df238f1f01e89eb3bc3905574ed684ff6131dc0 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Tue, 5 Aug 2025 12:51:11 +0200 Subject: [PATCH 010/116] Add full tests for ExamDtoMapper and move some validation to controller --- .../pkwmtt/examCalendar/ExamController.java | 7 +- .../org/pkwmtt/examCalendar/dto/ExamDto.java | 12 +++ .../org/pkwmtt/examCalendar/entity/Exam.java | 16 +--- .../InvalidGroupIdentifierException.java | 7 ++ .../mapper/ExamDtoToExamMapperTest.java | 85 ++++++++++++++++--- 5 files changed, 100 insertions(+), 27 deletions(-) create mode 100644 src/main/java/org/pkwmtt/exceptions/InvalidGroupIdentifierException.java diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamController.java b/src/main/java/org/pkwmtt/examCalendar/ExamController.java index d115e7c..4a8a203 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamController.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamController.java @@ -1,5 +1,6 @@ package org.pkwmtt.examCalendar; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.pkwmtt.examCalendar.dto.ExamDto; import org.pkwmtt.examCalendar.entity.Exam; @@ -22,7 +23,7 @@ public class ExamController { * @return 201 created with URI to GET method which returns created resource */ @PostMapping("") - public ResponseEntity addExam(@RequestBody ExamDto examDto) { + public ResponseEntity addExam(@RequestBody @Valid ExamDto examDto) { int id = examService.addExam(examDto); URI uri = ServletUriComponentsBuilder .fromCurrentRequest() @@ -30,7 +31,7 @@ public ResponseEntity addExam(@RequestBody ExamDto examDto) { .buildAndExpand(id) .toUri(); return ResponseEntity.created(uri).build(); -// TODO: add data verification +// TODO: test data validation in controller } /** @@ -39,7 +40,7 @@ public ResponseEntity addExam(@RequestBody ExamDto examDto) { * @return 204 no content */ @PutMapping("/{id}") - public ResponseEntity modifyExam(@PathVariable int id, @RequestBody ExamDto examDto) { + public ResponseEntity modifyExam(@PathVariable int id,@RequestBody @Valid ExamDto examDto) { examService.modifyExam(examDto, id); return ResponseEntity.noContent().build(); } diff --git a/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java b/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java index 221f63b..8627581 100644 --- a/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java +++ b/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java @@ -1,5 +1,9 @@ package org.pkwmtt.examCalendar.dto; +import jakarta.validation.constraints.Future; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -9,9 +13,17 @@ @RequiredArgsConstructor public class ExamDto { + @NotBlank(message = "Title is required") + @Size(max = 255, message = "max size of field is 255") private final String title; + @Size(max = 255, message = "max size of field is 255") private final String description; + @Future(message = "Date must be in the future") + @NotNull private final LocalDateTime date; + @NotBlank + @Size(max = 255, message = "max size of field is 255") private final String examGroups; + @NotNull private final String examType; } diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java b/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java index b0771e6..3465856 100644 --- a/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java +++ b/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java @@ -5,6 +5,7 @@ import lombok.Builder; import lombok.Getter; import lombok.RequiredArgsConstructor; +import org.pkwmtt.exceptions.InvalidGroupIdentifierException; import java.time.LocalDateTime; import java.util.Arrays; @@ -35,20 +36,11 @@ public class Exam { public static class Builder { public Exam build() { - if (title == null || title.isEmpty() || title.length() > 255) //TODO: change exception types - throw new RuntimeException("Invalid title"); - if(description.length() > 255) - throw new RuntimeException("Invalid description"); - if(date == null || date.isBefore(LocalDateTime.now())) - throw new RuntimeException("Invalid date"); - if(examGroups == null || examGroups.length() > 255) - throw new RuntimeException("Invalid exam groups String"); + // max length of group identifier is 6 Arrays.stream(examGroups.split(", ")).forEach(group -> { - if(group.length() > 8) - throw new RuntimeException("Invalid exam group: " + group); + if(group.length() > 6) + throw new InvalidGroupIdentifierException(group); }); - if(examType == null || examType.getName() == null || examType.getName().length() > 255) - throw new RuntimeException("Invalid exam type"); return new Exam(examId, title, description, date, examGroups, examType); } } diff --git a/src/main/java/org/pkwmtt/exceptions/InvalidGroupIdentifierException.java b/src/main/java/org/pkwmtt/exceptions/InvalidGroupIdentifierException.java new file mode 100644 index 0000000..4faadac --- /dev/null +++ b/src/main/java/org/pkwmtt/exceptions/InvalidGroupIdentifierException.java @@ -0,0 +1,7 @@ +package org.pkwmtt.exceptions; + +public class InvalidGroupIdentifierException extends RuntimeException { + public InvalidGroupIdentifierException(String groupIdentifier) { + super("Invalid group identifier: " + groupIdentifier); + } +} diff --git a/src/test/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapperTest.java b/src/test/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapperTest.java index b87ba37..45081ae 100644 --- a/src/test/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapperTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapperTest.java @@ -1,6 +1,5 @@ package org.pkwmtt.examCalendar.mapper; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -10,6 +9,7 @@ import org.pkwmtt.examCalendar.entity.Exam; import org.pkwmtt.examCalendar.entity.ExamType; import org.pkwmtt.examCalendar.repository.ExamTypeRepository; +import org.pkwmtt.exceptions.InvalidGroupIdentifierException; import java.time.LocalDateTime; import java.util.Optional; @@ -29,22 +29,24 @@ class ExamDtoToExamMapperTest { private ExamDto examDto; private String examTypeName; - @BeforeEach - void setup() { - examTypeName = "exam"; - examDto = new ExamDto( +// @BeforeEach +// void setup() { +// +// } + + /**********************************************************************************/ +// mapToNewExam + @Test + void isFieldsMappedProperlyToNewExam() { +// given + String examTypeName = "exam"; + ExamDto examDto = new ExamDto( "Math exam", "Linear algebra", LocalDateTime.now().plusDays(1), "12K2, 13S1", examTypeName ); - } - -// TODO: test data validation - @Test - void mapToNewExam() { -// given when(examTypeRepository.findByName(examTypeName)).thenReturn( Optional.of(ExamType.builder() .name(examTypeName) @@ -63,11 +65,45 @@ void mapToNewExam() { assertNull(exam.getExamId()); } + @Test + void ShouldThrowExceptionWhenGroupIdentifierIsLongerThanSixCharactersForNewExam() { + // given + String examTypeName = "exam"; + ExamDto examDto = new ExamDto( + "Math exam", + "Linear algebra", + LocalDateTime.now().plusDays(1), + "12K2, 13S1, Not_Valid_Identifier, 41K1", + examTypeName + ); + when(examTypeRepository.findByName(examTypeName)).thenReturn( + Optional.of(ExamType.builder() + .name(examTypeName) + .build()) + ); +// then + RuntimeException exception = assertThrows( + InvalidGroupIdentifierException.class, + () -> examDtoToExamMapper.mapToNewExam(examDto) + ); + assertEquals("Invalid group identifier: Not_Valid_Identifier", exception.getMessage()); + } + + /**********************************************************************************/ +// mapToExistingExam @Test - void mapToExistingExam() { + void isFieldsMappedProperlyToExistingExam() { // given int examId = 1; + examTypeName = "exam"; + examDto = new ExamDto( + "Math exam", + "Linear algebra", + LocalDateTime.now().plusDays(1), + "12K2, 13S1", + examTypeName + ); when(examTypeRepository.findByName(examTypeName)).thenReturn( Optional.of(ExamType.builder() .name(examTypeName) @@ -86,4 +122,29 @@ void mapToExistingExam() { // test not null id assertNotNull(exam.getExamId()); } + + @Test + void ShouldThrowExceptionWhenGroupIdentifierIsLongerThanSixCharactersForExistingExam() { + // given + int examId = 1; + String examTypeName = "exam"; + ExamDto examDto = new ExamDto( + "Math exam", + "Linear algebra", + LocalDateTime.now().plusDays(1), + "12K2, 13S1, Not_Valid_Identifier, 41K1", + examTypeName + ); + when(examTypeRepository.findByName(examTypeName)).thenReturn( + Optional.of(ExamType.builder() + .name(examTypeName) + .build()) + ); +// then + RuntimeException exception = assertThrows( + InvalidGroupIdentifierException.class, + () -> examDtoToExamMapper.mapToExistingExam(examDto, examId) + ); + assertEquals("Invalid group identifier: Not_Valid_Identifier", exception.getMessage()); + } } From 881cf79a18498f7f7bf95a5946df5266fb33224d Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Wed, 6 Aug 2025 13:55:26 +0200 Subject: [PATCH 011/116] Add tests for most of ExamService methods --- .../org/pkwmtt/examCalendar/ExamService.java | 19 +++ .../pkwmtt/examCalendar/ExamServiceTest.java | 130 +++++++++++++++++- 2 files changed, 147 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamService.java b/src/main/java/org/pkwmtt/examCalendar/ExamService.java index 40886de..5dd5fc7 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamService.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamService.java @@ -53,6 +53,7 @@ public Exam getExamById(int id) { } /** + * limited number of arguments but more efficient query to database * @param generalGroup symbol that identify exercise group of specific field of study (for example 12K2) * @param kGroup computer laboratory group (non required) * @param lGroup laboratory group (non required) @@ -61,6 +62,7 @@ public Exam getExamById(int id) { */ public Set getExamByGroup(String generalGroup, String kGroup, String lGroup, String pGroup) { // TODO: change to 1 query instead of 4 +// TODO: change arguments to list // TODO: N + 1 Set exams = examRepository.findByExamGroupsContaining(generalGroup); if (kGroup != null) exams.addAll(examRepository.findByExamGroupsContaining(kGroup)); @@ -68,4 +70,21 @@ public Set getExamByGroup(String generalGroup, String kGroup, String lGrou if (pGroup != null) exams.addAll(examRepository.findByExamGroupsContaining(pGroup)); return exams; } + + /** + * flexible number of arguments but inefficient query to database + * @param generalGroup symbol that identify exercise group of specific field of study (for example 12K2) + * @param groups set od groups + * @return set of exams for specific groups + */ + public Set getExamByGroupsSet(String generalGroup, Set groups) { +// TODO: change arguments to list +// TODO: N + 1 + throw new UnsupportedOperationException("Not supported yet."); +// Set exams = examRepository.findByExamGroupsContaining(generalGroup); +// if (kGroup != null) exams.addAll(examRepository.findByExamGroupsContaining(kGroup)); +// if (lGroup != null) exams.addAll(examRepository.findByExamGroupsContaining(lGroup)); +// if (pGroup != null) exams.addAll(examRepository.findByExamGroupsContaining(pGroup)); +// return exams; + } } diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java index 8bdc7d4..1e13e12 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java @@ -5,35 +5,161 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.pkwmtt.examCalendar.dto.ExamDto; +import org.pkwmtt.examCalendar.entity.Exam; +import org.pkwmtt.examCalendar.entity.ExamType; +import org.pkwmtt.examCalendar.mapper.ExamDtoToExamMapper; import org.pkwmtt.examCalendar.repository.ExamRepository; +import java.lang.reflect.Field; +import java.time.LocalDateTime; +import java.util.NoSuchElementException; +import java.util.Optional; + import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + @ExtendWith(MockitoExtension.class) class ExamServiceTest { @Mock private ExamRepository examRepository; + @Mock + private ExamDtoToExamMapper examDtoToExamMapper; + @InjectMocks private ExamService examService; @Test void addExam() { +// given + int examId = 1; + ExamDto examDto = new ExamDto( + "Math exam", + "desc", + LocalDateTime.now().plusDays(1), + "12K2, 13L1", + "Exam" + ); + Exam exam = Exam.builder() + .title("Math exam") + .description("desc") + .date(LocalDateTime.now().plusDays(1)) + .examGroups("12K2, 13L1") + .examType(new ExamType(1, "Exam")) + .build(); + when(examDtoToExamMapper.mapToNewExam(examDto)).thenReturn(exam); + +// assign exam id in repository + when(examRepository.save(exam)).thenAnswer(invocation -> { + Exam newExam = invocation.getArgument(0); + Field field = Exam.class.getDeclaredField("examId"); + field.setAccessible(true); + field.set(newExam, examId); + return newExam; + }); +// when + int result = examService.addExam(examDto); +// then + assertEquals(examId, result); + verify(examRepository).save(exam); + verify(examDtoToExamMapper).mapToNewExam(examDto); + } + +/************************************************************************************/ +//modify exam + @Test + void shouldModifyExamWhenIdExists() { + // given + int examId = 1; + ExamDto examDto = mock(ExamDto.class); + Exam exam = mock(Exam.class); + + when(examDtoToExamMapper.mapToExistingExam(examDto, examId)).thenReturn(exam); + when(examRepository.findById(examId)).thenReturn(Optional.of(exam)); +// when + examService.modifyExam(examDto, examId); +// then + verify(examDtoToExamMapper).mapToExistingExam(examDto, examId); + verify(examRepository).save(exam); } @Test - void modifyExam() { + void shouldThrowWhenExamIdNotExists() { + // given + int examId = 5; + ExamDto examDto = mock(ExamDto.class); + when(examRepository.findById(examId)).thenThrow(new NoSuchElementException("Exam not found")); +// when + RuntimeException exception = assertThrows( + NoSuchElementException.class, + () -> examService.modifyExam(examDto, examId) + ); +// then + verify(examDtoToExamMapper, never()).mapToExistingExam(examDto, examId); + verify(examRepository, never()).save(any()); + assertEquals("Exam not found", exception.getMessage()); + } + /************************************************************************************/ +//delete exam + @Test + void shouldDeleteExamWhenIdExists() { +// given + int examId = 1; + when(examRepository.findById(examId)).thenReturn(Optional.of(mock(Exam.class))); +// when + examService.deleteExam(examId); +// then + verify(examRepository).deleteById(examId); } @Test - void deleteExam() { + void shouldThrowExceptionWhenExamIdNotExists() { +// given + int examId = 5; + when(examRepository.findById(examId)).thenThrow(new NoSuchElementException("Exam not found")); +// when + RuntimeException exception = assertThrows( + NoSuchElementException.class, + () -> examService.deleteExam(examId) + ); +// then + verify(examRepository, never()).deleteById(examId); + assertEquals("Exam not found", exception.getMessage()); } + /************************************************************************************/ +// get exam by id + @Test void getExamById() { +// given + int examId = 1; + when(examRepository.findById(examId)).thenReturn(Optional.of(mock(Exam.class))); +// when + Exam exam = examService.getExamById(examId); +// then + verify(examRepository).findById(examId); + assertNotNull(exam); + } + + @Test + void shouldThrowExceptionWhenExamNotFound() { +// given + int examId = 5; + when(examRepository.findById(examId)).thenThrow(new NoSuchElementException("Exam not found")); +// when + RuntimeException exception = assertThrows( + NoSuchElementException.class, + () -> examService.getExamById(examId) + ); +// then + assertEquals("Exam not found", exception.getMessage()); } @Test void getExamByGroup() { + } } \ No newline at end of file From 48a983da75b5aa5bf9483adedcf2ec719a31326b Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Wed, 6 Aug 2025 17:27:58 +0200 Subject: [PATCH 012/116] implement integration tests for ExamRepository (with h2 database in test scope) --- pom.xml | 7 + .../repository/ExamRepository.java | 36 ++++ .../repository/ExamRepositoryTest.java | 182 ++++++++++++++++++ src/test/resources/application.properties | 10 + 4 files changed, 235 insertions(+) create mode 100644 src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java create mode 100644 src/test/resources/application.properties diff --git a/pom.xml b/pom.xml index ffb247c..bceddca 100644 --- a/pom.xml +++ b/pom.xml @@ -60,6 +60,7 @@ + com.h2database h2 @@ -93,6 +94,12 @@ mockito-core 5.18.0 + + + + + + diff --git a/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java index 1cf2127..512b0c5 100644 --- a/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java +++ b/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java @@ -11,6 +11,42 @@ public interface ExamRepository extends JpaRepository { @Query("select e from Exam e where e.examGroups LIKE %:gs%") Set findExamsByGroupSignature(@Param("gs")String groupSignature); + @Query("SELECT e FROM Exam e WHERE " + + "e.examGroups LIKE CONCAT('%', :g1, '%') OR " + + "e.examGroups LIKE CONCAT('%', :g2, '%') OR " + + "e.examGroups LIKE CONCAT('%', :g3, '%') OR " + + "e.examGroups LIKE CONCAT('%', :g4, '%') ") + Set findExamsByGroupsIdentifier( + @Param("g1") String group1, + @Param("g2") String group2, + @Param("g3") String group3, + @Param("g4") String group4 + ); + + @Query("SELECT e FROM Exam e WHERE " + + "e.examGroups LIKE CONCAT('%', :g1, '%') OR " + + "e.examGroups LIKE CONCAT('%', :g2, '%') OR " + + "e.examGroups LIKE CONCAT('%', :g3, '%') ") + Set findExamsByGroupsIdentifier( + @Param("g1") String group1, + @Param("g2") String group2, + @Param("g3") String group3 + ); + + @Query("SELECT e FROM Exam e WHERE " + + "e.examGroups LIKE CONCAT('%', :g1, '%') OR " + + "e.examGroups LIKE CONCAT('%', :g2, '%')" ) + Set findExamsByGroupsIdentifier( + @Param("g1") String group1, + @Param("g2") String group2 + ); + + @Query("SELECT e FROM Exam e WHERE " + + "e.examGroups LIKE CONCAT('%', :gg, '%')") + Set findExamsByGroupsIdentifier( + @Param("gg") String group + ); + /** * @param groupSignature symbol that identifies group * @return set of Exams for specific group diff --git a/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java b/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java new file mode 100644 index 0000000..350d7c3 --- /dev/null +++ b/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java @@ -0,0 +1,182 @@ +package org.pkwmtt.examCalendar.repository; + +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.pkwmtt.examCalendar.entity.Exam; +import org.pkwmtt.examCalendar.entity.ExamType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.*; + +@Slf4j +@DataJpaTest +class ExamRepositoryTest { + + @Autowired + private ExamRepository examRepository; + + @Autowired + private ExamTypeRepository examTypeRepository; + + private ExamType examType; + + @BeforeEach + void setup(){ + examType = ExamType.builder() + .name("exam") + .build(); + examTypeRepository.save(examType); + } + + /** + * test if method find specific count of exams when 1 or 0 group identifiers match + */ + @Test + void testSingleIdentifierMatch() { +// given + Exam exam1 = Exam.builder() + .title("Exam 1") + .description("Exam 1") + .date(LocalDateTime.now().plusDays(1)) + .examGroups("12K2, K03") + .examType(examType) + .build(); + examRepository.save(exam1); + Exam exam2 = Exam.builder() + .title("Exam 2") + .description("Exam 2") + .date(LocalDateTime.now().plusDays(1)) + .examGroups("12K3, K03, S02") + .examType(examType) + .build(); + examRepository.save(exam2); + Exam exam3 = Exam.builder() + .title("Exam 3") + .description("Exam 3") + .date(LocalDateTime.now().plusDays(1)) + .examGroups("13K1, K05, L05") + .examType(examType) + .build(); + examRepository.save(exam3); + Exam exam4 = Exam.builder() + .title("Exam 4") + .description("Exam 4") + .date(LocalDateTime.now().plusDays(1)) + .examGroups("41K1, L04, P03, I01") + .examType(examType) + .build(); + examRepository.save(exam4); + Exam exam5 = Exam.builder() + .title("Exam 5") + .description("Exam 5") + .date(LocalDateTime.now().plusDays(1)) + .examGroups("11A1, G03, H01, P02") + .examType(examType) + .build(); + examRepository.save(exam5); + + String generalGroup = "12K2"; + String kGroup = "K05"; + String lGroup = "L04"; + String pGroup = "P02"; + +// when + Set exams = examRepository.findExamsByGroupsIdentifier(generalGroup, kGroup, lGroup, pGroup); + List examsTitles = exams.stream().map(Exam::getTitle).toList(); +// then + assertEquals(4, exams.size()); + assertTrue(examsTitles.contains("Exam 1")); + assertTrue(examsTitles.contains("Exam 3")); + assertTrue(examsTitles.contains("Exam 4")); + assertTrue(examsTitles.contains("Exam 5")); + } + + /** + * test if method don't duplicate exams when more than 1 identifier match + */ + @Test + void testMultipleIdentifierMatch() { +// given + Exam exam1 = Exam.builder() + .title("Exam 1") + .description("Exam 1") + .date(LocalDateTime.now().plusDays(1)) + .examGroups("12K2, K01, L04, P03, I01") + .examType(examType) + .build(); + examRepository.save(exam1); + Exam exam2 = Exam.builder() + .title("Exam 2") + .description("Exam 2") + .date(LocalDateTime.now().plusDays(1)) + .examGroups("12K2, K05, L04, P02") + .examType(examType) + .build(); + examRepository.save(exam2); + Exam exam3 = Exam.builder() + .title("Exam 3") + .description("Exam 3") + .date(LocalDateTime.now().plusDays(1)) + .examGroups("12K2, K05, L04, P02, I05") + .examType(examType) + .build(); + examRepository.save(exam3); + + String generalGroup = "12K2"; + String kGroup = "K05"; + String lGroup = "L04"; + String pGroup = "P02"; + +// when + Set exams = examRepository.findExamsByGroupsIdentifier(generalGroup, kGroup, lGroup, pGroup); + List examsTitles = exams.stream().map(Exam::getTitle).toList(); + +// then + assertEquals(3, exams.size()); + assertTrue(examsTitles.contains("Exam 1")); + assertTrue(examsTitles.contains("Exam 2")); + assertTrue(examsTitles.contains("Exam 3")); + } + + /** + * test if method return empty set identifiers don't match + */ + @Test + void testNothingMatch() { +// given + Exam exam1 = Exam.builder() + .title("Exam 1") + .description("Exam 1") + .date(LocalDateTime.now().plusDays(1)) + .examGroups("12K2, K01,") + .examType(examType) + .build(); + examRepository.save(exam1); + Exam exam2 = Exam.builder() + .title("Exam 2") + .description("Exam 2") + .date(LocalDateTime.now().plusDays(1)) + .examGroups("12K3, L05") + .examType(examType) + .build(); + examRepository.save(exam2); + + String generalGroup = "14K3"; + String kGroup = "K05"; + String lGroup = "L02"; + String pGroup = "P02"; + +// when + Set exams = examRepository.findExamsByGroupsIdentifier(generalGroup, kGroup, lGroup, pGroup); + +// then + assertTrue(exams.isEmpty()); + } +} \ No newline at end of file diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties new file mode 100644 index 0000000..0c7b664 --- /dev/null +++ b/src/test/resources/application.properties @@ -0,0 +1,10 @@ +spring.datasource.url=jdbc:h2://mem:db;DB_CLOSE_DELAY=-1 +spring.datasource.username=sa +spring.datasource.password=sa + +spring.jpa.show-sql=true +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +spring.datasource.driver-class-name=org.h2.Driver +#spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false +spring.jpa.hibernate.ddl-auto=update +#spring.datasource.hikari.initialization-fail-timeout=0 From 24592a25c151375288a49a7bcf551ef6cde2235b Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Wed, 6 Aug 2025 18:49:45 +0200 Subject: [PATCH 013/116] optimizing getExamByGroup method --- .../pkwmtt/examCalendar/ExamController.java | 16 ++---- .../org/pkwmtt/examCalendar/ExamService.java | 52 +++++++------------ 2 files changed, 23 insertions(+), 45 deletions(-) diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamController.java b/src/main/java/org/pkwmtt/examCalendar/ExamController.java index 4a8a203..2d72123 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamController.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamController.java @@ -65,20 +65,12 @@ public ResponseEntity getExam(@PathVariable int id) { } /** - * @param generalGroup symbol that identify exercise group of specific field of study (for example 12K2) - * @param k computer laboratory group (non required) - * @param l laboratory group (non required) - * @param p project group (non required) + * @param groups set of groups * @return 200 ok with list of exams for specific group */ - @GetMapping("/by-groups/{generalGroup}") - public ResponseEntity> getExams( - @PathVariable String generalGroup, - @RequestParam(name = "k", required = false) String k, - @RequestParam(name = "l", required = false) String l, - @RequestParam(name = "p", required = false) String p - ){ - return ResponseEntity.ok(examService.getExamByGroup(generalGroup, k, l, p)); + @GetMapping("/by-groups") + public ResponseEntity> getExams(@RequestParam Set groups){ + return ResponseEntity.ok(examService.getExamByGroup(groups)); } } \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamService.java b/src/main/java/org/pkwmtt/examCalendar/ExamService.java index 5dd5fc7..1a63b02 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamService.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamService.java @@ -8,8 +8,7 @@ import org.pkwmtt.examCalendar.repository.ExamRepository; import org.springframework.stereotype.Service; -import java.util.NoSuchElementException; -import java.util.Set; +import java.util.*; @Service @RequiredArgsConstructor @@ -29,7 +28,7 @@ public int addExam(ExamDto examDto) { /** * @param examDto new details of exam that overwrite old ones - * @param id of exam that need to be modified + * @param id of exam that need to be modified */ public void modifyExam(ExamDto examDto, int id) { examRepository.findById(id).orElseThrow(() -> new NoSuchElementException("Exam not found")); //TODO: change exception type @@ -53,38 +52,25 @@ public Exam getExamById(int id) { } /** - * limited number of arguments but more efficient query to database - * @param generalGroup symbol that identify exercise group of specific field of study (for example 12K2) - * @param kGroup computer laboratory group (non required) - * @param lGroup laboratory group (non required) - * @param pGroup project group (non required) - * @return Set of exams for specific groups - */ - public Set getExamByGroup(String generalGroup, String kGroup, String lGroup, String pGroup) { -// TODO: change to 1 query instead of 4 -// TODO: change arguments to list -// TODO: N + 1 - Set exams = examRepository.findByExamGroupsContaining(generalGroup); - if (kGroup != null) exams.addAll(examRepository.findByExamGroupsContaining(kGroup)); - if (lGroup != null) exams.addAll(examRepository.findByExamGroupsContaining(lGroup)); - if (pGroup != null) exams.addAll(examRepository.findByExamGroupsContaining(pGroup)); - return exams; - } - - /** - * flexible number of arguments but inefficient query to database - * @param generalGroup symbol that identify exercise group of specific field of study (for example 12K2) - * @param groups set od groups + * @param groups set od groups (max 4) * @return set of exams for specific groups */ - public Set getExamByGroupsSet(String generalGroup, Set groups) { -// TODO: change arguments to list + public Set getExamByGroup(Set groups) { // TODO: N + 1 - throw new UnsupportedOperationException("Not supported yet."); -// Set exams = examRepository.findByExamGroupsContaining(generalGroup); -// if (kGroup != null) exams.addAll(examRepository.findByExamGroupsContaining(kGroup)); -// if (lGroup != null) exams.addAll(examRepository.findByExamGroupsContaining(lGroup)); -// if (pGroup != null) exams.addAll(examRepository.findByExamGroupsContaining(pGroup)); -// return exams; + if(groups.size() > 4) + throw new UnsupportedOperationException("Number of groups exceeds 4"); + List groupList = new ArrayList<>(groups); + Set exams = switch (groupList.size()) { + case 4 -> examRepository.findExamsByGroupsIdentifier( + groupList.get(0), groupList.get(1), groupList.get(2), groupList.get(3)); + case 3 -> examRepository.findExamsByGroupsIdentifier( + groupList.get(0), groupList.get(1), groupList.get(2)); + case 2 -> examRepository.findExamsByGroupsIdentifier( + groupList.get(0), groupList.get(1)); + case 1 -> examRepository.findExamsByGroupsIdentifier( + groupList.get(0)); + default -> Set.of(); + }; + return exams; } } From b4d5ea72a55d18c7971fe7dc3cc33a699026a245 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Wed, 6 Aug 2025 19:33:32 +0200 Subject: [PATCH 014/116] add custom exception in mapper --- .../pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java | 3 ++- .../org/pkwmtt/exceptions/ExamTypeNotExistsException.java | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/pkwmtt/exceptions/ExamTypeNotExistsException.java diff --git a/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java index fe2b726..867efac 100644 --- a/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java +++ b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java @@ -4,6 +4,7 @@ import org.pkwmtt.examCalendar.dto.ExamDto; import org.pkwmtt.examCalendar.entity.Exam; import org.pkwmtt.examCalendar.repository.ExamTypeRepository; +import org.pkwmtt.exceptions.ExamTypeNotExistsException; import org.springframework.stereotype.Component; /** @@ -42,7 +43,7 @@ public Exam mapToExistingExam(ExamDto examDto, int id) { .description(examDto.getDescription()) .date(examDto.getDate()) .examGroups(examDto.getExamGroups()) - .examType(examTypeRepository.findByName(examDto.getExamType()).orElseThrow()) + .examType(examTypeRepository.findByName(examDto.getExamType()).orElseThrow(() -> new ExamTypeNotExistsException(examDto.getExamType()))) .build(); } } diff --git a/src/main/java/org/pkwmtt/exceptions/ExamTypeNotExistsException.java b/src/main/java/org/pkwmtt/exceptions/ExamTypeNotExistsException.java new file mode 100644 index 0000000..5e8171a --- /dev/null +++ b/src/main/java/org/pkwmtt/exceptions/ExamTypeNotExistsException.java @@ -0,0 +1,7 @@ +package org.pkwmtt.exceptions; + +public class ExamTypeNotExistsException extends RuntimeException { + public ExamTypeNotExistsException(String examType) { + super("Invalid exam type " + examType); + } +} From a59143b287688d8a05cf20cfef0b84d86c0b3cc4 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Thu, 7 Aug 2025 14:22:12 +0200 Subject: [PATCH 015/116] Fix N+1 query problem by adding fetch join --- .../java/org/pkwmtt/examCalendar/ExamService.java | 4 +--- .../examCalendar/repository/ExamRepository.java | 15 ++++----------- .../repository/ExamRepositoryTest.java | 4 ++-- 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamService.java b/src/main/java/org/pkwmtt/examCalendar/ExamService.java index 1a63b02..1b1c7b2 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamService.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamService.java @@ -56,11 +56,10 @@ public Exam getExamById(int id) { * @return set of exams for specific groups */ public Set getExamByGroup(Set groups) { -// TODO: N + 1 if(groups.size() > 4) throw new UnsupportedOperationException("Number of groups exceeds 4"); List groupList = new ArrayList<>(groups); - Set exams = switch (groupList.size()) { + return switch (groupList.size()) { case 4 -> examRepository.findExamsByGroupsIdentifier( groupList.get(0), groupList.get(1), groupList.get(2), groupList.get(3)); case 3 -> examRepository.findExamsByGroupsIdentifier( @@ -71,6 +70,5 @@ public Set getExamByGroup(Set groups) { groupList.get(0)); default -> Set.of(); }; - return exams; } } diff --git a/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java index 512b0c5..e6cdc88 100644 --- a/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java +++ b/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java @@ -8,10 +8,8 @@ import java.util.Set; public interface ExamRepository extends JpaRepository { - @Query("select e from Exam e where e.examGroups LIKE %:gs%") - Set findExamsByGroupSignature(@Param("gs")String groupSignature); - @Query("SELECT e FROM Exam e WHERE " + + @Query("SELECT e FROM Exam e JOIN FETCH e.examType WHERE " + "e.examGroups LIKE CONCAT('%', :g1, '%') OR " + "e.examGroups LIKE CONCAT('%', :g2, '%') OR " + "e.examGroups LIKE CONCAT('%', :g3, '%') OR " + @@ -23,7 +21,7 @@ Set findExamsByGroupsIdentifier( @Param("g4") String group4 ); - @Query("SELECT e FROM Exam e WHERE " + + @Query("SELECT e FROM Exam e JOIN FETCH e.examType WHERE " + "e.examGroups LIKE CONCAT('%', :g1, '%') OR " + "e.examGroups LIKE CONCAT('%', :g2, '%') OR " + "e.examGroups LIKE CONCAT('%', :g3, '%') ") @@ -33,7 +31,7 @@ Set findExamsByGroupsIdentifier( @Param("g3") String group3 ); - @Query("SELECT e FROM Exam e WHERE " + + @Query("SELECT e FROM Exam e JOIN FETCH e.examType WHERE " + "e.examGroups LIKE CONCAT('%', :g1, '%') OR " + "e.examGroups LIKE CONCAT('%', :g2, '%')" ) Set findExamsByGroupsIdentifier( @@ -41,15 +39,10 @@ Set findExamsByGroupsIdentifier( @Param("g2") String group2 ); - @Query("SELECT e FROM Exam e WHERE " + + @Query("SELECT e FROM Exam e JOIN FETCH e.examType WHERE " + "e.examGroups LIKE CONCAT('%', :gg, '%')") Set findExamsByGroupsIdentifier( @Param("gg") String group ); - /** - * @param groupSignature symbol that identifies group - * @return set of Exams for specific group - */ - Set findByExamGroupsContaining(String groupSignature); } \ No newline at end of file diff --git a/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java b/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java index 350d7c3..90f1614 100644 --- a/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java @@ -11,9 +11,9 @@ import java.time.LocalDateTime; import java.util.List; import java.util.Set; -import java.util.stream.Collectors; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; @Slf4j @DataJpaTest From 8224356a4e4bf0b093032ec5446ce886b4ef1289 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Thu, 7 Aug 2025 14:30:46 +0200 Subject: [PATCH 016/116] add javadoc for ExamRepository --- .../repository/ExamRepository.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java index e6cdc88..e6d3454 100644 --- a/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java +++ b/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java @@ -9,6 +9,14 @@ public interface ExamRepository extends JpaRepository { + /** + * fetch all data using one query + * @param group1 group identifier + * @param group2 group identifier + * @param group3 group identifier + * @param group4 group identifier + * @return set of Exams for specific groups + */ @Query("SELECT e FROM Exam e JOIN FETCH e.examType WHERE " + "e.examGroups LIKE CONCAT('%', :g1, '%') OR " + "e.examGroups LIKE CONCAT('%', :g2, '%') OR " + @@ -21,6 +29,13 @@ Set findExamsByGroupsIdentifier( @Param("g4") String group4 ); + /** + * fetch all data using one query + * @param group1 group identifier + * @param group2 group identifier + * @param group3 group identifier + * @return set of Exams for specific groups + */ @Query("SELECT e FROM Exam e JOIN FETCH e.examType WHERE " + "e.examGroups LIKE CONCAT('%', :g1, '%') OR " + "e.examGroups LIKE CONCAT('%', :g2, '%') OR " + @@ -31,6 +46,12 @@ Set findExamsByGroupsIdentifier( @Param("g3") String group3 ); + /** + * fetch all data using one query + * @param group1 group identifier + * @param group2 group identifier + * @return set of Exams for specific groups + */ @Query("SELECT e FROM Exam e JOIN FETCH e.examType WHERE " + "e.examGroups LIKE CONCAT('%', :g1, '%') OR " + "e.examGroups LIKE CONCAT('%', :g2, '%')" ) @@ -39,6 +60,11 @@ Set findExamsByGroupsIdentifier( @Param("g2") String group2 ); + /** + * fetch all data using one query + * @param group group identifier + * @return set of Exams for specific group + */ @Query("SELECT e FROM Exam e JOIN FETCH e.examType WHERE " + "e.examGroups LIKE CONCAT('%', :gg, '%')") Set findExamsByGroupsIdentifier( From 490a0594bdc13380ec9031dafba45698c1bf96d5 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Thu, 7 Aug 2025 16:21:05 +0200 Subject: [PATCH 017/116] complete ExamService tests --- .../org/pkwmtt/examCalendar/ExamService.java | 5 +- .../UnsupportedCountOfArgumentsException.java | 8 + .../pkwmtt/examCalendar/ExamServiceTest.java | 177 +++++++++++++++++- 3 files changed, 182 insertions(+), 8 deletions(-) create mode 100644 src/main/java/org/pkwmtt/exceptions/UnsupportedCountOfArgumentsException.java diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamService.java b/src/main/java/org/pkwmtt/examCalendar/ExamService.java index 1b1c7b2..e1306e6 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamService.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamService.java @@ -6,6 +6,7 @@ import org.pkwmtt.examCalendar.entity.Exam; import org.pkwmtt.examCalendar.mapper.ExamDtoToExamMapper; import org.pkwmtt.examCalendar.repository.ExamRepository; +import org.pkwmtt.exceptions.UnsupportedCountOfArgumentsException; import org.springframework.stereotype.Service; import java.util.*; @@ -56,8 +57,8 @@ public Exam getExamById(int id) { * @return set of exams for specific groups */ public Set getExamByGroup(Set groups) { - if(groups.size() > 4) - throw new UnsupportedOperationException("Number of groups exceeds 4"); + if (groups.size() > 4 || groups.isEmpty()) + throw new UnsupportedCountOfArgumentsException(1, 5, groups.size()); List groupList = new ArrayList<>(groups); return switch (groupList.size()) { case 4 -> examRepository.findExamsByGroupsIdentifier( diff --git a/src/main/java/org/pkwmtt/exceptions/UnsupportedCountOfArgumentsException.java b/src/main/java/org/pkwmtt/exceptions/UnsupportedCountOfArgumentsException.java new file mode 100644 index 0000000..709978a --- /dev/null +++ b/src/main/java/org/pkwmtt/exceptions/UnsupportedCountOfArgumentsException.java @@ -0,0 +1,8 @@ +package org.pkwmtt.exceptions; + +public class UnsupportedCountOfArgumentsException extends RuntimeException { + public UnsupportedCountOfArgumentsException(int expectedMin, int expectedMax, int provided) { + super("Invalid count of arguments provided: " + provided + + " expected more than: " + expectedMin + " less than: " + expectedMax); + } +} diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java index 1e13e12..1686650 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java @@ -2,6 +2,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -10,11 +11,12 @@ import org.pkwmtt.examCalendar.entity.ExamType; import org.pkwmtt.examCalendar.mapper.ExamDtoToExamMapper; import org.pkwmtt.examCalendar.repository.ExamRepository; +import org.pkwmtt.exceptions.UnsupportedCountOfArgumentsException; import java.lang.reflect.Field; import java.time.LocalDateTime; -import java.util.NoSuchElementException; -import java.util.Optional; +import java.util.*; +import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; @@ -67,7 +69,7 @@ void addExam() { verify(examDtoToExamMapper).mapToNewExam(examDto); } -/************************************************************************************/ + /************************************************************************************/ //modify exam @Test void shouldModifyExamWhenIdExists() { @@ -101,6 +103,7 @@ void shouldThrowWhenExamIdNotExists() { verify(examRepository, never()).save(any()); assertEquals("Exam not found", exception.getMessage()); } + /************************************************************************************/ //delete exam @Test @@ -130,8 +133,7 @@ void shouldThrowExceptionWhenExamIdNotExists() { } /************************************************************************************/ -// get exam by id - +// getExamById @Test void getExamById() { // given @@ -158,8 +160,171 @@ void shouldThrowExceptionWhenExamNotFound() { assertEquals("Exam not found", exception.getMessage()); } + // getExamByGroup + @Test + void shouldThrowWithMoreThan4Arguments() { +// given + Set groups = new HashSet<>(); + groups.add("12K2"); + groups.add("13L1"); + groups.add("13A2"); + groups.add("41S2"); + groups.add("11S3"); +// when + RuntimeException exception = assertThrows( + UnsupportedCountOfArgumentsException.class, + () -> examService.getExamByGroup(groups) + ); +// then + assertEquals( + "Invalid count of arguments provided: 5 expected more than: 1 less than: 5", + exception.getMessage() + ); + } + + + @Test + void shouldCallRepositoryWith4Arguments() { +// given + Set groups = new HashSet<>(); + groups.add("12K2"); + groups.add("13L1"); + groups.add("13A2"); + groups.add("41S2"); + Exam mockExam = mock(Exam.class); + Set exams = new HashSet<>(); + exams.add(mockExam); + when(examRepository.findExamsByGroupsIdentifier(any(), any(), any(), any())).thenReturn(exams); +// when + Set result = examService.getExamByGroup(groups); +// then + List> cap = new ArrayList<>(); + for (int i = 0; i < 4; ++i) + cap.add(ArgumentCaptor.forClass(String.class)); + + verify(examRepository).findExamsByGroupsIdentifier( + cap.get(0).capture(), + cap.get(1).capture(), + cap.get(2).capture(), + cap.get(3).capture() + ); + Set passedGroups = cap.stream().map(ArgumentCaptor::getValue).collect(Collectors.toSet()); + + assertEquals(groups, passedGroups); + assertEquals(exams, result); + } + + + @Test + void shouldCallRepositoryWith3Arguments() { +// given + Set groups = new HashSet<>(); + groups.add("12K2"); + groups.add("13L1"); + groups.add("13A2"); + Exam mockExam = mock(Exam.class); + Set exams = new HashSet<>(); + exams.add(mockExam); + when(examRepository.findExamsByGroupsIdentifier(any(), any(), any())).thenReturn(exams); +// when + Set result = examService.getExamByGroup(groups); +// then + List> cap = new ArrayList<>(); + for (int i = 0; i < 3; ++i) + cap.add(ArgumentCaptor.forClass(String.class)); + + verify(examRepository).findExamsByGroupsIdentifier( + cap.get(0).capture(), + cap.get(1).capture(), + cap.get(2).capture() + ); + Set passedGroups = cap.stream().map(ArgumentCaptor::getValue).collect(Collectors.toSet()); + + assertEquals(groups, passedGroups); + assertEquals(exams, result); + } + + @Test + void shouldCallRepositoryWith2Arguments() { +// given + Set groups = new HashSet<>(); + groups.add("12K2"); + groups.add("13L1"); + Exam mockExam = mock(Exam.class); + Set exams = new HashSet<>(); + exams.add(mockExam); + when(examRepository.findExamsByGroupsIdentifier(any(), any())).thenReturn(exams); +// when + Set result = examService.getExamByGroup(groups); +// then + List> cap = new ArrayList<>(); + for (int i = 0; i < 2; ++i) + cap.add(ArgumentCaptor.forClass(String.class)); + + verify(examRepository).findExamsByGroupsIdentifier( + cap.get(0).capture(), + cap.get(1).capture() + ); + Set passedGroups = cap.stream().map(ArgumentCaptor::getValue).collect(Collectors.toSet()); + + assertEquals(groups, passedGroups); + assertEquals(exams, result); + } + + @Test + void shouldCallRepositoryWithSingleArguments() { +// given + Set groups = new HashSet<>(); + groups.add("12K2"); + Exam mockExam = mock(Exam.class); + Set exams = new HashSet<>(); + exams.add(mockExam); + when(examRepository.findExamsByGroupsIdentifier(any())).thenReturn(exams); +// when + Set result = examService.getExamByGroup(groups); +// then + ArgumentCaptor cap = ArgumentCaptor.forClass(String.class); + + verify(examRepository).findExamsByGroupsIdentifier(cap.capture()); + Set passedGroups = new HashSet<>(); + passedGroups.add(cap.getValue()); + + assertEquals(groups, passedGroups); + assertEquals(exams, result); + } + + @Test - void getExamByGroup() { + void shouldCallRepositoryWithDuplicatesOf4UniqueArguments() { +// given + Set groups = new HashSet<>(); + groups.add("12K2"); + groups.add("13L1"); + groups.add("13A2"); + groups.add("41S2"); + groups.add("41S2"); + groups.add("13L1"); + Exam mockExam = mock(Exam.class); + Set exams = new HashSet<>(); + exams.add(mockExam); + when(examRepository.findExamsByGroupsIdentifier(any(), any(), any(), any())).thenReturn(exams); +// when + Set result = examService.getExamByGroup(groups); +// then + List> cap = new ArrayList<>(); + for (int i = 0; i < 4; ++i) + cap.add(ArgumentCaptor.forClass(String.class)); + verify(examRepository).findExamsByGroupsIdentifier( + cap.get(0).capture(), + cap.get(1).capture(), + cap.get(2).capture(), + cap.get(3).capture() + ); + Set passedGroups = cap.stream().map(ArgumentCaptor::getValue).collect(Collectors.toSet()); + + assertEquals(groups, passedGroups); + assertEquals(exams, result); } + } \ No newline at end of file From 2de511dd551e3a0199ec6f06053bbb991bcb050d Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Thu, 7 Aug 2025 16:28:45 +0200 Subject: [PATCH 018/116] add new exception type for non-existing items --- src/main/java/org/pkwmtt/examCalendar/ExamService.java | 7 ++++--- .../exceptions/NoSuchElementWithProvidedIdException.java | 7 +++++++ src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java | 1 + 3 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 src/main/java/org/pkwmtt/exceptions/NoSuchElementWithProvidedIdException.java diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamService.java b/src/main/java/org/pkwmtt/examCalendar/ExamService.java index e1306e6..d1418be 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamService.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamService.java @@ -6,6 +6,7 @@ import org.pkwmtt.examCalendar.entity.Exam; import org.pkwmtt.examCalendar.mapper.ExamDtoToExamMapper; import org.pkwmtt.examCalendar.repository.ExamRepository; +import org.pkwmtt.exceptions.NoSuchElementWithProvidedIdException; import org.pkwmtt.exceptions.UnsupportedCountOfArgumentsException; import org.springframework.stereotype.Service; @@ -32,7 +33,7 @@ public int addExam(ExamDto examDto) { * @param id of exam that need to be modified */ public void modifyExam(ExamDto examDto, int id) { - examRepository.findById(id).orElseThrow(() -> new NoSuchElementException("Exam not found")); //TODO: change exception type + examRepository.findById(id).orElseThrow(() -> new NoSuchElementWithProvidedIdException(id)); examRepository.save(examMapper.mapToExistingExam(examDto, id)); } @@ -40,7 +41,7 @@ public void modifyExam(ExamDto examDto, int id) { * @param id of exam */ public void deleteExam(int id) { - examRepository.findById(id).orElseThrow(() -> new NoSuchElementException("Exam not found")); //TODO: change exception type + examRepository.findById(id).orElseThrow(() -> new NoSuchElementWithProvidedIdException(id)); examRepository.deleteById(id); } @@ -49,7 +50,7 @@ public void deleteExam(int id) { * @return exam */ public Exam getExamById(int id) { - return examRepository.findById(id).orElseThrow(() -> new NoSuchElementException("Exam not found")); //TODO: change exception typ + return examRepository.findById(id).orElseThrow(() -> new NoSuchElementWithProvidedIdException(id)); } /** diff --git a/src/main/java/org/pkwmtt/exceptions/NoSuchElementWithProvidedIdException.java b/src/main/java/org/pkwmtt/exceptions/NoSuchElementWithProvidedIdException.java new file mode 100644 index 0000000..e17eead --- /dev/null +++ b/src/main/java/org/pkwmtt/exceptions/NoSuchElementWithProvidedIdException.java @@ -0,0 +1,7 @@ +package org.pkwmtt.exceptions; + +public class NoSuchElementWithProvidedIdException extends RuntimeException{ + public NoSuchElementWithProvidedIdException(int id) { + super("No such element with id: " + id); + } +} diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java index 1686650..fbeff39 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java @@ -325,6 +325,7 @@ void shouldCallRepositoryWithDuplicatesOf4UniqueArguments() { assertEquals(groups, passedGroups); assertEquals(exams, result); + assertEquals(4, passedGroups.size()); } } \ No newline at end of file From 29e5bac50165ca183bdf9dacc5862157a1c322f9 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Thu, 7 Aug 2025 16:40:25 +0200 Subject: [PATCH 019/116] add endpoint for fetching groupTypes --- .../java/org/pkwmtt/examCalendar/ExamController.java | 11 +++++++++++ .../java/org/pkwmtt/examCalendar/ExamService.java | 10 ++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamController.java b/src/main/java/org/pkwmtt/examCalendar/ExamController.java index 2d72123..1791c8c 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamController.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamController.java @@ -4,11 +4,13 @@ import lombok.RequiredArgsConstructor; import org.pkwmtt.examCalendar.dto.ExamDto; import org.pkwmtt.examCalendar.entity.Exam; +import org.pkwmtt.examCalendar.entity.ExamType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import java.net.URI; +import java.util.List; import java.util.Set; @RequiredArgsConstructor @@ -73,4 +75,13 @@ public ResponseEntity> getExams(@RequestParam Set groups){ return ResponseEntity.ok(examService.getExamByGroup(groups)); } + /** + * @return 200 ok with list of available exam types + */ +// should be moved to new controller? + @GetMapping("/groups") + public ResponseEntity> getExamTypes(){ + return ResponseEntity.ok(examService.getExamTypes()); + } + } \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamService.java b/src/main/java/org/pkwmtt/examCalendar/ExamService.java index d1418be..8da2396 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamService.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamService.java @@ -4,8 +4,10 @@ import lombok.RequiredArgsConstructor; import org.pkwmtt.examCalendar.dto.ExamDto; import org.pkwmtt.examCalendar.entity.Exam; +import org.pkwmtt.examCalendar.entity.ExamType; import org.pkwmtt.examCalendar.mapper.ExamDtoToExamMapper; import org.pkwmtt.examCalendar.repository.ExamRepository; +import org.pkwmtt.examCalendar.repository.ExamTypeRepository; import org.pkwmtt.exceptions.NoSuchElementWithProvidedIdException; import org.pkwmtt.exceptions.UnsupportedCountOfArgumentsException; import org.springframework.stereotype.Service; @@ -19,6 +21,7 @@ public class ExamService { private final ExamRepository examRepository; private final ExamDtoToExamMapper examMapper; + private final ExamTypeRepository examTypeRepository; /** * @param examDto details of exam @@ -73,4 +76,11 @@ public Set getExamByGroup(Set groups) { default -> Set.of(); }; } + + /** + * @return list of examTypes + */ + public List getExamTypes() { + return examTypeRepository.findAll(); + } } From 75d962a2b15cea53a130e0673db4b068302aa178 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Mon, 11 Aug 2025 13:49:59 +0200 Subject: [PATCH 020/116] test data validation in ExamDto --- pom.xml | 9 +- .../pkwmtt/examCalendar/ExamController.java | 4 +- .../org/pkwmtt/examCalendar/dto/ExamDto.java | 4 + .../pkwmtt/examCalendar/dto/ExamDtoTest.java | 150 ++++++++++++++++++ 4 files changed, 163 insertions(+), 4 deletions(-) create mode 100644 src/test/java/org/pkwmtt/examCalendar/dto/ExamDtoTest.java diff --git a/pom.xml b/pom.xml index bceddca..55c7c01 100644 --- a/pom.xml +++ b/pom.xml @@ -94,6 +94,7 @@ mockito-core 5.18.0 + @@ -140,8 +141,12 @@ org.springframework.boot spring-boot-starter-actuator - - + + org.springframework.boot + spring-boot-starter-validation + + + diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamController.java b/src/main/java/org/pkwmtt/examCalendar/ExamController.java index 1791c8c..f319224 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamController.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamController.java @@ -33,7 +33,7 @@ public ResponseEntity addExam(@RequestBody @Valid ExamDto examDto) { .buildAndExpand(id) .toUri(); return ResponseEntity.created(uri).build(); -// TODO: test data validation in controller +// TODO: test not null validation in controller } /** @@ -79,7 +79,7 @@ public ResponseEntity> getExams(@RequestParam Set groups){ * @return 200 ok with list of available exam types */ // should be moved to new controller? - @GetMapping("/groups") + @GetMapping("/exam-types") public ResponseEntity> getExamTypes(){ return ResponseEntity.ok(examService.getExamTypes()); } diff --git a/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java b/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java index 8627581..81647a3 100644 --- a/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java +++ b/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java @@ -16,14 +16,18 @@ public class ExamDto { @NotBlank(message = "Title is required") @Size(max = 255, message = "max size of field is 255") private final String title; + @Size(max = 255, message = "max size of field is 255") private final String description; + @Future(message = "Date must be in the future") @NotNull private final LocalDateTime date; + @NotBlank @Size(max = 255, message = "max size of field is 255") private final String examGroups; + @NotNull private final String examType; } diff --git a/src/test/java/org/pkwmtt/examCalendar/dto/ExamDtoTest.java b/src/test/java/org/pkwmtt/examCalendar/dto/ExamDtoTest.java new file mode 100644 index 0000000..b14edc6 --- /dev/null +++ b/src/test/java/org/pkwmtt/examCalendar/dto/ExamDtoTest.java @@ -0,0 +1,150 @@ +package org.pkwmtt.examCalendar.dto; + +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; + +import java.time.LocalDateTime; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ExamDtoTest { + + private final Validator validator; + + public ExamDtoTest() { + this.validator = Validation.buildDefaultValidatorFactory().getValidator(); + } + + @Mock + private ExamDto examDto; + + @Test + void validData() { +// given + ExamDto examDto = new ExamDto( + "Math exam", + "First exam", + LocalDateTime.now().plusDays(1), + "12K2, K04", + "exam" + ); +// when, then + assertTrue(validator.validate(examDto).isEmpty()); + } + + + // empty Strings + @Test + void emptyStringTitle() { + // given + ExamDto examDto = new ExamDto( + "", + "First exam", + LocalDateTime.now().plusDays(1), + "12K2, K04", + "exam" + ); +// when + Set> violations = validator.validate(examDto); +// then + assertFalse(validator.validate(examDto).isEmpty()); + assertTrue(violations.stream().anyMatch(v -> v.getPropertyPath().toString().equals("title"))); + } + + @Test + void emptyExamGroups() { + // given + ExamDto examDto = new ExamDto( + "Math exam", + "First exam", + LocalDateTime.now().plusDays(1), + "", + "exam" + ); +// when + Set> violations = validator.validate(examDto); +// then + assertFalse(validator.validate(examDto).isEmpty()); + assertTrue(violations.stream().anyMatch(v -> v.getPropertyPath().toString().equals("examGroups"))); + } + +// to long Strings + + @Test + void toLongStringTitle() { + // given + ExamDto examDto = new ExamDto( +// 256 characters + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "First exam", + LocalDateTime.now().plusDays(1), + "12K2, K04", + "exam" + ); +// when + Set> violations = validator.validate(examDto); +// then + assertFalse(validator.validate(examDto).isEmpty()); + assertTrue(violations.stream().anyMatch(v -> v.getPropertyPath().toString().equals("title"))); + } + + @Test + void toLongDescription() { + // given + ExamDto examDto = new ExamDto( + "Math exam", +// 256 characters + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + LocalDateTime.now().plusDays(1), + "12K2, K04", + "exam" + ); +// when + Set> violations = validator.validate(examDto); +// then + assertFalse(validator.validate(examDto).isEmpty()); + assertTrue(violations.stream().anyMatch(v -> v.getPropertyPath().toString().equals("description"))); + } + + @Test + void toLongExamGroups() { + // given + ExamDto examDto = new ExamDto( + "Math exam", + "First exam", + LocalDateTime.now().plusDays(1), + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "exam" + ); +// when + Set> violations = validator.validate(examDto); +// then + assertFalse(validator.validate(examDto).isEmpty()); + assertTrue(violations.stream().anyMatch(v -> v.getPropertyPath().toString().equals("examGroups"))); + } + +// date not in future + + @Test + void dateNotInFuture() { + // given + ExamDto examDto = new ExamDto( + "Math exam", + "First exam", + LocalDateTime.now().minusHours(1), + "12K2, K04", + "exam" + ); + // when + Set> violations = validator.validate(examDto); +// then + assertFalse(validator.validate(examDto).isEmpty()); + assertTrue(violations.stream().anyMatch(v -> v.getPropertyPath().toString().equals("date"))); + } + +} \ No newline at end of file From 82185749d05dc955a5803fee81f8ecf5ac3cc56e Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Mon, 11 Aug 2025 19:44:13 +0200 Subject: [PATCH 021/116] exception handling --- .../pkwmtt/examCalendar/ExamController.java | 5 +- .../examCalendar/ExamControllerAdvice.java | 48 +++++++++++++++++++ .../mapper/ExamDtoToExamMapper.java | 2 +- 3 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamController.java b/src/main/java/org/pkwmtt/examCalendar/ExamController.java index f319224..7c69cff 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamController.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamController.java @@ -1,11 +1,13 @@ package org.pkwmtt.examCalendar; import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; import lombok.RequiredArgsConstructor; import org.pkwmtt.examCalendar.dto.ExamDto; import org.pkwmtt.examCalendar.entity.Exam; import org.pkwmtt.examCalendar.entity.ExamType; import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; @@ -13,6 +15,7 @@ import java.util.List; import java.util.Set; +@Validated @RequiredArgsConstructor @RequestMapping("/pkwmtt/api/v1/exams") @RestController @@ -42,7 +45,7 @@ public ResponseEntity addExam(@RequestBody @Valid ExamDto examDto) { * @return 204 no content */ @PutMapping("/{id}") - public ResponseEntity modifyExam(@PathVariable int id,@RequestBody @Valid ExamDto examDto) { + public ResponseEntity modifyExam(@PathVariable @Positive int id, @RequestBody @Valid ExamDto examDto) { examService.modifyExam(examDto, id); return ResponseEntity.noContent().build(); } diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java b/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java new file mode 100644 index 0000000..845be7a --- /dev/null +++ b/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java @@ -0,0 +1,48 @@ +package org.pkwmtt.examCalendar; + +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import org.pkwmtt.exceptions.ErrorResponseDTO; +import org.pkwmtt.exceptions.ExamTypeNotExistsException; +import org.pkwmtt.exceptions.NoSuchElementWithProvidedIdException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import java.util.stream.Collectors; + +@RestControllerAdvice +public class ExamControllerAdvice { + +// TODO: handle or remove UnsupportedCountOfArgumentsException + + @ExceptionHandler(NoSuchElementWithProvidedIdException.class) + public ResponseEntity handleNoSuchElementWithProvidedIdException(NoSuchElementWithProvidedIdException e) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponseDTO(e.getMessage())); + } + + @ExceptionHandler(ExamTypeNotExistsException.class) + public ResponseEntity handleExamTypeNotExistsException(ExamTypeNotExistsException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorResponseDTO(e.getMessage())); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + String message = e.getBindingResult().getFieldErrors().stream() + .map(field -> field.getField() + " : " + field.getDefaultMessage()) + .collect(Collectors.joining(", ")); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorResponseDTO(message)); + } + + @ExceptionHandler(ConstraintViolationException.class) + public ResponseEntity handleConstraintViolationException(ConstraintViolationException e) { + String message = e.getConstraintViolations().stream() + .map(field -> field.getPropertyPath() + " : " + field.getMessage()) + .collect(Collectors.joining(", ")); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorResponseDTO(message)); + } + + +} diff --git a/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java index 867efac..343f70e 100644 --- a/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java +++ b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java @@ -26,7 +26,7 @@ public Exam mapToNewExam(ExamDto examDto) { .description(examDto.getDescription()) .date(examDto.getDate()) .examGroups(examDto.getExamGroups()) - .examType(examTypeRepository.findByName(examDto.getExamType()).orElseThrow()) + .examType(examTypeRepository.findByName(examDto.getExamType()).orElseThrow(() -> new ExamTypeNotExistsException(examDto.getExamType()))) .build(); } From 8600c01a6c948cc097fadaa1269f7b5d644e72c8 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Tue, 12 Aug 2025 12:53:14 +0200 Subject: [PATCH 022/116] fix tests --- pom.xml | 25 ++++---- .../org/pkwmtt/examCalendar/entity/Exam.java | 1 + .../pkwmtt/examCalendar/entity/ExamType.java | 2 +- .../examCalendar/ExamControllerTest.java | 64 +++++++++++++++++++ .../pkwmtt/examCalendar/ExamServiceTest.java | 2 +- src/test/resources/application.properties | 10 +-- src/test/resources/schema.sql | 60 +++++++++++++++++ 7 files changed, 146 insertions(+), 18 deletions(-) create mode 100644 src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java create mode 100644 src/test/resources/schema.sql diff --git a/pom.xml b/pom.xml index 55c7c01..1e54dfb 100644 --- a/pom.xml +++ b/pom.xml @@ -61,11 +61,11 @@ - - com.h2database - h2 - runtime - + + + + + @@ -78,12 +78,13 @@ test + junit junit 4.13.1 - + @@ -95,12 +96,12 @@ 5.18.0 - - - - - - + + + com.h2database + h2 + test + diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java b/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java index 3465856..89fe16d 100644 --- a/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java +++ b/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java @@ -34,6 +34,7 @@ public class Exam { @JoinColumn(name = "exam_type_id") private ExamType examType; + @SuppressWarnings("unused") public static class Builder { public Exam build() { // max length of group identifier is 6 diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/ExamType.java b/src/main/java/org/pkwmtt/examCalendar/entity/ExamType.java index 14a8549..6c5355f 100644 --- a/src/main/java/org/pkwmtt/examCalendar/entity/ExamType.java +++ b/src/main/java/org/pkwmtt/examCalendar/entity/ExamType.java @@ -11,7 +11,7 @@ @Builder @AllArgsConstructor @RequiredArgsConstructor -@Table(name = "`exam_type`") +@Table(name = "exam_type") public class ExamType { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java new file mode 100644 index 0000000..db62e1c --- /dev/null +++ b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java @@ -0,0 +1,64 @@ +package org.pkwmtt.examCalendar; + +import lombok.RequiredArgsConstructor; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.pkwmtt.examCalendar.entity.Exam; +import org.pkwmtt.examCalendar.mapper.ExamDtoToExamMapper; +import org.pkwmtt.examCalendar.repository.ExamRepository; +import org.pkwmtt.examCalendar.repository.ExamTypeRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.TestPropertySource; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * integration tests of ExamCalendar + */ +@SpringBootTest +@AutoConfigureMockMvc +@TestPropertySource(locations = "classpath:application.properties") +@RequiredArgsConstructor +class ExamControllerTest { + + @Autowired + private final ExamTypeRepository examTypeRepository; + @Autowired + private final ExamRepository examRepository; + @Autowired + private final ExamDtoToExamMapper examDtoToExamMapper; + @Autowired + private final ExamService examService; + + @BeforeEach + void setup(){ + examRepository.deleteAll(); + } + + @Test + void addExam() { +// Exam exam = Exam.builder().build(); + } + + @Test + void modifyExam() { + } + + @Test + void deleteExam() { + } + + @Test + void getExam() { + } + + @Test + void getExams() { + } + + @Test + void getExamTypes() { + } +} \ No newline at end of file diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java index fbeff39..c86b0ab 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java @@ -55,7 +55,7 @@ void addExam() { // assign exam id in repository when(examRepository.save(exam)).thenAnswer(invocation -> { - Exam newExam = invocation.getArgument(0); + Exam newExam = invocation.getArgument(0, Exam.class); Field field = Exam.class.getDeclaredField("examId"); field.setAccessible(true); field.set(newExam, examId); diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index 0c7b664..7f26e4f 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -1,10 +1,12 @@ -spring.datasource.url=jdbc:h2://mem:db;DB_CLOSE_DELAY=-1 +spring.datasource.url=jdbc:h2://mem:db;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false spring.datasource.username=sa spring.datasource.password=sa spring.jpa.show-sql=true spring.jpa.database-platform=org.hibernate.dialect.H2Dialect spring.datasource.driver-class-name=org.h2.Driver -#spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false -spring.jpa.hibernate.ddl-auto=update -#spring.datasource.hikari.initialization-fail-timeout=0 +spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false +spring.jpa.hibernate.ddl-auto=none +spring.datasource.hikari.initialization-fail-timeout=0 + + diff --git a/src/test/resources/schema.sql b/src/test/resources/schema.sql new file mode 100644 index 0000000..760aaf7 --- /dev/null +++ b/src/test/resources/schema.sql @@ -0,0 +1,60 @@ +DROP TABLE IF EXISTS exams; +DROP TABLE IF EXISTS exam_type; +DROP TABLE IF EXISTS general_group; +DROP TABLE IF EXISTS groups; +DROP TABLE IF EXISTS otp_codes; +DROP TABLE IF EXISTS users; + +CREATE TABLE exam_type +( + exam_type_id INT PRIMARY KEY AUTO_INCREMENT, + name VARCHAR(255) +); + +CREATE TABLE general_group +( + general_group_id INT PRIMARY KEY AUTO_INCREMENT, + name VARCHAR(255) +); + +CREATE TABLE exams +( + exam_id INT PRIMARY KEY AUTO_INCREMENT, + title VARCHAR(255), + description VARCHAR(255), + date TIMESTAMP(6), + "groups" VARCHAR(255), + exam_type_id INT NOT NULL, + FOREIGN KEY (exam_type_id) REFERENCES exam_type (exam_type_id) +); + +CREATE TABLE groups +( + group_id INT PRIMARY KEY AUTO_INCREMENT, + letter CHAR(1) NOT NULL, + group_count INT NOT NULL, + general_group_id INT NOT NULL, + name VARCHAR(255), + FOREIGN KEY (general_group_id) REFERENCES general_group (general_group_id) +); + +CREATE TABLE users +( + user_id INT PRIMARY KEY AUTO_INCREMENT, + general_group_id INT NOT NULL, + email VARCHAR(254) NOT NULL, + is_active BOOLEAN NOT NULL, + role VARCHAR(20) NOT NULL, -- enum zamieniony na VARCHAR + FOREIGN KEY (general_group_id) REFERENCES general_group (general_group_id) +); + +CREATE TABLE otp_codes +( + otp_code_id INT PRIMARY KEY AUTO_INCREMENT, + code VARCHAR(255), + expire TIMESTAMP NOT NULL, + used BOOLEAN NOT NULL, + user_id INT NOT NULL, + timestamp TIMESTAMP(6), + FOREIGN KEY (user_id) REFERENCES users (user_id) +); \ No newline at end of file From 466b651d18f1cd11c3ab1336ee61de83abf8df18 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Wed, 13 Aug 2025 14:38:31 +0200 Subject: [PATCH 023/116] addExam tests --- .../org/pkwmtt/examCalendar/dto/ExamDto.java | 2 +- .../examCalendar/ExamControllerTest.java | 374 +++++++++++++++++- src/test/resources/application.properties | 7 +- 3 files changed, 367 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java b/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java index 81647a3..f1cfdfb 100644 --- a/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java +++ b/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java @@ -13,7 +13,7 @@ @RequiredArgsConstructor public class ExamDto { - @NotBlank(message = "Title is required") + @NotBlank @Size(max = 255, message = "max size of field is 255") private final String title; diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java index db62e1c..cc22053 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java @@ -1,47 +1,397 @@ package org.pkwmtt.examCalendar; -import lombok.RequiredArgsConstructor; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.pkwmtt.examCalendar.dto.ExamDto; import org.pkwmtt.examCalendar.entity.Exam; -import org.pkwmtt.examCalendar.mapper.ExamDtoToExamMapper; +import org.pkwmtt.examCalendar.entity.ExamType; import org.pkwmtt.examCalendar.repository.ExamRepository; import org.pkwmtt.examCalendar.repository.ExamTypeRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.TestPropertySource; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.HashMap; +import java.util.Map; + +import static org.hamcrest.Matchers.containsString; import static org.junit.jupiter.api.Assertions.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * integration tests of ExamCalendar */ @SpringBootTest @AutoConfigureMockMvc -@TestPropertySource(locations = "classpath:application.properties") -@RequiredArgsConstructor class ExamControllerTest { @Autowired - private final ExamTypeRepository examTypeRepository; + private MockMvc mockMvc; + @Autowired - private final ExamRepository examRepository; + private ExamTypeRepository examTypeRepository; + @Autowired - private final ExamDtoToExamMapper examDtoToExamMapper; + private ExamRepository examRepository; + @Autowired - private final ExamService examService; + private ObjectMapper mapper; @BeforeEach - void setup(){ + void setupBeforeEach() { examRepository.deleteAll(); + examTypeRepository.deleteAll(); + examTypeRepository.save(ExamType.builder().name("Project").build()); + } + + +// + /** + * check if addExam endpoint create new exam with correct URI and correct data + */ + @Test + void addExamWithCorrectData() throws Exception { + ExamDto examDtoRequest = new ExamDto( + "Math exam", + "first exam", + LocalDateTime.now().plusDays(1), + "12K2, L04", + "Project" + ); + + String json = mapper.writeValueAsString(examDtoRequest); + + MvcResult result = mockMvc.perform(MockMvcRequestBuilders + .post("/pkwmtt/api/v1/exams") + .contentType("application/json") + .content(json) + ).andDo(print()) + .andExpect(status().isCreated()) + .andExpect(header().string("Location", containsString("/pkwmtt/api/v1/exams/"))) + .andReturn(); + + String location = result.getResponse().getHeader("Location"); + @SuppressWarnings("DataFlowIssue") + int id = Integer.parseInt(location.substring(location.lastIndexOf("/") + 1)); + + Exam examResponse = examRepository.findById(id).orElseThrow(); + + assertEquals(examDtoRequest.getTitle(), examResponse.getTitle()); + assertEquals(examDtoRequest.getDescription(), examResponse.getDescription()); +// compare dates with minutes level precision + assertEquals( + examDtoRequest.getDate().truncatedTo(ChronoUnit.MINUTES), + examResponse.getDate().truncatedTo(ChronoUnit.MINUTES) + ); + assertEquals(examDtoRequest.getExamGroups(), examResponse.getExamGroups()); + assertEquals(examDtoRequest.getExamType(), examResponse.getExamType().getName()); + } + + @Test + void addExamWithBlankExamTitle() throws Exception { + Map requestData = new HashMap<>(); +// no exam title + requestData.put("description", "first exam"); + requestData.put("date", LocalDateTime.now().plusDays(1).toString()); + requestData.put("examGroups", "12K2, L04"); + requestData.put("examType", "Project"); + + String jsonRequest = mapper.writeValueAsString(requestData); + + MvcResult result = mockMvc.perform(MockMvcRequestBuilders + .post("/pkwmtt/api/v1/exams") + .contentType("application/json") + .content(jsonRequest) + ).andDo(print()) + .andExpect(status().isBadRequest()) + .andReturn(); + + JsonNode jsonResponse = mapper.readTree(result.getResponse().getContentAsString()); + assertTrue(jsonResponse.has("message")); + assertEquals("title : must not be blank", jsonResponse.get("message").asText()); + } + + @Test + void addExamWithBlankExamDescription() throws Exception { + Map requestData = new HashMap<>(); + requestData.put("title", "Math exam"); +// no exam description + requestData.put("date", LocalDateTime.now().plusDays(1).toString()); + requestData.put("examGroups", "12K2, L04"); + requestData.put("examType", "Project"); + + String jsonRequest = mapper.writeValueAsString(requestData); + + MvcResult result = mockMvc.perform(MockMvcRequestBuilders + .post("/pkwmtt/api/v1/exams") + .contentType("application/json") + .content(jsonRequest) + ).andDo(print()) + .andExpect(status().isCreated()) + .andReturn(); + + String location = result.getResponse().getHeader("Location"); + @SuppressWarnings("DataFlowIssue") + int id = Integer.parseInt(location.substring(location.lastIndexOf("/") + 1)); + + Exam examResponse = examRepository.findById(id).orElseThrow(); + + assertNull(examResponse.getDescription()); + } + + @Test + void addExamWithBlankDate() throws Exception { + Map requestData = new HashMap<>(); + requestData.put("title", "Math exam"); + requestData.put("description", "first exam"); +// no date + requestData.put("examGroups", "12K2, L04"); + requestData.put("examType", "Project"); + + String jsonRequest = mapper.writeValueAsString(requestData); + + MvcResult result = mockMvc.perform(MockMvcRequestBuilders + .post("/pkwmtt/api/v1/exams") + .contentType("application/json") + .content(jsonRequest) + ).andDo(print()) + .andExpect(status().isBadRequest()) + .andReturn(); + + JsonNode jsonResponse = mapper.readTree(result.getResponse().getContentAsString()); + assertTrue(jsonResponse.has("message")); + assertEquals("date : must not be null", jsonResponse.get("message").asText()); } @Test - void addExam() { -// Exam exam = Exam.builder().build(); + void addExamWithBlankExamGroups() throws Exception { + Map requestData = new HashMap<>(); + requestData.put("title", "Math exam"); + requestData.put("description", "first exam"); + requestData.put("date", LocalDateTime.now().plusDays(1).toString()); +// no examGroups + requestData.put("examType", "Project"); + + String jsonRequest = mapper.writeValueAsString(requestData); + + MvcResult result = mockMvc.perform(MockMvcRequestBuilders + .post("/pkwmtt/api/v1/exams") + .contentType("application/json") + .content(jsonRequest) + ).andDo(print()) + .andExpect(status().isBadRequest()) + .andReturn(); + + JsonNode jsonResponse = mapper.readTree(result.getResponse().getContentAsString()); + assertTrue(jsonResponse.has("message")); + assertEquals("examGroups : must not be blank", jsonResponse.get("message").asText()); } + @Test + void addExamWithNullExamTypes() throws Exception { + Map requestData = new HashMap<>(); + requestData.put("title", "Math exam"); + requestData.put("description", "first exam"); + requestData.put("date", LocalDateTime.now().plusDays(1).toString()); + requestData.put("examGroups", "12K2, L04"); +// no examType + + String jsonRequest = mapper.writeValueAsString(requestData); + + MvcResult result = mockMvc.perform(MockMvcRequestBuilders + .post("/pkwmtt/api/v1/exams") + .contentType("application/json") + .content(jsonRequest) + ).andDo(print()) + .andExpect(status().isBadRequest()) + .andReturn(); + + JsonNode jsonResponse = mapper.readTree(result.getResponse().getContentAsString()); + assertTrue(jsonResponse.has("message")); + assertEquals("examType : must not be null", jsonResponse.get("message").asText()); + } + + @Test + void addExamWithNotFutureDate() throws Exception { + Map requestData = new HashMap<>(); + requestData.put("title", "Math exam"); + requestData.put("description", "first exam"); + requestData.put("date", LocalDateTime.now().minusDays(1).toString()); + requestData.put("examGroups", "12K2, L04"); + requestData.put("examType", "Project"); + + String jsonRequest = mapper.writeValueAsString(requestData); + + MvcResult result = mockMvc.perform(MockMvcRequestBuilders + .post("/pkwmtt/api/v1/exams") + .contentType("application/json") + .content(jsonRequest) + ).andDo(print()) + .andExpect(status().isBadRequest()) + .andReturn(); + + JsonNode jsonResponse = mapper.readTree(result.getResponse().getContentAsString()); + assertTrue(jsonResponse.has("message")); + assertEquals("date : Date must be in the future", jsonResponse.get("message").asText()); + } + + @Test + void addExamWithEmptyStringExamTitle() throws Exception { + Map requestData = new HashMap<>(); + requestData.put("title", ""); + requestData.put("description", "first exam"); + requestData.put("date", LocalDateTime.now().plusDays(1).toString()); + requestData.put("examGroups", "12K2, L04"); + requestData.put("examType", "Project"); + + String jsonRequest = mapper.writeValueAsString(requestData); + + MvcResult result = mockMvc.perform(MockMvcRequestBuilders + .post("/pkwmtt/api/v1/exams") + .contentType("application/json") + .content(jsonRequest) + ).andDo(print()) + .andExpect(status().isBadRequest()) + .andReturn(); + + JsonNode jsonResponse = mapper.readTree(result.getResponse().getContentAsString()); + assertTrue(jsonResponse.has("message")); + assertEquals("title : must not be blank", jsonResponse.get("message").asText()); + } + + @Test + void addExamWithEmptyStringExamGroups() throws Exception { + Map requestData = new HashMap<>(); + requestData.put("title", "Math exam"); + requestData.put("description", "first exam"); + requestData.put("date", LocalDateTime.now().plusDays(1).toString()); + requestData.put("examGroups", ""); + requestData.put("examType", "Project"); + + String jsonRequest = mapper.writeValueAsString(requestData); + + MvcResult result = mockMvc.perform(MockMvcRequestBuilders + .post("/pkwmtt/api/v1/exams") + .contentType("application/json") + .content(jsonRequest) + ).andDo(print()) + .andExpect(status().isBadRequest()) + .andReturn(); + + JsonNode jsonResponse = mapper.readTree(result.getResponse().getContentAsString()); + assertTrue(jsonResponse.has("message")); + assertEquals("examGroups : must not be blank", jsonResponse.get("message").asText()); + } + + @Test + void addExamWithTooLongExamTitle() throws Exception { + Map requestData = new HashMap<>(); + requestData.put("title", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + requestData.put("description", "first exam"); + requestData.put("date", LocalDateTime.now().plusDays(1).toString()); + requestData.put("examGroups", "12K2, L04"); + requestData.put("examType", "Project"); + + String jsonRequest = mapper.writeValueAsString(requestData); + + MvcResult result = mockMvc.perform(MockMvcRequestBuilders + .post("/pkwmtt/api/v1/exams") + .contentType("application/json") + .content(jsonRequest) + ).andDo(print()) + .andExpect(status().isBadRequest()) + .andReturn(); + + JsonNode jsonResponse = mapper.readTree(result.getResponse().getContentAsString()); + assertTrue(jsonResponse.has("message")); + assertEquals("title : max size of field is 255", jsonResponse.get("message").asText()); + } + + @Test + void addExamWithTooLongDescription() throws Exception { + Map requestData = new HashMap<>(); + requestData.put("title", "Math exam"); + requestData.put("description", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + requestData.put("date", LocalDateTime.now().plusDays(1).toString()); + requestData.put("examGroups", "12K2, L04"); + requestData.put("examType", "Project"); + + String jsonRequest = mapper.writeValueAsString(requestData); + + MvcResult result = mockMvc.perform(MockMvcRequestBuilders + .post("/pkwmtt/api/v1/exams") + .contentType("application/json") + .content(jsonRequest) + ).andDo(print()) + .andExpect(status().isBadRequest()) + .andReturn(); + + JsonNode jsonResponse = mapper.readTree(result.getResponse().getContentAsString()); + assertTrue(jsonResponse.has("message")); + assertEquals("description : max size of field is 255", jsonResponse.get("message").asText()); + } + + @Test + void addExamWithTooLongExamGroups() throws Exception { + Map requestData = new HashMap<>(); + requestData.put("title", "Math exam"); + requestData.put("description", "first exam"); + requestData.put("date", LocalDateTime.now().plusDays(1).toString()); + requestData.put("examGroups", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + requestData.put("examType", "Project"); + + String jsonRequest = mapper.writeValueAsString(requestData); + + MvcResult result = mockMvc.perform(MockMvcRequestBuilders + .post("/pkwmtt/api/v1/exams") + .contentType("application/json") + .content(jsonRequest) + ).andDo(print()) + .andExpect(status().isBadRequest()) + .andReturn(); + + JsonNode jsonResponse = mapper.readTree(result.getResponse().getContentAsString()); + assertTrue(jsonResponse.has("message")); + assertEquals("examGroups : max size of field is 255", jsonResponse.get("message").asText()); + } + + @Test + void addExamWithNonExistingExamType() throws Exception { + Map requestData = new HashMap<>(); + requestData.put("title", "Math exam"); + requestData.put("description", "first exam"); + requestData.put("date", LocalDateTime.now().plusDays(1).toString()); + requestData.put("examGroups", "12K2, L04"); + requestData.put("examType", "NonExistingExamType"); + + String jsonRequest = mapper.writeValueAsString(requestData); + + MvcResult result = mockMvc.perform(MockMvcRequestBuilders + .post("/pkwmtt/api/v1/exams") + .contentType("application/json") + .content(jsonRequest) + ).andDo(print()) + .andExpect(status().isBadRequest()) + .andReturn(); + + JsonNode jsonResponse = mapper.readTree(result.getResponse().getContentAsString()); + assertTrue(jsonResponse.has("message")); + assertEquals("Invalid exam type NonExistingExamType", jsonResponse.get("message").asText()); + } + + + + // + @Test void modifyExam() { } diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index 7f26e4f..bb5b179 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -1,6 +1,7 @@ -spring.datasource.url=jdbc:h2://mem:db;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false -spring.datasource.username=sa -spring.datasource.password=sa +#casue issue by some reason +#spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false +#spring.datasource.username=sa +#spring.datasource.password=sa spring.jpa.show-sql=true spring.jpa.database-platform=org.hibernate.dialect.H2Dialect From 1c7d59d4d7c68988da982a4f62fcd138f9393921 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Wed, 13 Aug 2025 15:17:30 +0200 Subject: [PATCH 024/116] modifyExam integration tests --- .../examCalendar/ExamControllerTest.java | 84 ++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java index cc22053..2631029 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java @@ -9,6 +9,7 @@ import org.pkwmtt.examCalendar.entity.ExamType; import org.pkwmtt.examCalendar.repository.ExamRepository; import org.pkwmtt.examCalendar.repository.ExamTypeRepository; +import org.pkwmtt.exceptions.NoSuchElementWithProvidedIdException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; @@ -392,9 +393,90 @@ void addExamWithNonExistingExamType() throws Exception { // +// @Test - void modifyExam() { + void modifyExamWithCorrectData() throws Exception { + ExamType examType = ExamType.builder().name("Exam").build(); + examTypeRepository.save(examType); + Exam exam = Exam.builder() + .title("Exam") + .description("Exam description") + .date(LocalDateTime.now().plusDays(1)) + .examGroups("11K1, L01") + .examType(examType) + .build(); + int id = examRepository.save(exam).getExamId(); + + LocalDateTime dateNow = LocalDateTime.now().plusDays(1); + + ExamDto examDtoRequest = new ExamDto( + "Math exam", + "first exam", + dateNow, + "12K2, L04", + "Project" + ); + + String json = mapper.writeValueAsString(examDtoRequest); + + mockMvc.perform(MockMvcRequestBuilders + .put("/pkwmtt/api/v1/exams/{id}", id) + .contentType("application/json") + .content(json) + ).andDo(print()) + .andExpect(status().isNoContent()); + + Exam responseExam = examRepository.findById(id).orElseThrow(); + assertEquals("Math exam", responseExam.getTitle()); + assertEquals("first exam", responseExam.getDescription()); + assertEquals( + dateNow.truncatedTo(ChronoUnit.MINUTES), + responseExam.getDate().truncatedTo(ChronoUnit.MINUTES) + ); + assertEquals("12K2, L04", responseExam.getExamGroups()); + } + + + @Test + void modifyExamWithIncorrectExamId() throws Exception { + ExamType examType = ExamType.builder().name("Exam").build(); + examTypeRepository.save(examType); + Exam exam = Exam.builder() + .title("Exam") + .description("Exam description") + .date(LocalDateTime.now().plusDays(1)) + .examGroups("11K1, L01") + .examType(examType) + .build(); + + int id = examRepository.save(exam).getExamId(); + + ExamDto examDtoRequest = new ExamDto( + "Math exam", + "first exam", + LocalDateTime.now().plusDays(1), + "12K2, L04", + "Project" + ); + + String json = mapper.writeValueAsString(examDtoRequest); + + MvcResult result = mockMvc.perform(MockMvcRequestBuilders + .put("/pkwmtt/api/v1/exams/{id}", Integer.MAX_VALUE - 10) + .contentType("application/json") + .content(json) + ).andDo(print()) + .andExpect(status().isNotFound()) + .andReturn(); + + JsonNode jsonResponse = mapper.readTree(result.getResponse().getContentAsString()); + assertTrue(jsonResponse.has("message")); + assertEquals("No such element with id: " + (Integer.MAX_VALUE - 10), jsonResponse.get("message").asText()); +// to make sure that Integer.MAX_VALUE - 10 is invalid id + assertNotEquals(Integer.MAX_VALUE - 10, id); + } +// @Test void deleteExam() { From dfc389dbaaf0183b000ce4f46e979ac9c744e0b1 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Wed, 13 Aug 2025 18:01:00 +0200 Subject: [PATCH 025/116] refactor modifyExam tests --- .../examCalendar/ExamControllerTest.java | 117 ++++++++++++------ 1 file changed, 79 insertions(+), 38 deletions(-) diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java index 2631029..742cc3f 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java @@ -9,12 +9,12 @@ import org.pkwmtt.examCalendar.entity.ExamType; import org.pkwmtt.examCalendar.repository.ExamRepository; import org.pkwmtt.examCalendar.repository.ExamTypeRepository; -import org.pkwmtt.exceptions.NoSuchElementWithProvidedIdException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.ResultMatcher; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import java.time.LocalDateTime; @@ -56,6 +56,7 @@ void setupBeforeEach() { // + /** * check if addExam endpoint create new exam with correct URI and correct data */ @@ -390,12 +391,53 @@ void addExamWithNonExistingExamType() throws Exception { } - // -// + // @Test void modifyExamWithCorrectData() throws Exception { +// given + ExamType examType = createExampleExamType("Exam"); + Exam exam = createExampleExam(examType); + int id = examRepository.save(exam).getExamId(); + ExamDto examDto = createExampleExamDto(examType.getName()); + +// when + assertPutRequest(status().isNoContent(), examDto, id); + +// then + Exam responseExam = examRepository.findById(id).orElseThrow(); + assertEquals("Math exam", responseExam.getTitle()); + assertEquals("first exam", responseExam.getDescription()); + assertEquals( + LocalDateTime.now().plusDays(1).truncatedTo(ChronoUnit.MINUTES), + responseExam.getDate().truncatedTo(ChronoUnit.MINUTES) + ); + assertEquals("12K2, L04", responseExam.getExamGroups()); + } + + @Test + void modifyExamWithIncorrectExamId() throws Exception { +// given + ExamType examType = createExampleExamType("Exam"); + Exam exam = createExampleExam(examType); + int id = examRepository.save(exam).getExamId(); + ExamDto examDto = createExampleExamDto(examType.getName()); + + int invalidId = Integer.MAX_VALUE - 10; + assertNotEquals(invalidId, id); +// when + MvcResult result = assertPutRequest(status().isNotFound(), examDto, invalidId); + +// then + assertResponseMessage("No such element with id: " + (invalidId), result); + + } +// + + // + @Test + void deleteExamWithCorrectArguments() throws Exception { ExamType examType = ExamType.builder().name("Exam").build(); examTypeRepository.save(examType); Exam exam = Exam.builder() @@ -436,61 +478,60 @@ void modifyExamWithCorrectData() throws Exception { assertEquals("12K2, L04", responseExam.getExamGroups()); } + // + @Test + void getExam() { + } + + @Test + void getExams() { + } @Test - void modifyExamWithIncorrectExamId() throws Exception { - ExamType examType = ExamType.builder().name("Exam").build(); - examTypeRepository.save(examType); - Exam exam = Exam.builder() + void getExamTypes() { + } + + + private Exam createExampleExam(ExamType type) { + return Exam.builder() .title("Exam") .description("Exam description") .date(LocalDateTime.now().plusDays(1)) .examGroups("11K1, L01") - .examType(examType) + .examType(type) .build(); + } - int id = examRepository.save(exam).getExamId(); + private ExamType createExampleExamType(String name) { + ExamType examType = ExamType.builder().name(name).build(); + examTypeRepository.save(examType); + return examType; + } - ExamDto examDtoRequest = new ExamDto( + private ExamDto createExampleExamDto(String examTypeName) { + return new ExamDto( "Math exam", "first exam", LocalDateTime.now().plusDays(1), "12K2, L04", "Project" ); + } - String json = mapper.writeValueAsString(examDtoRequest); - - MvcResult result = mockMvc.perform(MockMvcRequestBuilders - .put("/pkwmtt/api/v1/exams/{id}", Integer.MAX_VALUE - 10) - .contentType("application/json") - .content(json) - ).andDo(print()) - .andExpect(status().isNotFound()) - .andReturn(); - + private void assertResponseMessage(String expectedMessage, MvcResult result) throws Exception { JsonNode jsonResponse = mapper.readTree(result.getResponse().getContentAsString()); assertTrue(jsonResponse.has("message")); - assertEquals("No such element with id: " + (Integer.MAX_VALUE - 10), jsonResponse.get("message").asText()); -// to make sure that Integer.MAX_VALUE - 10 is invalid id - assertNotEquals(Integer.MAX_VALUE - 10, id); - - } -// - - @Test - void deleteExam() { - } - - @Test - void getExam() { + assertEquals(expectedMessage, jsonResponse.get("message").asText()); } - @Test - void getExams() { + private MvcResult assertPutRequest(ResultMatcher expectedStatus, Object content, int pathId) throws Exception { + return mockMvc.perform(MockMvcRequestBuilders + .put("/pkwmtt/api/v1/exams/{id}", pathId) + .contentType("application/json") + .content(mapper.writeValueAsString(content)) + ).andDo(print()) + .andExpect(expectedStatus) + .andReturn(); } - @Test - void getExamTypes() { - } } \ No newline at end of file From 50f9077593724f8b50d1b8c92b0fcd381f8b7580 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Wed, 13 Aug 2025 18:31:16 +0200 Subject: [PATCH 026/116] refactor addExam tests --- .../examCalendar/ExamControllerTest.java | 223 ++++++------------ 1 file changed, 72 insertions(+), 151 deletions(-) diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java index 742cc3f..fa18d22 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java @@ -55,21 +55,15 @@ void setupBeforeEach() { } -// + // /** * check if addExam endpoint create new exam with correct URI and correct data */ @Test void addExamWithCorrectData() throws Exception { - ExamDto examDtoRequest = new ExamDto( - "Math exam", - "first exam", - LocalDateTime.now().plusDays(1), - "12K2, L04", - "Project" - ); - +// given + ExamDto examDtoRequest = createExampleExamDto("Project"); String json = mapper.writeValueAsString(examDtoRequest); MvcResult result = mockMvc.perform(MockMvcRequestBuilders @@ -100,6 +94,7 @@ void addExamWithCorrectData() throws Exception { @Test void addExamWithBlankExamTitle() throws Exception { +// given Map requestData = new HashMap<>(); // no exam title requestData.put("description", "first exam"); @@ -107,23 +102,16 @@ void addExamWithBlankExamTitle() throws Exception { requestData.put("examGroups", "12K2, L04"); requestData.put("examType", "Project"); - String jsonRequest = mapper.writeValueAsString(requestData); - - MvcResult result = mockMvc.perform(MockMvcRequestBuilders - .post("/pkwmtt/api/v1/exams") - .contentType("application/json") - .content(jsonRequest) - ).andDo(print()) - .andExpect(status().isBadRequest()) - .andReturn(); +// when + MvcResult result = assertPostRequest(status().isBadRequest(), requestData); - JsonNode jsonResponse = mapper.readTree(result.getResponse().getContentAsString()); - assertTrue(jsonResponse.has("message")); - assertEquals("title : must not be blank", jsonResponse.get("message").asText()); +// then + assertResponseMessage("title : must not be blank", result); } @Test void addExamWithBlankExamDescription() throws Exception { +// given Map requestData = new HashMap<>(); requestData.put("title", "Math exam"); // no exam description @@ -131,27 +119,20 @@ void addExamWithBlankExamDescription() throws Exception { requestData.put("examGroups", "12K2, L04"); requestData.put("examType", "Project"); - String jsonRequest = mapper.writeValueAsString(requestData); - - MvcResult result = mockMvc.perform(MockMvcRequestBuilders - .post("/pkwmtt/api/v1/exams") - .contentType("application/json") - .content(jsonRequest) - ).andDo(print()) - .andExpect(status().isCreated()) - .andReturn(); +// when + MvcResult result = assertPostRequest(status().isCreated(), requestData); String location = result.getResponse().getHeader("Location"); @SuppressWarnings("DataFlowIssue") int id = Integer.parseInt(location.substring(location.lastIndexOf("/") + 1)); Exam examResponse = examRepository.findById(id).orElseThrow(); - assertNull(examResponse.getDescription()); } @Test void addExamWithBlankDate() throws Exception { +// given Map requestData = new HashMap<>(); requestData.put("title", "Math exam"); requestData.put("description", "first exam"); @@ -159,23 +140,16 @@ void addExamWithBlankDate() throws Exception { requestData.put("examGroups", "12K2, L04"); requestData.put("examType", "Project"); - String jsonRequest = mapper.writeValueAsString(requestData); - - MvcResult result = mockMvc.perform(MockMvcRequestBuilders - .post("/pkwmtt/api/v1/exams") - .contentType("application/json") - .content(jsonRequest) - ).andDo(print()) - .andExpect(status().isBadRequest()) - .andReturn(); +// when + MvcResult result = assertPostRequest(status().isBadRequest(), requestData); - JsonNode jsonResponse = mapper.readTree(result.getResponse().getContentAsString()); - assertTrue(jsonResponse.has("message")); - assertEquals("date : must not be null", jsonResponse.get("message").asText()); +// then + assertResponseMessage("date : must not be null", result); } @Test void addExamWithBlankExamGroups() throws Exception { +// given Map requestData = new HashMap<>(); requestData.put("title", "Math exam"); requestData.put("description", "first exam"); @@ -183,23 +157,16 @@ void addExamWithBlankExamGroups() throws Exception { // no examGroups requestData.put("examType", "Project"); - String jsonRequest = mapper.writeValueAsString(requestData); - - MvcResult result = mockMvc.perform(MockMvcRequestBuilders - .post("/pkwmtt/api/v1/exams") - .contentType("application/json") - .content(jsonRequest) - ).andDo(print()) - .andExpect(status().isBadRequest()) - .andReturn(); +// when + MvcResult result = assertPostRequest(status().isBadRequest(), requestData); - JsonNode jsonResponse = mapper.readTree(result.getResponse().getContentAsString()); - assertTrue(jsonResponse.has("message")); - assertEquals("examGroups : must not be blank", jsonResponse.get("message").asText()); +// then + assertResponseMessage("examGroups : must not be blank", result); } @Test void addExamWithNullExamTypes() throws Exception { +// given Map requestData = new HashMap<>(); requestData.put("title", "Math exam"); requestData.put("description", "first exam"); @@ -207,23 +174,16 @@ void addExamWithNullExamTypes() throws Exception { requestData.put("examGroups", "12K2, L04"); // no examType - String jsonRequest = mapper.writeValueAsString(requestData); - - MvcResult result = mockMvc.perform(MockMvcRequestBuilders - .post("/pkwmtt/api/v1/exams") - .contentType("application/json") - .content(jsonRequest) - ).andDo(print()) - .andExpect(status().isBadRequest()) - .andReturn(); +// when + MvcResult result = assertPostRequest(status().isBadRequest(), requestData); - JsonNode jsonResponse = mapper.readTree(result.getResponse().getContentAsString()); - assertTrue(jsonResponse.has("message")); - assertEquals("examType : must not be null", jsonResponse.get("message").asText()); +// then + assertResponseMessage("examType : must not be null", result); } @Test void addExamWithNotFutureDate() throws Exception { +// given Map requestData = new HashMap<>(); requestData.put("title", "Math exam"); requestData.put("description", "first exam"); @@ -231,23 +191,16 @@ void addExamWithNotFutureDate() throws Exception { requestData.put("examGroups", "12K2, L04"); requestData.put("examType", "Project"); - String jsonRequest = mapper.writeValueAsString(requestData); - - MvcResult result = mockMvc.perform(MockMvcRequestBuilders - .post("/pkwmtt/api/v1/exams") - .contentType("application/json") - .content(jsonRequest) - ).andDo(print()) - .andExpect(status().isBadRequest()) - .andReturn(); +// when + MvcResult result = assertPostRequest(status().isBadRequest(), requestData); - JsonNode jsonResponse = mapper.readTree(result.getResponse().getContentAsString()); - assertTrue(jsonResponse.has("message")); - assertEquals("date : Date must be in the future", jsonResponse.get("message").asText()); +// then + assertResponseMessage("date : Date must be in the future", result); } @Test void addExamWithEmptyStringExamTitle() throws Exception { +// given Map requestData = new HashMap<>(); requestData.put("title", ""); requestData.put("description", "first exam"); @@ -255,23 +208,16 @@ void addExamWithEmptyStringExamTitle() throws Exception { requestData.put("examGroups", "12K2, L04"); requestData.put("examType", "Project"); - String jsonRequest = mapper.writeValueAsString(requestData); - - MvcResult result = mockMvc.perform(MockMvcRequestBuilders - .post("/pkwmtt/api/v1/exams") - .contentType("application/json") - .content(jsonRequest) - ).andDo(print()) - .andExpect(status().isBadRequest()) - .andReturn(); +// when + MvcResult result = assertPostRequest(status().isBadRequest(), requestData); - JsonNode jsonResponse = mapper.readTree(result.getResponse().getContentAsString()); - assertTrue(jsonResponse.has("message")); - assertEquals("title : must not be blank", jsonResponse.get("message").asText()); +// then + assertResponseMessage("title : must not be blank", result); } @Test void addExamWithEmptyStringExamGroups() throws Exception { +// given Map requestData = new HashMap<>(); requestData.put("title", "Math exam"); requestData.put("description", "first exam"); @@ -279,23 +225,16 @@ void addExamWithEmptyStringExamGroups() throws Exception { requestData.put("examGroups", ""); requestData.put("examType", "Project"); - String jsonRequest = mapper.writeValueAsString(requestData); - - MvcResult result = mockMvc.perform(MockMvcRequestBuilders - .post("/pkwmtt/api/v1/exams") - .contentType("application/json") - .content(jsonRequest) - ).andDo(print()) - .andExpect(status().isBadRequest()) - .andReturn(); +// when + MvcResult result = assertPostRequest(status().isBadRequest(), requestData); - JsonNode jsonResponse = mapper.readTree(result.getResponse().getContentAsString()); - assertTrue(jsonResponse.has("message")); - assertEquals("examGroups : must not be blank", jsonResponse.get("message").asText()); +// then + assertResponseMessage("examGroups : must not be blank", result); } @Test void addExamWithTooLongExamTitle() throws Exception { +// given Map requestData = new HashMap<>(); requestData.put("title", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); requestData.put("description", "first exam"); @@ -303,23 +242,16 @@ void addExamWithTooLongExamTitle() throws Exception { requestData.put("examGroups", "12K2, L04"); requestData.put("examType", "Project"); - String jsonRequest = mapper.writeValueAsString(requestData); - - MvcResult result = mockMvc.perform(MockMvcRequestBuilders - .post("/pkwmtt/api/v1/exams") - .contentType("application/json") - .content(jsonRequest) - ).andDo(print()) - .andExpect(status().isBadRequest()) - .andReturn(); +// when + MvcResult result = assertPostRequest(status().isBadRequest(), requestData); - JsonNode jsonResponse = mapper.readTree(result.getResponse().getContentAsString()); - assertTrue(jsonResponse.has("message")); - assertEquals("title : max size of field is 255", jsonResponse.get("message").asText()); +// then + assertResponseMessage("title : max size of field is 255", result); } @Test void addExamWithTooLongDescription() throws Exception { +// given Map requestData = new HashMap<>(); requestData.put("title", "Math exam"); requestData.put("description", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); @@ -327,23 +259,16 @@ void addExamWithTooLongDescription() throws Exception { requestData.put("examGroups", "12K2, L04"); requestData.put("examType", "Project"); - String jsonRequest = mapper.writeValueAsString(requestData); - - MvcResult result = mockMvc.perform(MockMvcRequestBuilders - .post("/pkwmtt/api/v1/exams") - .contentType("application/json") - .content(jsonRequest) - ).andDo(print()) - .andExpect(status().isBadRequest()) - .andReturn(); +// when + MvcResult result = assertPostRequest(status().isBadRequest(), requestData); - JsonNode jsonResponse = mapper.readTree(result.getResponse().getContentAsString()); - assertTrue(jsonResponse.has("message")); - assertEquals("description : max size of field is 255", jsonResponse.get("message").asText()); +// then + assertResponseMessage("description : max size of field is 255", result); } @Test void addExamWithTooLongExamGroups() throws Exception { +// given Map requestData = new HashMap<>(); requestData.put("title", "Math exam"); requestData.put("description", "first exam"); @@ -351,23 +276,16 @@ void addExamWithTooLongExamGroups() throws Exception { requestData.put("examGroups", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); requestData.put("examType", "Project"); - String jsonRequest = mapper.writeValueAsString(requestData); - - MvcResult result = mockMvc.perform(MockMvcRequestBuilders - .post("/pkwmtt/api/v1/exams") - .contentType("application/json") - .content(jsonRequest) - ).andDo(print()) - .andExpect(status().isBadRequest()) - .andReturn(); +// when + MvcResult result = assertPostRequest(status().isBadRequest(), requestData); - JsonNode jsonResponse = mapper.readTree(result.getResponse().getContentAsString()); - assertTrue(jsonResponse.has("message")); - assertEquals("examGroups : max size of field is 255", jsonResponse.get("message").asText()); +// then + assertResponseMessage("examGroups : max size of field is 255", result); } @Test void addExamWithNonExistingExamType() throws Exception { +// given Map requestData = new HashMap<>(); requestData.put("title", "Math exam"); requestData.put("description", "first exam"); @@ -375,19 +293,11 @@ void addExamWithNonExistingExamType() throws Exception { requestData.put("examGroups", "12K2, L04"); requestData.put("examType", "NonExistingExamType"); - String jsonRequest = mapper.writeValueAsString(requestData); - - MvcResult result = mockMvc.perform(MockMvcRequestBuilders - .post("/pkwmtt/api/v1/exams") - .contentType("application/json") - .content(jsonRequest) - ).andDo(print()) - .andExpect(status().isBadRequest()) - .andReturn(); +// when + MvcResult result = assertPostRequest(status().isBadRequest(), requestData); - JsonNode jsonResponse = mapper.readTree(result.getResponse().getContentAsString()); - assertTrue(jsonResponse.has("message")); - assertEquals("Invalid exam type NonExistingExamType", jsonResponse.get("message").asText()); +// then + assertResponseMessage("Invalid exam type NonExistingExamType", result); } @@ -524,6 +434,16 @@ private void assertResponseMessage(String expectedMessage, MvcResult result) thr assertEquals(expectedMessage, jsonResponse.get("message").asText()); } + private MvcResult assertPostRequest(ResultMatcher expectedStatus, Object content) throws Exception { + return mockMvc.perform(MockMvcRequestBuilders + .post("/pkwmtt/api/v1/exams") + .contentType("application/json") + .content(mapper.writeValueAsString(content)) + ).andDo(print()) + .andExpect(expectedStatus) + .andReturn(); + } + private MvcResult assertPutRequest(ResultMatcher expectedStatus, Object content, int pathId) throws Exception { return mockMvc.perform(MockMvcRequestBuilders .put("/pkwmtt/api/v1/exams/{id}", pathId) @@ -534,4 +454,5 @@ private MvcResult assertPutRequest(ResultMatcher expectedStatus, Object content, .andReturn(); } + } \ No newline at end of file From bdc71806a326977ec95b2b11e21bbe5f98573085 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Wed, 13 Aug 2025 18:49:04 +0200 Subject: [PATCH 027/116] integration tests of deleteExam --- .../examCalendar/ExamControllerTest.java | 63 +++++++++---------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java index fa18d22..a8827eb 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java @@ -348,44 +348,34 @@ void modifyExamWithIncorrectExamId() throws Exception { // @Test void deleteExamWithCorrectArguments() throws Exception { - ExamType examType = ExamType.builder().name("Exam").build(); - examTypeRepository.save(examType); - Exam exam = Exam.builder() - .title("Exam") - .description("Exam description") - .date(LocalDateTime.now().plusDays(1)) - .examGroups("11K1, L01") - .examType(examType) - .build(); +// given + ExamType examType = createExampleExamType("Exam"); + Exam exam = createExampleExam(examType); int id = examRepository.save(exam).getExamId(); - LocalDateTime dateNow = LocalDateTime.now().plusDays(1); +// when + assertDeleteRequest(status().isNoContent(), id); - ExamDto examDtoRequest = new ExamDto( - "Math exam", - "first exam", - dateNow, - "12K2, L04", - "Project" - ); +// then + assertTrue(examRepository.findById(id).isEmpty()); + } - String json = mapper.writeValueAsString(examDtoRequest); + @Test + void deleteNonExistingExam() throws Exception { +// given + ExamType examType = createExampleExamType("Exam"); + Exam exam = createExampleExam(examType); + int id = examRepository.save(exam).getExamId(); + int invalidId = Integer.MAX_VALUE - 10; + assertNotEquals(invalidId, id); - mockMvc.perform(MockMvcRequestBuilders - .put("/pkwmtt/api/v1/exams/{id}", id) - .contentType("application/json") - .content(json) - ).andDo(print()) - .andExpect(status().isNoContent()); +// when + MvcResult result = assertDeleteRequest(status().isNotFound(), invalidId); + +// then + assertTrue(examRepository.findById(id).isPresent()); + assertResponseMessage("No such element with id: " + (invalidId), result); - Exam responseExam = examRepository.findById(id).orElseThrow(); - assertEquals("Math exam", responseExam.getTitle()); - assertEquals("first exam", responseExam.getDescription()); - assertEquals( - dateNow.truncatedTo(ChronoUnit.MINUTES), - responseExam.getDate().truncatedTo(ChronoUnit.MINUTES) - ); - assertEquals("12K2, L04", responseExam.getExamGroups()); } // @@ -454,5 +444,14 @@ private MvcResult assertPutRequest(ResultMatcher expectedStatus, Object content, .andReturn(); } + private MvcResult assertDeleteRequest(ResultMatcher expectedStatus, int pathId) throws Exception { + return mockMvc.perform(MockMvcRequestBuilders + .delete("/pkwmtt/api/v1/exams/{id}", pathId) + .contentType("application/json") + ).andDo(print()) + .andExpect(expectedStatus) + .andReturn(); + } + } \ No newline at end of file From 69333ab2b4444a0ae92becfed8068f55857de561 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Wed, 13 Aug 2025 19:31:01 +0200 Subject: [PATCH 028/116] integration tests of getExamById --- .../examCalendar/ExamControllerTest.java | 52 ++++++++++++++++++- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java index a8827eb..f1541e7 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java @@ -375,16 +375,55 @@ void deleteNonExistingExam() throws Exception { // then assertTrue(examRepository.findById(id).isPresent()); assertResponseMessage("No such element with id: " + (invalidId), result); - } // + +// + @Test - void getExam() { + void getExamByIdWithCorrectId() throws Exception { +// given + ExamType examType = createExampleExamType("Exam"); + Exam exam = createExampleExam(examType); + int id = examRepository.save(exam).getExamId(); + +// when + MvcResult result = assertGetByIdRequest(status().isOk(), id); + JsonNode responseNode = mapper.readTree(result.getResponse().getContentAsString()); + +// then + assertEquals(exam.getTitle(), responseNode.get("title").asText()); + assertEquals(exam.getDescription(), responseNode.get("description").asText()); + assertEquals( + exam.getDate().truncatedTo(ChronoUnit.MINUTES), + LocalDateTime.parse(responseNode.get("date").textValue()).truncatedTo(ChronoUnit.MINUTES) + ); + assertEquals(exam.getExamGroups(), responseNode.get("examGroups").asText()); + assertEquals(mapper.readTree(mapper.writeValueAsString(exam.getExamType())), responseNode.get("examType")); } + @Test + void getNonExistingExamById() throws Exception { +// given + ExamType examType = createExampleExamType("Exam"); + Exam exam = createExampleExam(examType); + int id = examRepository.save(exam).getExamId(); + int invalidId = Integer.MAX_VALUE - 10; + assertNotEquals(invalidId, id); + +// when + MvcResult result = assertGetByIdRequest(status().isNotFound(), invalidId); + +// then + assertResponseMessage("No such element with id: " + (invalidId), result); + } + +// + @Test void getExams() { +// TODO: test getExamsByGroups after implementing new version } @Test @@ -453,5 +492,14 @@ private MvcResult assertDeleteRequest(ResultMatcher expectedStatus, int pathId) .andReturn(); } + private MvcResult assertGetByIdRequest(ResultMatcher expectedStatus, int pathId) throws Exception { + return mockMvc.perform(MockMvcRequestBuilders + .get("/pkwmtt/api/v1/exams/{id}", pathId) + .contentType("application/json") + ).andDo(print()) + .andExpect(expectedStatus) + .andReturn(); + } + } \ No newline at end of file From 339a85634701bfc485def1fe4780c01f0f57e65b Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Wed, 13 Aug 2025 20:10:39 +0200 Subject: [PATCH 029/116] integration tests of getExamTypes --- .../examCalendar/ExamControllerTest.java | 64 +++++++++++++++++-- 1 file changed, 59 insertions(+), 5 deletions(-) diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java index f1541e7..0e7eb4a 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java @@ -51,10 +51,8 @@ class ExamControllerTest { void setupBeforeEach() { examRepository.deleteAll(); examTypeRepository.deleteAll(); - examTypeRepository.save(ExamType.builder().name("Project").build()); } - // /** @@ -63,6 +61,7 @@ void setupBeforeEach() { @Test void addExamWithCorrectData() throws Exception { // given + createExampleExamType("Project"); ExamDto examDtoRequest = createExampleExamDto("Project"); String json = mapper.writeValueAsString(examDtoRequest); @@ -95,6 +94,7 @@ void addExamWithCorrectData() throws Exception { @Test void addExamWithBlankExamTitle() throws Exception { // given + createExampleExamType("Project"); Map requestData = new HashMap<>(); // no exam title requestData.put("description", "first exam"); @@ -112,6 +112,7 @@ void addExamWithBlankExamTitle() throws Exception { @Test void addExamWithBlankExamDescription() throws Exception { // given + createExampleExamType("Project"); Map requestData = new HashMap<>(); requestData.put("title", "Math exam"); // no exam description @@ -133,6 +134,7 @@ void addExamWithBlankExamDescription() throws Exception { @Test void addExamWithBlankDate() throws Exception { // given + createExampleExamType("Project"); Map requestData = new HashMap<>(); requestData.put("title", "Math exam"); requestData.put("description", "first exam"); @@ -150,6 +152,7 @@ void addExamWithBlankDate() throws Exception { @Test void addExamWithBlankExamGroups() throws Exception { // given + createExampleExamType("Project"); Map requestData = new HashMap<>(); requestData.put("title", "Math exam"); requestData.put("description", "first exam"); @@ -167,6 +170,7 @@ void addExamWithBlankExamGroups() throws Exception { @Test void addExamWithNullExamTypes() throws Exception { // given + createExampleExamType("Project"); Map requestData = new HashMap<>(); requestData.put("title", "Math exam"); requestData.put("description", "first exam"); @@ -184,6 +188,7 @@ void addExamWithNullExamTypes() throws Exception { @Test void addExamWithNotFutureDate() throws Exception { // given + createExampleExamType("Project"); Map requestData = new HashMap<>(); requestData.put("title", "Math exam"); requestData.put("description", "first exam"); @@ -201,6 +206,7 @@ void addExamWithNotFutureDate() throws Exception { @Test void addExamWithEmptyStringExamTitle() throws Exception { // given + createExampleExamType("Project"); Map requestData = new HashMap<>(); requestData.put("title", ""); requestData.put("description", "first exam"); @@ -218,6 +224,7 @@ void addExamWithEmptyStringExamTitle() throws Exception { @Test void addExamWithEmptyStringExamGroups() throws Exception { // given + createExampleExamType("Project"); Map requestData = new HashMap<>(); requestData.put("title", "Math exam"); requestData.put("description", "first exam"); @@ -235,6 +242,7 @@ void addExamWithEmptyStringExamGroups() throws Exception { @Test void addExamWithTooLongExamTitle() throws Exception { // given + createExampleExamType("Project"); Map requestData = new HashMap<>(); requestData.put("title", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); requestData.put("description", "first exam"); @@ -252,6 +260,7 @@ void addExamWithTooLongExamTitle() throws Exception { @Test void addExamWithTooLongDescription() throws Exception { // given + createExampleExamType("Project"); Map requestData = new HashMap<>(); requestData.put("title", "Math exam"); requestData.put("description", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); @@ -269,6 +278,7 @@ void addExamWithTooLongDescription() throws Exception { @Test void addExamWithTooLongExamGroups() throws Exception { // given + createExampleExamType("Project"); Map requestData = new HashMap<>(); requestData.put("title", "Math exam"); requestData.put("description", "first exam"); @@ -286,6 +296,7 @@ void addExamWithTooLongExamGroups() throws Exception { @Test void addExamWithNonExistingExamType() throws Exception { // given + createExampleExamType("Project"); Map requestData = new HashMap<>(); requestData.put("title", "Math exam"); requestData.put("description", "first exam"); @@ -379,7 +390,7 @@ void deleteNonExistingExam() throws Exception { // -// + // @Test void getExamByIdWithCorrectId() throws Exception { @@ -426,10 +437,43 @@ void getExams() { // TODO: test getExamsByGroups after implementing new version } + // + + @Test + void getExamTypesWhenExamTypesExists() throws Exception { +// given + ExamType exam = createExampleExamType("Exam"); + ExamType project = createExampleExamType("Project"); + +// when + MvcResult result = assertGetExamTypesRequest(status().isOk()); + JsonNode responseArray = mapper.readTree(result.getResponse().getContentAsString()); + +// then + assertEquals(2, responseArray.size()); + assertTrue(responseArray.valueStream().anyMatch(e -> e.get("name").asText().equals(exam.getName()))); + assertTrue(responseArray.valueStream().anyMatch(e -> e.get("name").asText().equals(project.getName()))); + } + @Test - void getExamTypes() { + void getExamTypesWhenExamTypesNotExists() throws Exception { +// given +// when + MvcResult result = mockMvc.perform(MockMvcRequestBuilders + .get("/pkwmtt/api/v1/exams/exam-types") + .contentType("application/json") + ).andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + JsonNode responseArray = mapper.readTree(result.getResponse().getContentAsString()); + +// then + assertEquals(0, responseArray.size()); } + // + + // private Exam createExampleExam(ExamType type) { return Exam.builder() @@ -453,7 +497,7 @@ private ExamDto createExampleExamDto(String examTypeName) { "first exam", LocalDateTime.now().plusDays(1), "12K2, L04", - "Project" + examTypeName ); } @@ -501,5 +545,15 @@ private MvcResult assertGetByIdRequest(ResultMatcher expectedStatus, int pathId) .andReturn(); } + private MvcResult assertGetExamTypesRequest(ResultMatcher expectedStatus) throws Exception { + return mockMvc.perform(MockMvcRequestBuilders + .get("/pkwmtt/api/v1/exams/exam-types") + .contentType("application/json") + ).andDo(print()) + .andExpect(expectedStatus) + .andReturn(); + } + +// } \ No newline at end of file From 27120fdc7ab1c11924d5d3bb443a3d7d8c7cfb83 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Wed, 13 Aug 2025 20:39:58 +0200 Subject: [PATCH 030/116] javadoc for helper methods --- .../examCalendar/ExamControllerTest.java | 73 +++++++++++++++++-- 1 file changed, 67 insertions(+), 6 deletions(-) diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java index 0e7eb4a..5f7f6ae 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java @@ -475,6 +475,22 @@ void getExamTypesWhenExamTypesNotExists() throws Exception { // + /** + * this method create examType object and add it to repository + * @param name of new examType + * @return created examType object + */ + private ExamType createExampleExamType(String name) { + ExamType examType = ExamType.builder().name(name).build(); + examTypeRepository.save(examType); + return examType; + } + + /** + * this method don't add created Exam to repository, because in that case id of created Exam would be unreachable + * @param type ExamType object which is required argument of Exam + * @return created Exam + */ private Exam createExampleExam(ExamType type) { return Exam.builder() .title("Exam") @@ -485,12 +501,10 @@ private Exam createExampleExam(ExamType type) { .build(); } - private ExamType createExampleExamType(String name) { - ExamType examType = ExamType.builder().name(name).build(); - examTypeRepository.save(examType); - return examType; - } - + /** + * @param examTypeName name of type of exam as String + * @return created ExamDto + */ private ExamDto createExampleExamDto(String examTypeName) { return new ExamDto( "Math exam", @@ -501,12 +515,27 @@ private ExamDto createExampleExamDto(String examTypeName) { ); } + /** + * compare error message form response with expected value + * @param expectedMessage full message that is expected in response + * @param result response generated by mockMvc.perform() or one of assert[httpMethod]Request() + * @throws Exception + */ private void assertResponseMessage(String expectedMessage, MvcResult result) throws Exception { JsonNode jsonResponse = mapper.readTree(result.getResponse().getContentAsString()); assertTrue(jsonResponse.has("message")); assertEquals(expectedMessage, jsonResponse.get("message").asText()); } + /** + * method send POST request to ExamController with content as JSON attached to body and then check if response + * code is the same as expected + * @param expectedStatus status().[http response] (example: status().isCreated() ) + * @param content object that would be mapped to JSON by ObjectMapper and then attached to request + * it could be dto object or Map + * @return MvcResult object which could be used to capture response body + * @throws Exception + */ private MvcResult assertPostRequest(ResultMatcher expectedStatus, Object content) throws Exception { return mockMvc.perform(MockMvcRequestBuilders .post("/pkwmtt/api/v1/exams") @@ -517,6 +546,15 @@ private MvcResult assertPostRequest(ResultMatcher expectedStatus, Object content .andReturn(); } + /** + * method send PUT request to ExamController with content as JSON attached to body and examId as pathID. + * Then check if response code is the same as expected + * @param expectedStatus status().[http response] (example: status().isNoContent() ) + * @param content object that would be mapped to JSON by ObjectMapper and then attached to request + * @param pathId id of resource that would be updated + * @return MvcResult object which could be used to capture response body + * @throws Exception + */ private MvcResult assertPutRequest(ResultMatcher expectedStatus, Object content, int pathId) throws Exception { return mockMvc.perform(MockMvcRequestBuilders .put("/pkwmtt/api/v1/exams/{id}", pathId) @@ -527,6 +565,14 @@ private MvcResult assertPutRequest(ResultMatcher expectedStatus, Object content, .andReturn(); } + /** + * method send DELETE request to ExamController with examId as pathID. + * Then check if response code is the same as expected + * @param expectedStatus status().[http response] (example: status().isNoContent() ) + * @param pathId id of resource that would be deleted + * @return MvcResult object which could be used to capture response body + * @throws Exception + */ private MvcResult assertDeleteRequest(ResultMatcher expectedStatus, int pathId) throws Exception { return mockMvc.perform(MockMvcRequestBuilders .delete("/pkwmtt/api/v1/exams/{id}", pathId) @@ -536,6 +582,14 @@ private MvcResult assertDeleteRequest(ResultMatcher expectedStatus, int pathId) .andReturn(); } + /** + * method send GET request to ExamController at /pkwmtt/api/v1/exams/{id} URI with examId as pathID. + * Then check if response code is the same as expected + * @param expectedStatus status().[http response] (example: status().isOk() ) + * @param pathId id of resource that would be returned + * @return MvcResult object which could be used to capture response body + * @throws Exception + */ private MvcResult assertGetByIdRequest(ResultMatcher expectedStatus, int pathId) throws Exception { return mockMvc.perform(MockMvcRequestBuilders .get("/pkwmtt/api/v1/exams/{id}", pathId) @@ -545,6 +599,13 @@ private MvcResult assertGetByIdRequest(ResultMatcher expectedStatus, int pathId) .andReturn(); } + /** + * method send GET request to ExamController at /pkwmtt/api/v1/exams/exam-types URI. + * Then check if response code is the same as expected + * @param expectedStatus expectedStatus status().[http response] (example: status().isOk() ) + * @return MvcResult object which could be used to capture response body + * @throws Exception + */ private MvcResult assertGetExamTypesRequest(ResultMatcher expectedStatus) throws Exception { return mockMvc.perform(MockMvcRequestBuilders .get("/pkwmtt/api/v1/exams/exam-types") From 2eb3b96efbde8806ef06a9a81ec7b9a5b31f5e9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Florczak?= <84631301+florczaq@users.noreply.github.com> Date: Mon, 18 Aug 2025 21:59:29 +0200 Subject: [PATCH 031/116] Resolve conficlts --- pom.xml | 365 +++++------ src/main/java/org/pkwmtt/entity/Exam.java | 38 -- src/main/java/org/pkwmtt/entity/ExamType.java | 18 - .../java/org/pkwmtt/entity/GeneralGroup.java | 17 - src/main/java/org/pkwmtt/entity/OTPCode.java | 25 - .../java/org/pkwmtt/entity/StudentGroup.java | 23 - src/main/java/org/pkwmtt/entity/User.java | 29 - .../pkwmtt/examCalendar/ExamController.java | 90 +++ .../examCalendar/ExamControllerAdvice.java | 48 ++ .../org/pkwmtt/examCalendar/ExamService.java | 86 +++ .../org/pkwmtt/examCalendar/dto/ExamDto.java | 33 + .../org/pkwmtt/examCalendar/entity/Exam.java | 48 ++ .../pkwmtt/examCalendar/entity/ExamType.java | 21 + .../examCalendar/entity/GeneralGroup.java | 26 + .../org/pkwmtt/examCalendar/entity/Group.java | 27 + .../pkwmtt/examCalendar/entity/OTPCode.java | 31 + .../org/pkwmtt/examCalendar/entity/User.java | 33 + .../mapper/ExamDtoToExamMapper.java | 49 ++ .../repository/ExamRepository.java | 74 +++ .../repository/ExamTypeRepository.java | 10 + .../repository/GeneralGroupRepository.java | 4 +- .../repository/GroupRepository.java | 7 + .../repository/OTPCodeRepository.java | 4 +- .../repository/UserRepository.java | 4 +- .../ExamTypeNotExistsException.java | 7 + .../InvalidGroupIdentifierException.java | 7 + .../NoSuchElementWithProvidedIdException.java | 7 + .../UnsupportedCountOfArgumentsException.java | 8 + .../org/pkwmtt/repository/ExamRepository.java | 7 - .../pkwmtt/repository/ExamTypeRepository.java | 7 - .../pkwmtt/repository/GroupRepository.java | 7 - .../examCalendar/ExamControllerTest.java | 620 ++++++++++++++++++ .../pkwmtt/examCalendar/ExamServiceTest.java | 331 ++++++++++ .../pkwmtt/examCalendar/dto/ExamDtoTest.java | 150 +++++ .../mapper/ExamDtoToExamMapperTest.java | 150 +++++ .../repository/ExamRepositoryTest.java | 182 +++++ src/test/resources/application.properties | 13 + src/test/resources/schema.sql | 60 ++ 38 files changed, 2299 insertions(+), 367 deletions(-) delete mode 100644 src/main/java/org/pkwmtt/entity/Exam.java delete mode 100644 src/main/java/org/pkwmtt/entity/ExamType.java delete mode 100644 src/main/java/org/pkwmtt/entity/GeneralGroup.java delete mode 100644 src/main/java/org/pkwmtt/entity/OTPCode.java delete mode 100644 src/main/java/org/pkwmtt/entity/StudentGroup.java delete mode 100644 src/main/java/org/pkwmtt/entity/User.java create mode 100644 src/main/java/org/pkwmtt/examCalendar/ExamController.java create mode 100644 src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java create mode 100644 src/main/java/org/pkwmtt/examCalendar/ExamService.java create mode 100644 src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java create mode 100644 src/main/java/org/pkwmtt/examCalendar/entity/Exam.java create mode 100644 src/main/java/org/pkwmtt/examCalendar/entity/ExamType.java create mode 100644 src/main/java/org/pkwmtt/examCalendar/entity/GeneralGroup.java create mode 100644 src/main/java/org/pkwmtt/examCalendar/entity/Group.java create mode 100644 src/main/java/org/pkwmtt/examCalendar/entity/OTPCode.java create mode 100644 src/main/java/org/pkwmtt/examCalendar/entity/User.java create mode 100644 src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java create mode 100644 src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java create mode 100644 src/main/java/org/pkwmtt/examCalendar/repository/ExamTypeRepository.java rename src/main/java/org/pkwmtt/{ => examCalendar}/repository/GeneralGroupRepository.java (61%) create mode 100644 src/main/java/org/pkwmtt/examCalendar/repository/GroupRepository.java rename src/main/java/org/pkwmtt/{ => examCalendar}/repository/OTPCodeRepository.java (60%) rename src/main/java/org/pkwmtt/{ => examCalendar}/repository/UserRepository.java (60%) create mode 100644 src/main/java/org/pkwmtt/exceptions/ExamTypeNotExistsException.java create mode 100644 src/main/java/org/pkwmtt/exceptions/InvalidGroupIdentifierException.java create mode 100644 src/main/java/org/pkwmtt/exceptions/NoSuchElementWithProvidedIdException.java create mode 100644 src/main/java/org/pkwmtt/exceptions/UnsupportedCountOfArgumentsException.java delete mode 100644 src/main/java/org/pkwmtt/repository/ExamRepository.java delete mode 100644 src/main/java/org/pkwmtt/repository/ExamTypeRepository.java delete mode 100644 src/main/java/org/pkwmtt/repository/GroupRepository.java create mode 100644 src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java create mode 100644 src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java create mode 100644 src/test/java/org/pkwmtt/examCalendar/dto/ExamDtoTest.java create mode 100644 src/test/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapperTest.java create mode 100644 src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java create mode 100644 src/test/resources/application.properties create mode 100644 src/test/resources/schema.sql diff --git a/pom.xml b/pom.xml index 1ab2d6a..1e54dfb 100644 --- a/pom.xml +++ b/pom.xml @@ -1,203 +1,188 @@ - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 3.5.3 - - - org.pkwmtt - PKWMTT-backend - 0.1.0-ALPHA - PKWMTT-backend - PKWM App (Server) – timetable, exam calendar and ECTS calculator for students of Mechanical Engineering - @ Cracow University of Technology - - https://github.com/PKTTTeam/PKWMTT-backend - - - MIT - - - - - Mikołaj Florczak - https://github.com/florczaq - PKWM Mobile App Team - https://github.com/PKTTTeam - - - - - - - - - - - - 21 - - + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.5.3 + + + org.pkwmtt + PKWMTT-backend + 0.0.1-SNAPSHOT + PKWMTT-backend + PKWMTT-backend + + + + + + + + + + + + + + + 21 + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-data-jpa + - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-devtools - runtime - true - - - org.springframework.boot - spring-boot-starter-test - test - - - org.springframework.boot - spring-boot-starter-data-jpa - + + + org.projectlombok + lombok + true + - - - org.projectlombok - lombok - true - + + + + + + + - - - com.h2database - h2 - runtime - + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.security + spring-security-test + test + + + + + junit + junit + 4.13.1 + + + + + + + + + org.mockito + mockito-core + 5.18.0 + - - - org.springframework.boot - spring-boot-starter-security - - - org.springframework.security - spring-security-test - test - - - - junit - junit - 4.13.1 - - - org.mockito - mockito-all - 1.10.19 - - - org.mockito - mockito-core - 5.18.0 - + + + com.h2database + h2 + test + - - - org.jsoup - jsoup - 1.15.3 - + + + org.jsoup + jsoup + 1.15.3 + - - - com.github.ben-manes.caffeine - caffeine - 3.1.8 - - - org.springframework.boot - spring-boot-starter-cache - + + + com.github.ben-manes.caffeine + caffeine + 3.1.8 + + + org.springframework.boot + spring-boot-starter-cache + - - - org.springdoc - springdoc-openapi-starter-webmvc-ui - 2.8.9 - + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.6.0 + + + com.mysql + mysql-connector-j + runtime + + + + + com.mysql + mysql-connector-j + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-validation + - - - com.mysql - mysql-connector-j - - - org.springframework.boot - spring-boot-starter-actuator - - - - ch.qos.logback - logback-classic - 1.5.13 - - - org.wiremock.integrations - wiremock-spring-boot - 3.10.0 - - - - org.springframework.boot - spring-boot-starter-mail - - - - io.github.cdimascio - dotenv-java - 3.0.0 - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - - org.projectlombok - lombok - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - org.projectlombok - lombok - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - -javaagent:${settings.localRepository}/org/mockito/mockito-core/5.18.0/mockito-core-5.18.0.jar - - - + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.projectlombok + lombok + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + -javaagent:${settings.localRepository}/org/mockito/mockito-core/5.18.0/mockito-core-5.18.0.jar + + - - + + diff --git a/src/main/java/org/pkwmtt/entity/Exam.java b/src/main/java/org/pkwmtt/entity/Exam.java deleted file mode 100644 index 6cda359..0000000 --- a/src/main/java/org/pkwmtt/entity/Exam.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.pkwmtt.entity; - -import jakarta.persistence.*; -import lombok.Data; -import java.time.LocalDateTime; -import java.util.HashSet; -import java.util.Set; - -@Entity -@Table(name = "`exams`") -@Data -public class Exam { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "exam_id") - private Integer examId; - - @Column(nullable = false) - private String title; - - private String description; - - @Column(name = "`exam_date`", nullable = false) - private LocalDateTime examDate; - - @ManyToOne - @JoinColumn(name = "exam_type_id", nullable = false) - private ExamType examType; - - @ManyToMany - @JoinTable( - name="exams_groups", - joinColumns = @JoinColumn(name = "exam_id"), - inverseJoinColumns = @JoinColumn(name = "group_id") - ) - private Set groups = new HashSet<>();; - -} diff --git a/src/main/java/org/pkwmtt/entity/ExamType.java b/src/main/java/org/pkwmtt/entity/ExamType.java deleted file mode 100644 index fd971ef..0000000 --- a/src/main/java/org/pkwmtt/entity/ExamType.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.pkwmtt.entity; - -import jakarta.persistence.*; -import lombok.Data; - -@Entity -@Data -@Table(name = "`exam_type`") -public class ExamType { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "exam_type_id", nullable = false) - private Integer examTypeId; - - @Column(nullable = false) - private String name; -} diff --git a/src/main/java/org/pkwmtt/entity/GeneralGroup.java b/src/main/java/org/pkwmtt/entity/GeneralGroup.java deleted file mode 100644 index eb00158..0000000 --- a/src/main/java/org/pkwmtt/entity/GeneralGroup.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.pkwmtt.entity; - -import jakarta.persistence.*; -import lombok.Data; - -@Entity -@Data -@Table(name = "`general_group`") -public class GeneralGroup { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "general_group_id") - private Integer generalGroupId; - - @Column(nullable = false) - private String name; -} diff --git a/src/main/java/org/pkwmtt/entity/OTPCode.java b/src/main/java/org/pkwmtt/entity/OTPCode.java deleted file mode 100644 index a202b42..0000000 --- a/src/main/java/org/pkwmtt/entity/OTPCode.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.pkwmtt.entity; - -import jakarta.persistence.*; -import lombok.Data; -import java.time.LocalDateTime; - -@Entity -@Data -@Table(name = "otp_codes") -public class OTPCode { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "otp_code_id") - private Integer otpCodeId; - - @Column(nullable = false) - private String code; - - @Column(nullable = false) - private LocalDateTime expire; - - @OneToOne - @JoinColumn(name = "`general_group_id`", nullable = false) - private GeneralGroup generalGroup; -} diff --git a/src/main/java/org/pkwmtt/entity/StudentGroup.java b/src/main/java/org/pkwmtt/entity/StudentGroup.java deleted file mode 100644 index 5c0f10c..0000000 --- a/src/main/java/org/pkwmtt/entity/StudentGroup.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.pkwmtt.entity; - -import jakarta.persistence.*; -import lombok.Data; - -import java.util.HashSet; -import java.util.Set; - -@Entity -@Data -@Table(name = "`groups`") -public class StudentGroup { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "group_id") - private Integer groupId; - - @Column(nullable = false) - private String name; - - @ManyToMany(mappedBy = "groups") - private Set exams = new HashSet<>(); -} diff --git a/src/main/java/org/pkwmtt/entity/User.java b/src/main/java/org/pkwmtt/entity/User.java deleted file mode 100644 index 81a03c8..0000000 --- a/src/main/java/org/pkwmtt/entity/User.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.pkwmtt.entity; - -import jakarta.persistence.*; -import lombok.Data; -import org.pkwmtt.enums.Role; - -@Entity -@Data -@Table(name = "`users`") -public class User { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "user_id") - private Integer userId; - - @OneToOne - @JoinColumn(name = "general_group_id", nullable = false) - private GeneralGroup generalGroup; - - @Column(nullable = false) - private String email; - - @Column(name = "is_active", nullable = false) - private boolean isActive; - - @Enumerated(EnumType.STRING) - @Column(nullable = false) - private Role role; -} diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamController.java b/src/main/java/org/pkwmtt/examCalendar/ExamController.java new file mode 100644 index 0000000..7c69cff --- /dev/null +++ b/src/main/java/org/pkwmtt/examCalendar/ExamController.java @@ -0,0 +1,90 @@ +package org.pkwmtt.examCalendar; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; +import lombok.RequiredArgsConstructor; +import org.pkwmtt.examCalendar.dto.ExamDto; +import org.pkwmtt.examCalendar.entity.Exam; +import org.pkwmtt.examCalendar.entity.ExamType; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import java.net.URI; +import java.util.List; +import java.util.Set; + +@Validated +@RequiredArgsConstructor +@RequestMapping("/pkwmtt/api/v1/exams") +@RestController +public class ExamController { + + private final ExamService examService; + + /** + * @param examDto details of exam + * @return 201 created with URI to GET method which returns created resource + */ + @PostMapping("") + public ResponseEntity addExam(@RequestBody @Valid ExamDto examDto) { + int id = examService.addExam(examDto); + URI uri = ServletUriComponentsBuilder + .fromCurrentRequest() + .path("/{id}") + .buildAndExpand(id) + .toUri(); + return ResponseEntity.created(uri).build(); +// TODO: test not null validation in controller + } + + /** + * @param id of exam or test + * @param examDto new details of exam or test + * @return 204 no content + */ + @PutMapping("/{id}") + public ResponseEntity modifyExam(@PathVariable @Positive int id, @RequestBody @Valid ExamDto examDto) { + examService.modifyExam(examDto, id); + return ResponseEntity.noContent().build(); + } + + /** + * @param id of exam or test + * @return 204 no content + */ + @DeleteMapping("/{id}") + public ResponseEntity deleteExam(@PathVariable int id) { + examService.deleteExam(id); + return ResponseEntity.noContent().build(); + } + + /** + * @param id of exam or test + * @return 200 ok with single exam or test details + */ + @GetMapping("/{id}") + public ResponseEntity getExam(@PathVariable int id) { + return ResponseEntity.ok(examService.getExamById(id)); + } + + /** + * @param groups set of groups + * @return 200 ok with list of exams for specific group + */ + @GetMapping("/by-groups") + public ResponseEntity> getExams(@RequestParam Set groups){ + return ResponseEntity.ok(examService.getExamByGroup(groups)); + } + + /** + * @return 200 ok with list of available exam types + */ +// should be moved to new controller? + @GetMapping("/exam-types") + public ResponseEntity> getExamTypes(){ + return ResponseEntity.ok(examService.getExamTypes()); + } + +} \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java b/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java new file mode 100644 index 0000000..845be7a --- /dev/null +++ b/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java @@ -0,0 +1,48 @@ +package org.pkwmtt.examCalendar; + +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import org.pkwmtt.exceptions.ErrorResponseDTO; +import org.pkwmtt.exceptions.ExamTypeNotExistsException; +import org.pkwmtt.exceptions.NoSuchElementWithProvidedIdException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import java.util.stream.Collectors; + +@RestControllerAdvice +public class ExamControllerAdvice { + +// TODO: handle or remove UnsupportedCountOfArgumentsException + + @ExceptionHandler(NoSuchElementWithProvidedIdException.class) + public ResponseEntity handleNoSuchElementWithProvidedIdException(NoSuchElementWithProvidedIdException e) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponseDTO(e.getMessage())); + } + + @ExceptionHandler(ExamTypeNotExistsException.class) + public ResponseEntity handleExamTypeNotExistsException(ExamTypeNotExistsException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorResponseDTO(e.getMessage())); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + String message = e.getBindingResult().getFieldErrors().stream() + .map(field -> field.getField() + " : " + field.getDefaultMessage()) + .collect(Collectors.joining(", ")); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorResponseDTO(message)); + } + + @ExceptionHandler(ConstraintViolationException.class) + public ResponseEntity handleConstraintViolationException(ConstraintViolationException e) { + String message = e.getConstraintViolations().stream() + .map(field -> field.getPropertyPath() + " : " + field.getMessage()) + .collect(Collectors.joining(", ")); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorResponseDTO(message)); + } + + +} diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamService.java b/src/main/java/org/pkwmtt/examCalendar/ExamService.java new file mode 100644 index 0000000..8da2396 --- /dev/null +++ b/src/main/java/org/pkwmtt/examCalendar/ExamService.java @@ -0,0 +1,86 @@ +package org.pkwmtt.examCalendar; + +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.pkwmtt.examCalendar.dto.ExamDto; +import org.pkwmtt.examCalendar.entity.Exam; +import org.pkwmtt.examCalendar.entity.ExamType; +import org.pkwmtt.examCalendar.mapper.ExamDtoToExamMapper; +import org.pkwmtt.examCalendar.repository.ExamRepository; +import org.pkwmtt.examCalendar.repository.ExamTypeRepository; +import org.pkwmtt.exceptions.NoSuchElementWithProvidedIdException; +import org.pkwmtt.exceptions.UnsupportedCountOfArgumentsException; +import org.springframework.stereotype.Service; + +import java.util.*; + +@Service +@RequiredArgsConstructor +@Transactional +public class ExamService { + + private final ExamRepository examRepository; + private final ExamDtoToExamMapper examMapper; + private final ExamTypeRepository examTypeRepository; + + /** + * @param examDto details of exam + * @return id of exam added to database + */ + public int addExam(ExamDto examDto) { + return examRepository.save(examMapper.mapToNewExam(examDto)).getExamId(); + } + + /** + * @param examDto new details of exam that overwrite old ones + * @param id of exam that need to be modified + */ + public void modifyExam(ExamDto examDto, int id) { + examRepository.findById(id).orElseThrow(() -> new NoSuchElementWithProvidedIdException(id)); + examRepository.save(examMapper.mapToExistingExam(examDto, id)); + } + + /** + * @param id of exam + */ + public void deleteExam(int id) { + examRepository.findById(id).orElseThrow(() -> new NoSuchElementWithProvidedIdException(id)); + examRepository.deleteById(id); + } + + /** + * @param id of exam + * @return exam + */ + public Exam getExamById(int id) { + return examRepository.findById(id).orElseThrow(() -> new NoSuchElementWithProvidedIdException(id)); + } + + /** + * @param groups set od groups (max 4) + * @return set of exams for specific groups + */ + public Set getExamByGroup(Set groups) { + if (groups.size() > 4 || groups.isEmpty()) + throw new UnsupportedCountOfArgumentsException(1, 5, groups.size()); + List groupList = new ArrayList<>(groups); + return switch (groupList.size()) { + case 4 -> examRepository.findExamsByGroupsIdentifier( + groupList.get(0), groupList.get(1), groupList.get(2), groupList.get(3)); + case 3 -> examRepository.findExamsByGroupsIdentifier( + groupList.get(0), groupList.get(1), groupList.get(2)); + case 2 -> examRepository.findExamsByGroupsIdentifier( + groupList.get(0), groupList.get(1)); + case 1 -> examRepository.findExamsByGroupsIdentifier( + groupList.get(0)); + default -> Set.of(); + }; + } + + /** + * @return list of examTypes + */ + public List getExamTypes() { + return examTypeRepository.findAll(); + } +} diff --git a/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java b/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java new file mode 100644 index 0000000..f1cfdfb --- /dev/null +++ b/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java @@ -0,0 +1,33 @@ +package org.pkwmtt.examCalendar.dto; + +import jakarta.validation.constraints.Future; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.time.LocalDateTime; + +@Getter +@RequiredArgsConstructor +public class ExamDto { + + @NotBlank + @Size(max = 255, message = "max size of field is 255") + private final String title; + + @Size(max = 255, message = "max size of field is 255") + private final String description; + + @Future(message = "Date must be in the future") + @NotNull + private final LocalDateTime date; + + @NotBlank + @Size(max = 255, message = "max size of field is 255") + private final String examGroups; + + @NotNull + private final String examType; +} diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java b/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java new file mode 100644 index 0000000..89fe16d --- /dev/null +++ b/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java @@ -0,0 +1,48 @@ +package org.pkwmtt.examCalendar.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.pkwmtt.exceptions.InvalidGroupIdentifierException; + +import java.time.LocalDateTime; +import java.util.Arrays; + +@Entity +@Getter +@Builder(builderClassName = "Builder", buildMethodName = "build") +@RequiredArgsConstructor +@Table(name = "exams") +@AllArgsConstructor +public class Exam { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer examId; + + private String title; + + private String description; + + private LocalDateTime date; + + @Column(name = "`groups`") + private String examGroups; + + @ManyToOne + @JoinColumn(name = "exam_type_id") + private ExamType examType; + + @SuppressWarnings("unused") + public static class Builder { + public Exam build() { + // max length of group identifier is 6 + Arrays.stream(examGroups.split(", ")).forEach(group -> { + if(group.length() > 6) + throw new InvalidGroupIdentifierException(group); + }); + return new Exam(examId, title, description, date, examGroups, examType); + } + } +} diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/ExamType.java b/src/main/java/org/pkwmtt/examCalendar/entity/ExamType.java new file mode 100644 index 0000000..6c5355f --- /dev/null +++ b/src/main/java/org/pkwmtt/examCalendar/entity/ExamType.java @@ -0,0 +1,21 @@ +package org.pkwmtt.examCalendar.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Entity +@Getter +@Builder +@AllArgsConstructor +@RequiredArgsConstructor +@Table(name = "exam_type") +public class ExamType { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer exam_type_id; + + private String name; +} \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/GeneralGroup.java b/src/main/java/org/pkwmtt/examCalendar/entity/GeneralGroup.java new file mode 100644 index 0000000..3eb27af --- /dev/null +++ b/src/main/java/org/pkwmtt/examCalendar/entity/GeneralGroup.java @@ -0,0 +1,26 @@ +package org.pkwmtt.examCalendar.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.Set; + +@Entity +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Table(name = "`general_group`") +public class GeneralGroup { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer general_group_id; + + private String name; + + @OneToMany(mappedBy = "general_group") + private Set groups; +} diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/Group.java b/src/main/java/org/pkwmtt/examCalendar/entity/Group.java new file mode 100644 index 0000000..050a5cc --- /dev/null +++ b/src/main/java/org/pkwmtt/examCalendar/entity/Group.java @@ -0,0 +1,27 @@ +package org.pkwmtt.examCalendar.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Table(name = "`groups`") +public class Group { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer group_id; + + private String name; + + private int group_count; + + @ManyToOne + @JoinColumn(name = "general_group_id") + private GeneralGroup general_group; +} diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/OTPCode.java b/src/main/java/org/pkwmtt/examCalendar/entity/OTPCode.java new file mode 100644 index 0000000..47d3a9a --- /dev/null +++ b/src/main/java/org/pkwmtt/examCalendar/entity/OTPCode.java @@ -0,0 +1,31 @@ +package org.pkwmtt.examCalendar.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Entity +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Table(name = "otp_codes") +public class OTPCode { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer otp_code_id; + + private String code; + + private LocalDateTime timestamp; + + private boolean used; + + @OneToOne + @JoinColumn(name = "user_id", unique = true) + private User user; +} diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/User.java b/src/main/java/org/pkwmtt/examCalendar/entity/User.java new file mode 100644 index 0000000..cb90f87 --- /dev/null +++ b/src/main/java/org/pkwmtt/examCalendar/entity/User.java @@ -0,0 +1,33 @@ +package org.pkwmtt.examCalendar.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.pkwmtt.enums.Role; + +@Entity +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Table(name = "`users`") +public class User { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer user_id; + + @ManyToOne + @JoinColumn(name = "general_group_id") + private GeneralGroup general_group; + + private String email; + + private boolean is_active; + + private Role role; + + @OneToOne(mappedBy = "user") + private OTPCode otp_code; +} diff --git a/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java new file mode 100644 index 0000000..343f70e --- /dev/null +++ b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java @@ -0,0 +1,49 @@ +package org.pkwmtt.examCalendar.mapper; + +import lombok.RequiredArgsConstructor; +import org.pkwmtt.examCalendar.dto.ExamDto; +import org.pkwmtt.examCalendar.entity.Exam; +import org.pkwmtt.examCalendar.repository.ExamTypeRepository; +import org.pkwmtt.exceptions.ExamTypeNotExistsException; +import org.springframework.stereotype.Component; + +/** + * maps ExamDto to Exam entity. Couldn't be utility class, because needs ExamTypeRepository to validate exam types + */ +@Component +@RequiredArgsConstructor +public class ExamDtoToExamMapper { + private final ExamTypeRepository examTypeRepository; + + /** + * @param examDto examDto object received from request + * @return Exam entity WITHOUT examId which should be assigned by database + * Also contains examType field converted from String do ExamType + */ + public Exam mapToNewExam(ExamDto examDto) { + return Exam.builder() + .title(examDto.getTitle()) + .description(examDto.getDescription()) + .date(examDto.getDate()) + .examGroups(examDto.getExamGroups()) + .examType(examTypeRepository.findByName(examDto.getExamType()).orElseThrow(() -> new ExamTypeNotExistsException(examDto.getExamType()))) + .build(); + } + + /** + * @param examDto examDto object received from request + * @param id of Exam that need to be modified + * @return Exam entity WITH examId that allow to update entity in database instead of creating new one + * Also contains examType field converted from String do ExamType + */ + public Exam mapToExistingExam(ExamDto examDto, int id) { + return Exam.builder() + .examId(id) + .title(examDto.getTitle()) + .description(examDto.getDescription()) + .date(examDto.getDate()) + .examGroups(examDto.getExamGroups()) + .examType(examTypeRepository.findByName(examDto.getExamType()).orElseThrow(() -> new ExamTypeNotExistsException(examDto.getExamType()))) + .build(); + } +} diff --git a/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java new file mode 100644 index 0000000..e6d3454 --- /dev/null +++ b/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java @@ -0,0 +1,74 @@ +package org.pkwmtt.examCalendar.repository; + +import org.pkwmtt.examCalendar.entity.Exam; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.Set; + +public interface ExamRepository extends JpaRepository { + + /** + * fetch all data using one query + * @param group1 group identifier + * @param group2 group identifier + * @param group3 group identifier + * @param group4 group identifier + * @return set of Exams for specific groups + */ + @Query("SELECT e FROM Exam e JOIN FETCH e.examType WHERE " + + "e.examGroups LIKE CONCAT('%', :g1, '%') OR " + + "e.examGroups LIKE CONCAT('%', :g2, '%') OR " + + "e.examGroups LIKE CONCAT('%', :g3, '%') OR " + + "e.examGroups LIKE CONCAT('%', :g4, '%') ") + Set findExamsByGroupsIdentifier( + @Param("g1") String group1, + @Param("g2") String group2, + @Param("g3") String group3, + @Param("g4") String group4 + ); + + /** + * fetch all data using one query + * @param group1 group identifier + * @param group2 group identifier + * @param group3 group identifier + * @return set of Exams for specific groups + */ + @Query("SELECT e FROM Exam e JOIN FETCH e.examType WHERE " + + "e.examGroups LIKE CONCAT('%', :g1, '%') OR " + + "e.examGroups LIKE CONCAT('%', :g2, '%') OR " + + "e.examGroups LIKE CONCAT('%', :g3, '%') ") + Set findExamsByGroupsIdentifier( + @Param("g1") String group1, + @Param("g2") String group2, + @Param("g3") String group3 + ); + + /** + * fetch all data using one query + * @param group1 group identifier + * @param group2 group identifier + * @return set of Exams for specific groups + */ + @Query("SELECT e FROM Exam e JOIN FETCH e.examType WHERE " + + "e.examGroups LIKE CONCAT('%', :g1, '%') OR " + + "e.examGroups LIKE CONCAT('%', :g2, '%')" ) + Set findExamsByGroupsIdentifier( + @Param("g1") String group1, + @Param("g2") String group2 + ); + + /** + * fetch all data using one query + * @param group group identifier + * @return set of Exams for specific group + */ + @Query("SELECT e FROM Exam e JOIN FETCH e.examType WHERE " + + "e.examGroups LIKE CONCAT('%', :gg, '%')") + Set findExamsByGroupsIdentifier( + @Param("gg") String group + ); + +} \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/examCalendar/repository/ExamTypeRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/ExamTypeRepository.java new file mode 100644 index 0000000..c14d733 --- /dev/null +++ b/src/main/java/org/pkwmtt/examCalendar/repository/ExamTypeRepository.java @@ -0,0 +1,10 @@ +package org.pkwmtt.examCalendar.repository; + +import org.pkwmtt.examCalendar.entity.ExamType; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface ExamTypeRepository extends JpaRepository { + Optional findByName(String name); +} \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/repository/GeneralGroupRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/GeneralGroupRepository.java similarity index 61% rename from src/main/java/org/pkwmtt/repository/GeneralGroupRepository.java rename to src/main/java/org/pkwmtt/examCalendar/repository/GeneralGroupRepository.java index a4c1c55..62f4fbb 100644 --- a/src/main/java/org/pkwmtt/repository/GeneralGroupRepository.java +++ b/src/main/java/org/pkwmtt/examCalendar/repository/GeneralGroupRepository.java @@ -1,6 +1,6 @@ -package org.pkwmtt.repository; +package org.pkwmtt.examCalendar.repository; -import org.pkwmtt.entity.GeneralGroup; +import org.pkwmtt.examCalendar.entity.GeneralGroup; import org.springframework.data.jpa.repository.JpaRepository; public interface GeneralGroupRepository extends JpaRepository { diff --git a/src/main/java/org/pkwmtt/examCalendar/repository/GroupRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/GroupRepository.java new file mode 100644 index 0000000..98bb7a3 --- /dev/null +++ b/src/main/java/org/pkwmtt/examCalendar/repository/GroupRepository.java @@ -0,0 +1,7 @@ +package org.pkwmtt.examCalendar.repository; + +import org.pkwmtt.examCalendar.entity.Group; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface GroupRepository extends JpaRepository { +} \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/repository/OTPCodeRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/OTPCodeRepository.java similarity index 60% rename from src/main/java/org/pkwmtt/repository/OTPCodeRepository.java rename to src/main/java/org/pkwmtt/examCalendar/repository/OTPCodeRepository.java index 4f79485..848b4d4 100644 --- a/src/main/java/org/pkwmtt/repository/OTPCodeRepository.java +++ b/src/main/java/org/pkwmtt/examCalendar/repository/OTPCodeRepository.java @@ -1,6 +1,6 @@ -package org.pkwmtt.repository; +package org.pkwmtt.examCalendar.repository; -import org.pkwmtt.entity.OTPCode; +import org.pkwmtt.examCalendar.entity.OTPCode; import org.springframework.data.jpa.repository.JpaRepository; public interface OTPCodeRepository extends JpaRepository { diff --git a/src/main/java/org/pkwmtt/repository/UserRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/UserRepository.java similarity index 60% rename from src/main/java/org/pkwmtt/repository/UserRepository.java rename to src/main/java/org/pkwmtt/examCalendar/repository/UserRepository.java index 71ccd75..acdf767 100644 --- a/src/main/java/org/pkwmtt/repository/UserRepository.java +++ b/src/main/java/org/pkwmtt/examCalendar/repository/UserRepository.java @@ -1,6 +1,6 @@ -package org.pkwmtt.repository; +package org.pkwmtt.examCalendar.repository; -import org.pkwmtt.entity.User; +import org.pkwmtt.examCalendar.entity.User; import org.springframework.data.jpa.repository.JpaRepository; public interface UserRepository extends JpaRepository { diff --git a/src/main/java/org/pkwmtt/exceptions/ExamTypeNotExistsException.java b/src/main/java/org/pkwmtt/exceptions/ExamTypeNotExistsException.java new file mode 100644 index 0000000..5e8171a --- /dev/null +++ b/src/main/java/org/pkwmtt/exceptions/ExamTypeNotExistsException.java @@ -0,0 +1,7 @@ +package org.pkwmtt.exceptions; + +public class ExamTypeNotExistsException extends RuntimeException { + public ExamTypeNotExistsException(String examType) { + super("Invalid exam type " + examType); + } +} diff --git a/src/main/java/org/pkwmtt/exceptions/InvalidGroupIdentifierException.java b/src/main/java/org/pkwmtt/exceptions/InvalidGroupIdentifierException.java new file mode 100644 index 0000000..4faadac --- /dev/null +++ b/src/main/java/org/pkwmtt/exceptions/InvalidGroupIdentifierException.java @@ -0,0 +1,7 @@ +package org.pkwmtt.exceptions; + +public class InvalidGroupIdentifierException extends RuntimeException { + public InvalidGroupIdentifierException(String groupIdentifier) { + super("Invalid group identifier: " + groupIdentifier); + } +} diff --git a/src/main/java/org/pkwmtt/exceptions/NoSuchElementWithProvidedIdException.java b/src/main/java/org/pkwmtt/exceptions/NoSuchElementWithProvidedIdException.java new file mode 100644 index 0000000..e17eead --- /dev/null +++ b/src/main/java/org/pkwmtt/exceptions/NoSuchElementWithProvidedIdException.java @@ -0,0 +1,7 @@ +package org.pkwmtt.exceptions; + +public class NoSuchElementWithProvidedIdException extends RuntimeException{ + public NoSuchElementWithProvidedIdException(int id) { + super("No such element with id: " + id); + } +} diff --git a/src/main/java/org/pkwmtt/exceptions/UnsupportedCountOfArgumentsException.java b/src/main/java/org/pkwmtt/exceptions/UnsupportedCountOfArgumentsException.java new file mode 100644 index 0000000..709978a --- /dev/null +++ b/src/main/java/org/pkwmtt/exceptions/UnsupportedCountOfArgumentsException.java @@ -0,0 +1,8 @@ +package org.pkwmtt.exceptions; + +public class UnsupportedCountOfArgumentsException extends RuntimeException { + public UnsupportedCountOfArgumentsException(int expectedMin, int expectedMax, int provided) { + super("Invalid count of arguments provided: " + provided + + " expected more than: " + expectedMin + " less than: " + expectedMax); + } +} diff --git a/src/main/java/org/pkwmtt/repository/ExamRepository.java b/src/main/java/org/pkwmtt/repository/ExamRepository.java deleted file mode 100644 index 2faafaa..0000000 --- a/src/main/java/org/pkwmtt/repository/ExamRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.pkwmtt.repository; - -import org.pkwmtt.entity.Exam; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface ExamRepository extends JpaRepository { -} \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/repository/ExamTypeRepository.java b/src/main/java/org/pkwmtt/repository/ExamTypeRepository.java deleted file mode 100644 index 1b7d38c..0000000 --- a/src/main/java/org/pkwmtt/repository/ExamTypeRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.pkwmtt.repository; - -import org.pkwmtt.entity.ExamType; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface ExamTypeRepository extends JpaRepository { -} \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/repository/GroupRepository.java b/src/main/java/org/pkwmtt/repository/GroupRepository.java deleted file mode 100644 index b2396a9..0000000 --- a/src/main/java/org/pkwmtt/repository/GroupRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.pkwmtt.repository; - -import org.pkwmtt.entity.StudentGroup; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface GroupRepository extends JpaRepository { -} \ No newline at end of file diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java new file mode 100644 index 0000000..5f7f6ae --- /dev/null +++ b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java @@ -0,0 +1,620 @@ +package org.pkwmtt.examCalendar; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.pkwmtt.examCalendar.dto.ExamDto; +import org.pkwmtt.examCalendar.entity.Exam; +import org.pkwmtt.examCalendar.entity.ExamType; +import org.pkwmtt.examCalendar.repository.ExamRepository; +import org.pkwmtt.examCalendar.repository.ExamTypeRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.ResultMatcher; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.HashMap; +import java.util.Map; + +import static org.hamcrest.Matchers.containsString; +import static org.junit.jupiter.api.Assertions.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * integration tests of ExamCalendar + */ +@SpringBootTest +@AutoConfigureMockMvc +class ExamControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ExamTypeRepository examTypeRepository; + + @Autowired + private ExamRepository examRepository; + + @Autowired + private ObjectMapper mapper; + + @BeforeEach + void setupBeforeEach() { + examRepository.deleteAll(); + examTypeRepository.deleteAll(); + } + + // + + /** + * check if addExam endpoint create new exam with correct URI and correct data + */ + @Test + void addExamWithCorrectData() throws Exception { +// given + createExampleExamType("Project"); + ExamDto examDtoRequest = createExampleExamDto("Project"); + String json = mapper.writeValueAsString(examDtoRequest); + + MvcResult result = mockMvc.perform(MockMvcRequestBuilders + .post("/pkwmtt/api/v1/exams") + .contentType("application/json") + .content(json) + ).andDo(print()) + .andExpect(status().isCreated()) + .andExpect(header().string("Location", containsString("/pkwmtt/api/v1/exams/"))) + .andReturn(); + + String location = result.getResponse().getHeader("Location"); + @SuppressWarnings("DataFlowIssue") + int id = Integer.parseInt(location.substring(location.lastIndexOf("/") + 1)); + + Exam examResponse = examRepository.findById(id).orElseThrow(); + + assertEquals(examDtoRequest.getTitle(), examResponse.getTitle()); + assertEquals(examDtoRequest.getDescription(), examResponse.getDescription()); +// compare dates with minutes level precision + assertEquals( + examDtoRequest.getDate().truncatedTo(ChronoUnit.MINUTES), + examResponse.getDate().truncatedTo(ChronoUnit.MINUTES) + ); + assertEquals(examDtoRequest.getExamGroups(), examResponse.getExamGroups()); + assertEquals(examDtoRequest.getExamType(), examResponse.getExamType().getName()); + } + + @Test + void addExamWithBlankExamTitle() throws Exception { +// given + createExampleExamType("Project"); + Map requestData = new HashMap<>(); +// no exam title + requestData.put("description", "first exam"); + requestData.put("date", LocalDateTime.now().plusDays(1).toString()); + requestData.put("examGroups", "12K2, L04"); + requestData.put("examType", "Project"); + +// when + MvcResult result = assertPostRequest(status().isBadRequest(), requestData); + +// then + assertResponseMessage("title : must not be blank", result); + } + + @Test + void addExamWithBlankExamDescription() throws Exception { +// given + createExampleExamType("Project"); + Map requestData = new HashMap<>(); + requestData.put("title", "Math exam"); +// no exam description + requestData.put("date", LocalDateTime.now().plusDays(1).toString()); + requestData.put("examGroups", "12K2, L04"); + requestData.put("examType", "Project"); + +// when + MvcResult result = assertPostRequest(status().isCreated(), requestData); + + String location = result.getResponse().getHeader("Location"); + @SuppressWarnings("DataFlowIssue") + int id = Integer.parseInt(location.substring(location.lastIndexOf("/") + 1)); + + Exam examResponse = examRepository.findById(id).orElseThrow(); + assertNull(examResponse.getDescription()); + } + + @Test + void addExamWithBlankDate() throws Exception { +// given + createExampleExamType("Project"); + Map requestData = new HashMap<>(); + requestData.put("title", "Math exam"); + requestData.put("description", "first exam"); +// no date + requestData.put("examGroups", "12K2, L04"); + requestData.put("examType", "Project"); + +// when + MvcResult result = assertPostRequest(status().isBadRequest(), requestData); + +// then + assertResponseMessage("date : must not be null", result); + } + + @Test + void addExamWithBlankExamGroups() throws Exception { +// given + createExampleExamType("Project"); + Map requestData = new HashMap<>(); + requestData.put("title", "Math exam"); + requestData.put("description", "first exam"); + requestData.put("date", LocalDateTime.now().plusDays(1).toString()); +// no examGroups + requestData.put("examType", "Project"); + +// when + MvcResult result = assertPostRequest(status().isBadRequest(), requestData); + +// then + assertResponseMessage("examGroups : must not be blank", result); + } + + @Test + void addExamWithNullExamTypes() throws Exception { +// given + createExampleExamType("Project"); + Map requestData = new HashMap<>(); + requestData.put("title", "Math exam"); + requestData.put("description", "first exam"); + requestData.put("date", LocalDateTime.now().plusDays(1).toString()); + requestData.put("examGroups", "12K2, L04"); +// no examType + +// when + MvcResult result = assertPostRequest(status().isBadRequest(), requestData); + +// then + assertResponseMessage("examType : must not be null", result); + } + + @Test + void addExamWithNotFutureDate() throws Exception { +// given + createExampleExamType("Project"); + Map requestData = new HashMap<>(); + requestData.put("title", "Math exam"); + requestData.put("description", "first exam"); + requestData.put("date", LocalDateTime.now().minusDays(1).toString()); + requestData.put("examGroups", "12K2, L04"); + requestData.put("examType", "Project"); + +// when + MvcResult result = assertPostRequest(status().isBadRequest(), requestData); + +// then + assertResponseMessage("date : Date must be in the future", result); + } + + @Test + void addExamWithEmptyStringExamTitle() throws Exception { +// given + createExampleExamType("Project"); + Map requestData = new HashMap<>(); + requestData.put("title", ""); + requestData.put("description", "first exam"); + requestData.put("date", LocalDateTime.now().plusDays(1).toString()); + requestData.put("examGroups", "12K2, L04"); + requestData.put("examType", "Project"); + +// when + MvcResult result = assertPostRequest(status().isBadRequest(), requestData); + +// then + assertResponseMessage("title : must not be blank", result); + } + + @Test + void addExamWithEmptyStringExamGroups() throws Exception { +// given + createExampleExamType("Project"); + Map requestData = new HashMap<>(); + requestData.put("title", "Math exam"); + requestData.put("description", "first exam"); + requestData.put("date", LocalDateTime.now().plusDays(1).toString()); + requestData.put("examGroups", ""); + requestData.put("examType", "Project"); + +// when + MvcResult result = assertPostRequest(status().isBadRequest(), requestData); + +// then + assertResponseMessage("examGroups : must not be blank", result); + } + + @Test + void addExamWithTooLongExamTitle() throws Exception { +// given + createExampleExamType("Project"); + Map requestData = new HashMap<>(); + requestData.put("title", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + requestData.put("description", "first exam"); + requestData.put("date", LocalDateTime.now().plusDays(1).toString()); + requestData.put("examGroups", "12K2, L04"); + requestData.put("examType", "Project"); + +// when + MvcResult result = assertPostRequest(status().isBadRequest(), requestData); + +// then + assertResponseMessage("title : max size of field is 255", result); + } + + @Test + void addExamWithTooLongDescription() throws Exception { +// given + createExampleExamType("Project"); + Map requestData = new HashMap<>(); + requestData.put("title", "Math exam"); + requestData.put("description", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + requestData.put("date", LocalDateTime.now().plusDays(1).toString()); + requestData.put("examGroups", "12K2, L04"); + requestData.put("examType", "Project"); + +// when + MvcResult result = assertPostRequest(status().isBadRequest(), requestData); + +// then + assertResponseMessage("description : max size of field is 255", result); + } + + @Test + void addExamWithTooLongExamGroups() throws Exception { +// given + createExampleExamType("Project"); + Map requestData = new HashMap<>(); + requestData.put("title", "Math exam"); + requestData.put("description", "first exam"); + requestData.put("date", LocalDateTime.now().plusDays(1).toString()); + requestData.put("examGroups", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + requestData.put("examType", "Project"); + +// when + MvcResult result = assertPostRequest(status().isBadRequest(), requestData); + +// then + assertResponseMessage("examGroups : max size of field is 255", result); + } + + @Test + void addExamWithNonExistingExamType() throws Exception { +// given + createExampleExamType("Project"); + Map requestData = new HashMap<>(); + requestData.put("title", "Math exam"); + requestData.put("description", "first exam"); + requestData.put("date", LocalDateTime.now().plusDays(1).toString()); + requestData.put("examGroups", "12K2, L04"); + requestData.put("examType", "NonExistingExamType"); + +// when + MvcResult result = assertPostRequest(status().isBadRequest(), requestData); + +// then + assertResponseMessage("Invalid exam type NonExistingExamType", result); + } + + + // + + // + @Test + void modifyExamWithCorrectData() throws Exception { +// given + ExamType examType = createExampleExamType("Exam"); + Exam exam = createExampleExam(examType); + int id = examRepository.save(exam).getExamId(); + ExamDto examDto = createExampleExamDto(examType.getName()); + +// when + assertPutRequest(status().isNoContent(), examDto, id); + +// then + Exam responseExam = examRepository.findById(id).orElseThrow(); + assertEquals("Math exam", responseExam.getTitle()); + assertEquals("first exam", responseExam.getDescription()); + assertEquals( + LocalDateTime.now().plusDays(1).truncatedTo(ChronoUnit.MINUTES), + responseExam.getDate().truncatedTo(ChronoUnit.MINUTES) + ); + assertEquals("12K2, L04", responseExam.getExamGroups()); + } + + @Test + void modifyExamWithIncorrectExamId() throws Exception { +// given + ExamType examType = createExampleExamType("Exam"); + Exam exam = createExampleExam(examType); + int id = examRepository.save(exam).getExamId(); + ExamDto examDto = createExampleExamDto(examType.getName()); + + int invalidId = Integer.MAX_VALUE - 10; + assertNotEquals(invalidId, id); +// when + MvcResult result = assertPutRequest(status().isNotFound(), examDto, invalidId); + +// then + assertResponseMessage("No such element with id: " + (invalidId), result); + + } +// + + // + @Test + void deleteExamWithCorrectArguments() throws Exception { +// given + ExamType examType = createExampleExamType("Exam"); + Exam exam = createExampleExam(examType); + int id = examRepository.save(exam).getExamId(); + +// when + assertDeleteRequest(status().isNoContent(), id); + +// then + assertTrue(examRepository.findById(id).isEmpty()); + } + + @Test + void deleteNonExistingExam() throws Exception { +// given + ExamType examType = createExampleExamType("Exam"); + Exam exam = createExampleExam(examType); + int id = examRepository.save(exam).getExamId(); + int invalidId = Integer.MAX_VALUE - 10; + assertNotEquals(invalidId, id); + +// when + MvcResult result = assertDeleteRequest(status().isNotFound(), invalidId); + +// then + assertTrue(examRepository.findById(id).isPresent()); + assertResponseMessage("No such element with id: " + (invalidId), result); + } + + // + + // + + @Test + void getExamByIdWithCorrectId() throws Exception { +// given + ExamType examType = createExampleExamType("Exam"); + Exam exam = createExampleExam(examType); + int id = examRepository.save(exam).getExamId(); + +// when + MvcResult result = assertGetByIdRequest(status().isOk(), id); + JsonNode responseNode = mapper.readTree(result.getResponse().getContentAsString()); + +// then + assertEquals(exam.getTitle(), responseNode.get("title").asText()); + assertEquals(exam.getDescription(), responseNode.get("description").asText()); + assertEquals( + exam.getDate().truncatedTo(ChronoUnit.MINUTES), + LocalDateTime.parse(responseNode.get("date").textValue()).truncatedTo(ChronoUnit.MINUTES) + ); + assertEquals(exam.getExamGroups(), responseNode.get("examGroups").asText()); + assertEquals(mapper.readTree(mapper.writeValueAsString(exam.getExamType())), responseNode.get("examType")); + } + + @Test + void getNonExistingExamById() throws Exception { +// given + ExamType examType = createExampleExamType("Exam"); + Exam exam = createExampleExam(examType); + int id = examRepository.save(exam).getExamId(); + int invalidId = Integer.MAX_VALUE - 10; + assertNotEquals(invalidId, id); + +// when + MvcResult result = assertGetByIdRequest(status().isNotFound(), invalidId); + +// then + assertResponseMessage("No such element with id: " + (invalidId), result); + } + +// + + @Test + void getExams() { +// TODO: test getExamsByGroups after implementing new version + } + + // + + @Test + void getExamTypesWhenExamTypesExists() throws Exception { +// given + ExamType exam = createExampleExamType("Exam"); + ExamType project = createExampleExamType("Project"); + +// when + MvcResult result = assertGetExamTypesRequest(status().isOk()); + JsonNode responseArray = mapper.readTree(result.getResponse().getContentAsString()); + +// then + assertEquals(2, responseArray.size()); + assertTrue(responseArray.valueStream().anyMatch(e -> e.get("name").asText().equals(exam.getName()))); + assertTrue(responseArray.valueStream().anyMatch(e -> e.get("name").asText().equals(project.getName()))); + } + + @Test + void getExamTypesWhenExamTypesNotExists() throws Exception { +// given +// when + MvcResult result = mockMvc.perform(MockMvcRequestBuilders + .get("/pkwmtt/api/v1/exams/exam-types") + .contentType("application/json") + ).andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + JsonNode responseArray = mapper.readTree(result.getResponse().getContentAsString()); + +// then + assertEquals(0, responseArray.size()); + } + + // + + // + + /** + * this method create examType object and add it to repository + * @param name of new examType + * @return created examType object + */ + private ExamType createExampleExamType(String name) { + ExamType examType = ExamType.builder().name(name).build(); + examTypeRepository.save(examType); + return examType; + } + + /** + * this method don't add created Exam to repository, because in that case id of created Exam would be unreachable + * @param type ExamType object which is required argument of Exam + * @return created Exam + */ + private Exam createExampleExam(ExamType type) { + return Exam.builder() + .title("Exam") + .description("Exam description") + .date(LocalDateTime.now().plusDays(1)) + .examGroups("11K1, L01") + .examType(type) + .build(); + } + + /** + * @param examTypeName name of type of exam as String + * @return created ExamDto + */ + private ExamDto createExampleExamDto(String examTypeName) { + return new ExamDto( + "Math exam", + "first exam", + LocalDateTime.now().plusDays(1), + "12K2, L04", + examTypeName + ); + } + + /** + * compare error message form response with expected value + * @param expectedMessage full message that is expected in response + * @param result response generated by mockMvc.perform() or one of assert[httpMethod]Request() + * @throws Exception + */ + private void assertResponseMessage(String expectedMessage, MvcResult result) throws Exception { + JsonNode jsonResponse = mapper.readTree(result.getResponse().getContentAsString()); + assertTrue(jsonResponse.has("message")); + assertEquals(expectedMessage, jsonResponse.get("message").asText()); + } + + /** + * method send POST request to ExamController with content as JSON attached to body and then check if response + * code is the same as expected + * @param expectedStatus status().[http response] (example: status().isCreated() ) + * @param content object that would be mapped to JSON by ObjectMapper and then attached to request + * it could be dto object or Map + * @return MvcResult object which could be used to capture response body + * @throws Exception + */ + private MvcResult assertPostRequest(ResultMatcher expectedStatus, Object content) throws Exception { + return mockMvc.perform(MockMvcRequestBuilders + .post("/pkwmtt/api/v1/exams") + .contentType("application/json") + .content(mapper.writeValueAsString(content)) + ).andDo(print()) + .andExpect(expectedStatus) + .andReturn(); + } + + /** + * method send PUT request to ExamController with content as JSON attached to body and examId as pathID. + * Then check if response code is the same as expected + * @param expectedStatus status().[http response] (example: status().isNoContent() ) + * @param content object that would be mapped to JSON by ObjectMapper and then attached to request + * @param pathId id of resource that would be updated + * @return MvcResult object which could be used to capture response body + * @throws Exception + */ + private MvcResult assertPutRequest(ResultMatcher expectedStatus, Object content, int pathId) throws Exception { + return mockMvc.perform(MockMvcRequestBuilders + .put("/pkwmtt/api/v1/exams/{id}", pathId) + .contentType("application/json") + .content(mapper.writeValueAsString(content)) + ).andDo(print()) + .andExpect(expectedStatus) + .andReturn(); + } + + /** + * method send DELETE request to ExamController with examId as pathID. + * Then check if response code is the same as expected + * @param expectedStatus status().[http response] (example: status().isNoContent() ) + * @param pathId id of resource that would be deleted + * @return MvcResult object which could be used to capture response body + * @throws Exception + */ + private MvcResult assertDeleteRequest(ResultMatcher expectedStatus, int pathId) throws Exception { + return mockMvc.perform(MockMvcRequestBuilders + .delete("/pkwmtt/api/v1/exams/{id}", pathId) + .contentType("application/json") + ).andDo(print()) + .andExpect(expectedStatus) + .andReturn(); + } + + /** + * method send GET request to ExamController at /pkwmtt/api/v1/exams/{id} URI with examId as pathID. + * Then check if response code is the same as expected + * @param expectedStatus status().[http response] (example: status().isOk() ) + * @param pathId id of resource that would be returned + * @return MvcResult object which could be used to capture response body + * @throws Exception + */ + private MvcResult assertGetByIdRequest(ResultMatcher expectedStatus, int pathId) throws Exception { + return mockMvc.perform(MockMvcRequestBuilders + .get("/pkwmtt/api/v1/exams/{id}", pathId) + .contentType("application/json") + ).andDo(print()) + .andExpect(expectedStatus) + .andReturn(); + } + + /** + * method send GET request to ExamController at /pkwmtt/api/v1/exams/exam-types URI. + * Then check if response code is the same as expected + * @param expectedStatus expectedStatus status().[http response] (example: status().isOk() ) + * @return MvcResult object which could be used to capture response body + * @throws Exception + */ + private MvcResult assertGetExamTypesRequest(ResultMatcher expectedStatus) throws Exception { + return mockMvc.perform(MockMvcRequestBuilders + .get("/pkwmtt/api/v1/exams/exam-types") + .contentType("application/json") + ).andDo(print()) + .andExpect(expectedStatus) + .andReturn(); + } + +// + +} \ No newline at end of file diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java new file mode 100644 index 0000000..c86b0ab --- /dev/null +++ b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java @@ -0,0 +1,331 @@ +package org.pkwmtt.examCalendar; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.pkwmtt.examCalendar.dto.ExamDto; +import org.pkwmtt.examCalendar.entity.Exam; +import org.pkwmtt.examCalendar.entity.ExamType; +import org.pkwmtt.examCalendar.mapper.ExamDtoToExamMapper; +import org.pkwmtt.examCalendar.repository.ExamRepository; +import org.pkwmtt.exceptions.UnsupportedCountOfArgumentsException; + +import java.lang.reflect.Field; +import java.time.LocalDateTime; +import java.util.*; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class ExamServiceTest { + + @Mock + private ExamRepository examRepository; + + @Mock + private ExamDtoToExamMapper examDtoToExamMapper; + + @InjectMocks + private ExamService examService; + + @Test + void addExam() { +// given + int examId = 1; + ExamDto examDto = new ExamDto( + "Math exam", + "desc", + LocalDateTime.now().plusDays(1), + "12K2, 13L1", + "Exam" + ); + Exam exam = Exam.builder() + .title("Math exam") + .description("desc") + .date(LocalDateTime.now().plusDays(1)) + .examGroups("12K2, 13L1") + .examType(new ExamType(1, "Exam")) + .build(); + when(examDtoToExamMapper.mapToNewExam(examDto)).thenReturn(exam); + +// assign exam id in repository + when(examRepository.save(exam)).thenAnswer(invocation -> { + Exam newExam = invocation.getArgument(0, Exam.class); + Field field = Exam.class.getDeclaredField("examId"); + field.setAccessible(true); + field.set(newExam, examId); + return newExam; + }); +// when + int result = examService.addExam(examDto); +// then + assertEquals(examId, result); + verify(examRepository).save(exam); + verify(examDtoToExamMapper).mapToNewExam(examDto); + } + + /************************************************************************************/ +//modify exam + @Test + void shouldModifyExamWhenIdExists() { + // given + int examId = 1; + ExamDto examDto = mock(ExamDto.class); + Exam exam = mock(Exam.class); + + when(examDtoToExamMapper.mapToExistingExam(examDto, examId)).thenReturn(exam); + when(examRepository.findById(examId)).thenReturn(Optional.of(exam)); +// when + examService.modifyExam(examDto, examId); +// then + verify(examDtoToExamMapper).mapToExistingExam(examDto, examId); + verify(examRepository).save(exam); + } + + @Test + void shouldThrowWhenExamIdNotExists() { + // given + int examId = 5; + ExamDto examDto = mock(ExamDto.class); + when(examRepository.findById(examId)).thenThrow(new NoSuchElementException("Exam not found")); +// when + RuntimeException exception = assertThrows( + NoSuchElementException.class, + () -> examService.modifyExam(examDto, examId) + ); +// then + verify(examDtoToExamMapper, never()).mapToExistingExam(examDto, examId); + verify(examRepository, never()).save(any()); + assertEquals("Exam not found", exception.getMessage()); + } + + /************************************************************************************/ +//delete exam + @Test + void shouldDeleteExamWhenIdExists() { +// given + int examId = 1; + when(examRepository.findById(examId)).thenReturn(Optional.of(mock(Exam.class))); +// when + examService.deleteExam(examId); +// then + verify(examRepository).deleteById(examId); + } + + @Test + void shouldThrowExceptionWhenExamIdNotExists() { +// given + int examId = 5; + when(examRepository.findById(examId)).thenThrow(new NoSuchElementException("Exam not found")); +// when + RuntimeException exception = assertThrows( + NoSuchElementException.class, + () -> examService.deleteExam(examId) + ); +// then + verify(examRepository, never()).deleteById(examId); + assertEquals("Exam not found", exception.getMessage()); + } + + /************************************************************************************/ +// getExamById + @Test + void getExamById() { +// given + int examId = 1; + when(examRepository.findById(examId)).thenReturn(Optional.of(mock(Exam.class))); +// when + Exam exam = examService.getExamById(examId); +// then + verify(examRepository).findById(examId); + assertNotNull(exam); + } + + @Test + void shouldThrowExceptionWhenExamNotFound() { +// given + int examId = 5; + when(examRepository.findById(examId)).thenThrow(new NoSuchElementException("Exam not found")); +// when + RuntimeException exception = assertThrows( + NoSuchElementException.class, + () -> examService.getExamById(examId) + ); +// then + assertEquals("Exam not found", exception.getMessage()); + } + + // getExamByGroup + @Test + void shouldThrowWithMoreThan4Arguments() { +// given + Set groups = new HashSet<>(); + groups.add("12K2"); + groups.add("13L1"); + groups.add("13A2"); + groups.add("41S2"); + groups.add("11S3"); +// when + RuntimeException exception = assertThrows( + UnsupportedCountOfArgumentsException.class, + () -> examService.getExamByGroup(groups) + ); +// then + assertEquals( + "Invalid count of arguments provided: 5 expected more than: 1 less than: 5", + exception.getMessage() + ); + } + + + @Test + void shouldCallRepositoryWith4Arguments() { +// given + Set groups = new HashSet<>(); + groups.add("12K2"); + groups.add("13L1"); + groups.add("13A2"); + groups.add("41S2"); + Exam mockExam = mock(Exam.class); + Set exams = new HashSet<>(); + exams.add(mockExam); + when(examRepository.findExamsByGroupsIdentifier(any(), any(), any(), any())).thenReturn(exams); +// when + Set result = examService.getExamByGroup(groups); +// then + List> cap = new ArrayList<>(); + for (int i = 0; i < 4; ++i) + cap.add(ArgumentCaptor.forClass(String.class)); + + verify(examRepository).findExamsByGroupsIdentifier( + cap.get(0).capture(), + cap.get(1).capture(), + cap.get(2).capture(), + cap.get(3).capture() + ); + Set passedGroups = cap.stream().map(ArgumentCaptor::getValue).collect(Collectors.toSet()); + + assertEquals(groups, passedGroups); + assertEquals(exams, result); + } + + + @Test + void shouldCallRepositoryWith3Arguments() { +// given + Set groups = new HashSet<>(); + groups.add("12K2"); + groups.add("13L1"); + groups.add("13A2"); + Exam mockExam = mock(Exam.class); + Set exams = new HashSet<>(); + exams.add(mockExam); + when(examRepository.findExamsByGroupsIdentifier(any(), any(), any())).thenReturn(exams); +// when + Set result = examService.getExamByGroup(groups); +// then + List> cap = new ArrayList<>(); + for (int i = 0; i < 3; ++i) + cap.add(ArgumentCaptor.forClass(String.class)); + + verify(examRepository).findExamsByGroupsIdentifier( + cap.get(0).capture(), + cap.get(1).capture(), + cap.get(2).capture() + ); + Set passedGroups = cap.stream().map(ArgumentCaptor::getValue).collect(Collectors.toSet()); + + assertEquals(groups, passedGroups); + assertEquals(exams, result); + } + + @Test + void shouldCallRepositoryWith2Arguments() { +// given + Set groups = new HashSet<>(); + groups.add("12K2"); + groups.add("13L1"); + Exam mockExam = mock(Exam.class); + Set exams = new HashSet<>(); + exams.add(mockExam); + when(examRepository.findExamsByGroupsIdentifier(any(), any())).thenReturn(exams); +// when + Set result = examService.getExamByGroup(groups); +// then + List> cap = new ArrayList<>(); + for (int i = 0; i < 2; ++i) + cap.add(ArgumentCaptor.forClass(String.class)); + + verify(examRepository).findExamsByGroupsIdentifier( + cap.get(0).capture(), + cap.get(1).capture() + ); + Set passedGroups = cap.stream().map(ArgumentCaptor::getValue).collect(Collectors.toSet()); + + assertEquals(groups, passedGroups); + assertEquals(exams, result); + } + + @Test + void shouldCallRepositoryWithSingleArguments() { +// given + Set groups = new HashSet<>(); + groups.add("12K2"); + Exam mockExam = mock(Exam.class); + Set exams = new HashSet<>(); + exams.add(mockExam); + when(examRepository.findExamsByGroupsIdentifier(any())).thenReturn(exams); +// when + Set result = examService.getExamByGroup(groups); +// then + ArgumentCaptor cap = ArgumentCaptor.forClass(String.class); + + verify(examRepository).findExamsByGroupsIdentifier(cap.capture()); + Set passedGroups = new HashSet<>(); + passedGroups.add(cap.getValue()); + + assertEquals(groups, passedGroups); + assertEquals(exams, result); + } + + + @Test + void shouldCallRepositoryWithDuplicatesOf4UniqueArguments() { +// given + Set groups = new HashSet<>(); + groups.add("12K2"); + groups.add("13L1"); + groups.add("13A2"); + groups.add("41S2"); + groups.add("41S2"); + groups.add("13L1"); + Exam mockExam = mock(Exam.class); + Set exams = new HashSet<>(); + exams.add(mockExam); + when(examRepository.findExamsByGroupsIdentifier(any(), any(), any(), any())).thenReturn(exams); +// when + Set result = examService.getExamByGroup(groups); +// then + List> cap = new ArrayList<>(); + for (int i = 0; i < 4; ++i) + cap.add(ArgumentCaptor.forClass(String.class)); + + verify(examRepository).findExamsByGroupsIdentifier( + cap.get(0).capture(), + cap.get(1).capture(), + cap.get(2).capture(), + cap.get(3).capture() + ); + Set passedGroups = cap.stream().map(ArgumentCaptor::getValue).collect(Collectors.toSet()); + + assertEquals(groups, passedGroups); + assertEquals(exams, result); + assertEquals(4, passedGroups.size()); + } + +} \ No newline at end of file diff --git a/src/test/java/org/pkwmtt/examCalendar/dto/ExamDtoTest.java b/src/test/java/org/pkwmtt/examCalendar/dto/ExamDtoTest.java new file mode 100644 index 0000000..b14edc6 --- /dev/null +++ b/src/test/java/org/pkwmtt/examCalendar/dto/ExamDtoTest.java @@ -0,0 +1,150 @@ +package org.pkwmtt.examCalendar.dto; + +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; + +import java.time.LocalDateTime; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ExamDtoTest { + + private final Validator validator; + + public ExamDtoTest() { + this.validator = Validation.buildDefaultValidatorFactory().getValidator(); + } + + @Mock + private ExamDto examDto; + + @Test + void validData() { +// given + ExamDto examDto = new ExamDto( + "Math exam", + "First exam", + LocalDateTime.now().plusDays(1), + "12K2, K04", + "exam" + ); +// when, then + assertTrue(validator.validate(examDto).isEmpty()); + } + + + // empty Strings + @Test + void emptyStringTitle() { + // given + ExamDto examDto = new ExamDto( + "", + "First exam", + LocalDateTime.now().plusDays(1), + "12K2, K04", + "exam" + ); +// when + Set> violations = validator.validate(examDto); +// then + assertFalse(validator.validate(examDto).isEmpty()); + assertTrue(violations.stream().anyMatch(v -> v.getPropertyPath().toString().equals("title"))); + } + + @Test + void emptyExamGroups() { + // given + ExamDto examDto = new ExamDto( + "Math exam", + "First exam", + LocalDateTime.now().plusDays(1), + "", + "exam" + ); +// when + Set> violations = validator.validate(examDto); +// then + assertFalse(validator.validate(examDto).isEmpty()); + assertTrue(violations.stream().anyMatch(v -> v.getPropertyPath().toString().equals("examGroups"))); + } + +// to long Strings + + @Test + void toLongStringTitle() { + // given + ExamDto examDto = new ExamDto( +// 256 characters + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "First exam", + LocalDateTime.now().plusDays(1), + "12K2, K04", + "exam" + ); +// when + Set> violations = validator.validate(examDto); +// then + assertFalse(validator.validate(examDto).isEmpty()); + assertTrue(violations.stream().anyMatch(v -> v.getPropertyPath().toString().equals("title"))); + } + + @Test + void toLongDescription() { + // given + ExamDto examDto = new ExamDto( + "Math exam", +// 256 characters + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + LocalDateTime.now().plusDays(1), + "12K2, K04", + "exam" + ); +// when + Set> violations = validator.validate(examDto); +// then + assertFalse(validator.validate(examDto).isEmpty()); + assertTrue(violations.stream().anyMatch(v -> v.getPropertyPath().toString().equals("description"))); + } + + @Test + void toLongExamGroups() { + // given + ExamDto examDto = new ExamDto( + "Math exam", + "First exam", + LocalDateTime.now().plusDays(1), + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "exam" + ); +// when + Set> violations = validator.validate(examDto); +// then + assertFalse(validator.validate(examDto).isEmpty()); + assertTrue(violations.stream().anyMatch(v -> v.getPropertyPath().toString().equals("examGroups"))); + } + +// date not in future + + @Test + void dateNotInFuture() { + // given + ExamDto examDto = new ExamDto( + "Math exam", + "First exam", + LocalDateTime.now().minusHours(1), + "12K2, K04", + "exam" + ); + // when + Set> violations = validator.validate(examDto); +// then + assertFalse(validator.validate(examDto).isEmpty()); + assertTrue(violations.stream().anyMatch(v -> v.getPropertyPath().toString().equals("date"))); + } + +} \ No newline at end of file diff --git a/src/test/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapperTest.java b/src/test/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapperTest.java new file mode 100644 index 0000000..45081ae --- /dev/null +++ b/src/test/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapperTest.java @@ -0,0 +1,150 @@ +package org.pkwmtt.examCalendar.mapper; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.pkwmtt.examCalendar.dto.ExamDto; +import org.pkwmtt.examCalendar.entity.Exam; +import org.pkwmtt.examCalendar.entity.ExamType; +import org.pkwmtt.examCalendar.repository.ExamTypeRepository; +import org.pkwmtt.exceptions.InvalidGroupIdentifierException; + +import java.time.LocalDateTime; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class ExamDtoToExamMapperTest { + + @Mock + private ExamTypeRepository examTypeRepository; + + @InjectMocks + private ExamDtoToExamMapper examDtoToExamMapper; + + private ExamDto examDto; + private String examTypeName; + +// @BeforeEach +// void setup() { +// +// } + + /**********************************************************************************/ +// mapToNewExam + @Test + void isFieldsMappedProperlyToNewExam() { +// given + String examTypeName = "exam"; + ExamDto examDto = new ExamDto( + "Math exam", + "Linear algebra", + LocalDateTime.now().plusDays(1), + "12K2, 13S1", + examTypeName + ); + when(examTypeRepository.findByName(examTypeName)).thenReturn( + Optional.of(ExamType.builder() + .name(examTypeName) + .build()) + ); +// when + Exam exam = examDtoToExamMapper.mapToNewExam(examDto); +// then +// test fields + assertEquals(examDto.getTitle(), exam.getTitle()); + assertEquals(examDto.getDescription(), exam.getDescription()); + assertEquals(examDto.getDate(), exam.getDate()); + assertEquals(examDto.getExamGroups(), exam.getExamGroups()); + assertEquals(examTypeName, exam.getExamType().getName()); +// test null id + assertNull(exam.getExamId()); + } + + @Test + void ShouldThrowExceptionWhenGroupIdentifierIsLongerThanSixCharactersForNewExam() { + // given + String examTypeName = "exam"; + ExamDto examDto = new ExamDto( + "Math exam", + "Linear algebra", + LocalDateTime.now().plusDays(1), + "12K2, 13S1, Not_Valid_Identifier, 41K1", + examTypeName + ); + when(examTypeRepository.findByName(examTypeName)).thenReturn( + Optional.of(ExamType.builder() + .name(examTypeName) + .build()) + ); +// then + RuntimeException exception = assertThrows( + InvalidGroupIdentifierException.class, + () -> examDtoToExamMapper.mapToNewExam(examDto) + ); + assertEquals("Invalid group identifier: Not_Valid_Identifier", exception.getMessage()); + } + + + /**********************************************************************************/ +// mapToExistingExam + @Test + void isFieldsMappedProperlyToExistingExam() { + // given + int examId = 1; + examTypeName = "exam"; + examDto = new ExamDto( + "Math exam", + "Linear algebra", + LocalDateTime.now().plusDays(1), + "12K2, 13S1", + examTypeName + ); + when(examTypeRepository.findByName(examTypeName)).thenReturn( + Optional.of(ExamType.builder() + .name(examTypeName) + .build()) + ); +// when + Exam exam = examDtoToExamMapper.mapToExistingExam(examDto, examId); +// then +// test fields + assertEquals(examId, exam.getExamId()); + assertEquals(examDto.getTitle(), exam.getTitle()); + assertEquals(examDto.getDescription(), exam.getDescription()); + assertEquals(examDto.getDate(), exam.getDate()); + assertEquals(examDto.getExamGroups(), exam.getExamGroups()); + assertEquals(examTypeName, exam.getExamType().getName()); +// test not null id + assertNotNull(exam.getExamId()); + } + + @Test + void ShouldThrowExceptionWhenGroupIdentifierIsLongerThanSixCharactersForExistingExam() { + // given + int examId = 1; + String examTypeName = "exam"; + ExamDto examDto = new ExamDto( + "Math exam", + "Linear algebra", + LocalDateTime.now().plusDays(1), + "12K2, 13S1, Not_Valid_Identifier, 41K1", + examTypeName + ); + when(examTypeRepository.findByName(examTypeName)).thenReturn( + Optional.of(ExamType.builder() + .name(examTypeName) + .build()) + ); +// then + RuntimeException exception = assertThrows( + InvalidGroupIdentifierException.class, + () -> examDtoToExamMapper.mapToExistingExam(examDto, examId) + ); + assertEquals("Invalid group identifier: Not_Valid_Identifier", exception.getMessage()); + } +} diff --git a/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java b/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java new file mode 100644 index 0000000..90f1614 --- /dev/null +++ b/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java @@ -0,0 +1,182 @@ +package org.pkwmtt.examCalendar.repository; + +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.pkwmtt.examCalendar.entity.Exam; +import org.pkwmtt.examCalendar.entity.ExamType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Slf4j +@DataJpaTest +class ExamRepositoryTest { + + @Autowired + private ExamRepository examRepository; + + @Autowired + private ExamTypeRepository examTypeRepository; + + private ExamType examType; + + @BeforeEach + void setup(){ + examType = ExamType.builder() + .name("exam") + .build(); + examTypeRepository.save(examType); + } + + /** + * test if method find specific count of exams when 1 or 0 group identifiers match + */ + @Test + void testSingleIdentifierMatch() { +// given + Exam exam1 = Exam.builder() + .title("Exam 1") + .description("Exam 1") + .date(LocalDateTime.now().plusDays(1)) + .examGroups("12K2, K03") + .examType(examType) + .build(); + examRepository.save(exam1); + Exam exam2 = Exam.builder() + .title("Exam 2") + .description("Exam 2") + .date(LocalDateTime.now().plusDays(1)) + .examGroups("12K3, K03, S02") + .examType(examType) + .build(); + examRepository.save(exam2); + Exam exam3 = Exam.builder() + .title("Exam 3") + .description("Exam 3") + .date(LocalDateTime.now().plusDays(1)) + .examGroups("13K1, K05, L05") + .examType(examType) + .build(); + examRepository.save(exam3); + Exam exam4 = Exam.builder() + .title("Exam 4") + .description("Exam 4") + .date(LocalDateTime.now().plusDays(1)) + .examGroups("41K1, L04, P03, I01") + .examType(examType) + .build(); + examRepository.save(exam4); + Exam exam5 = Exam.builder() + .title("Exam 5") + .description("Exam 5") + .date(LocalDateTime.now().plusDays(1)) + .examGroups("11A1, G03, H01, P02") + .examType(examType) + .build(); + examRepository.save(exam5); + + String generalGroup = "12K2"; + String kGroup = "K05"; + String lGroup = "L04"; + String pGroup = "P02"; + +// when + Set exams = examRepository.findExamsByGroupsIdentifier(generalGroup, kGroup, lGroup, pGroup); + List examsTitles = exams.stream().map(Exam::getTitle).toList(); +// then + assertEquals(4, exams.size()); + assertTrue(examsTitles.contains("Exam 1")); + assertTrue(examsTitles.contains("Exam 3")); + assertTrue(examsTitles.contains("Exam 4")); + assertTrue(examsTitles.contains("Exam 5")); + } + + /** + * test if method don't duplicate exams when more than 1 identifier match + */ + @Test + void testMultipleIdentifierMatch() { +// given + Exam exam1 = Exam.builder() + .title("Exam 1") + .description("Exam 1") + .date(LocalDateTime.now().plusDays(1)) + .examGroups("12K2, K01, L04, P03, I01") + .examType(examType) + .build(); + examRepository.save(exam1); + Exam exam2 = Exam.builder() + .title("Exam 2") + .description("Exam 2") + .date(LocalDateTime.now().plusDays(1)) + .examGroups("12K2, K05, L04, P02") + .examType(examType) + .build(); + examRepository.save(exam2); + Exam exam3 = Exam.builder() + .title("Exam 3") + .description("Exam 3") + .date(LocalDateTime.now().plusDays(1)) + .examGroups("12K2, K05, L04, P02, I05") + .examType(examType) + .build(); + examRepository.save(exam3); + + String generalGroup = "12K2"; + String kGroup = "K05"; + String lGroup = "L04"; + String pGroup = "P02"; + +// when + Set exams = examRepository.findExamsByGroupsIdentifier(generalGroup, kGroup, lGroup, pGroup); + List examsTitles = exams.stream().map(Exam::getTitle).toList(); + +// then + assertEquals(3, exams.size()); + assertTrue(examsTitles.contains("Exam 1")); + assertTrue(examsTitles.contains("Exam 2")); + assertTrue(examsTitles.contains("Exam 3")); + } + + /** + * test if method return empty set identifiers don't match + */ + @Test + void testNothingMatch() { +// given + Exam exam1 = Exam.builder() + .title("Exam 1") + .description("Exam 1") + .date(LocalDateTime.now().plusDays(1)) + .examGroups("12K2, K01,") + .examType(examType) + .build(); + examRepository.save(exam1); + Exam exam2 = Exam.builder() + .title("Exam 2") + .description("Exam 2") + .date(LocalDateTime.now().plusDays(1)) + .examGroups("12K3, L05") + .examType(examType) + .build(); + examRepository.save(exam2); + + String generalGroup = "14K3"; + String kGroup = "K05"; + String lGroup = "L02"; + String pGroup = "P02"; + +// when + Set exams = examRepository.findExamsByGroupsIdentifier(generalGroup, kGroup, lGroup, pGroup); + +// then + assertTrue(exams.isEmpty()); + } +} \ No newline at end of file diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties new file mode 100644 index 0000000..bb5b179 --- /dev/null +++ b/src/test/resources/application.properties @@ -0,0 +1,13 @@ +#casue issue by some reason +#spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false +#spring.datasource.username=sa +#spring.datasource.password=sa + +spring.jpa.show-sql=true +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +spring.datasource.driver-class-name=org.h2.Driver +spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false +spring.jpa.hibernate.ddl-auto=none +spring.datasource.hikari.initialization-fail-timeout=0 + + diff --git a/src/test/resources/schema.sql b/src/test/resources/schema.sql new file mode 100644 index 0000000..760aaf7 --- /dev/null +++ b/src/test/resources/schema.sql @@ -0,0 +1,60 @@ +DROP TABLE IF EXISTS exams; +DROP TABLE IF EXISTS exam_type; +DROP TABLE IF EXISTS general_group; +DROP TABLE IF EXISTS groups; +DROP TABLE IF EXISTS otp_codes; +DROP TABLE IF EXISTS users; + +CREATE TABLE exam_type +( + exam_type_id INT PRIMARY KEY AUTO_INCREMENT, + name VARCHAR(255) +); + +CREATE TABLE general_group +( + general_group_id INT PRIMARY KEY AUTO_INCREMENT, + name VARCHAR(255) +); + +CREATE TABLE exams +( + exam_id INT PRIMARY KEY AUTO_INCREMENT, + title VARCHAR(255), + description VARCHAR(255), + date TIMESTAMP(6), + "groups" VARCHAR(255), + exam_type_id INT NOT NULL, + FOREIGN KEY (exam_type_id) REFERENCES exam_type (exam_type_id) +); + +CREATE TABLE groups +( + group_id INT PRIMARY KEY AUTO_INCREMENT, + letter CHAR(1) NOT NULL, + group_count INT NOT NULL, + general_group_id INT NOT NULL, + name VARCHAR(255), + FOREIGN KEY (general_group_id) REFERENCES general_group (general_group_id) +); + +CREATE TABLE users +( + user_id INT PRIMARY KEY AUTO_INCREMENT, + general_group_id INT NOT NULL, + email VARCHAR(254) NOT NULL, + is_active BOOLEAN NOT NULL, + role VARCHAR(20) NOT NULL, -- enum zamieniony na VARCHAR + FOREIGN KEY (general_group_id) REFERENCES general_group (general_group_id) +); + +CREATE TABLE otp_codes +( + otp_code_id INT PRIMARY KEY AUTO_INCREMENT, + code VARCHAR(255), + expire TIMESTAMP NOT NULL, + used BOOLEAN NOT NULL, + user_id INT NOT NULL, + timestamp TIMESTAMP(6), + FOREIGN KEY (user_id) REFERENCES users (user_id) +); \ No newline at end of file From 097c19921d3486ef701a18090e9555864c7893e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Florczak?= <84631301+florczaq@users.noreply.github.com> Date: Mon, 18 Aug 2025 22:04:42 +0200 Subject: [PATCH 032/116] Revert "Resolve conficlts" This reverts commit 2eb3b96efbde8806ef06a9a81ec7b9a5b31f5e9f. --- pom.xml | 365 ++++++----- src/main/java/org/pkwmtt/entity/Exam.java | 38 ++ src/main/java/org/pkwmtt/entity/ExamType.java | 18 + .../java/org/pkwmtt/entity/GeneralGroup.java | 17 + src/main/java/org/pkwmtt/entity/OTPCode.java | 25 + .../java/org/pkwmtt/entity/StudentGroup.java | 23 + src/main/java/org/pkwmtt/entity/User.java | 29 + .../pkwmtt/examCalendar/ExamController.java | 90 --- .../examCalendar/ExamControllerAdvice.java | 48 -- .../org/pkwmtt/examCalendar/ExamService.java | 86 --- .../org/pkwmtt/examCalendar/dto/ExamDto.java | 33 - .../org/pkwmtt/examCalendar/entity/Exam.java | 48 -- .../pkwmtt/examCalendar/entity/ExamType.java | 21 - .../examCalendar/entity/GeneralGroup.java | 26 - .../org/pkwmtt/examCalendar/entity/Group.java | 27 - .../pkwmtt/examCalendar/entity/OTPCode.java | 31 - .../org/pkwmtt/examCalendar/entity/User.java | 33 - .../mapper/ExamDtoToExamMapper.java | 49 -- .../repository/ExamRepository.java | 74 --- .../repository/ExamTypeRepository.java | 10 - .../repository/GroupRepository.java | 7 - .../ExamTypeNotExistsException.java | 7 - .../InvalidGroupIdentifierException.java | 7 - .../NoSuchElementWithProvidedIdException.java | 7 - .../UnsupportedCountOfArgumentsException.java | 8 - .../org/pkwmtt/repository/ExamRepository.java | 7 + .../pkwmtt/repository/ExamTypeRepository.java | 7 + .../repository/GeneralGroupRepository.java | 4 +- .../pkwmtt/repository/GroupRepository.java | 7 + .../repository/OTPCodeRepository.java | 4 +- .../repository/UserRepository.java | 4 +- .../examCalendar/ExamControllerTest.java | 620 ------------------ .../pkwmtt/examCalendar/ExamServiceTest.java | 331 ---------- .../pkwmtt/examCalendar/dto/ExamDtoTest.java | 150 ----- .../mapper/ExamDtoToExamMapperTest.java | 150 ----- .../repository/ExamRepositoryTest.java | 182 ----- src/test/resources/application.properties | 13 - src/test/resources/schema.sql | 60 -- 38 files changed, 367 insertions(+), 2299 deletions(-) create mode 100644 src/main/java/org/pkwmtt/entity/Exam.java create mode 100644 src/main/java/org/pkwmtt/entity/ExamType.java create mode 100644 src/main/java/org/pkwmtt/entity/GeneralGroup.java create mode 100644 src/main/java/org/pkwmtt/entity/OTPCode.java create mode 100644 src/main/java/org/pkwmtt/entity/StudentGroup.java create mode 100644 src/main/java/org/pkwmtt/entity/User.java delete mode 100644 src/main/java/org/pkwmtt/examCalendar/ExamController.java delete mode 100644 src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java delete mode 100644 src/main/java/org/pkwmtt/examCalendar/ExamService.java delete mode 100644 src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java delete mode 100644 src/main/java/org/pkwmtt/examCalendar/entity/Exam.java delete mode 100644 src/main/java/org/pkwmtt/examCalendar/entity/ExamType.java delete mode 100644 src/main/java/org/pkwmtt/examCalendar/entity/GeneralGroup.java delete mode 100644 src/main/java/org/pkwmtt/examCalendar/entity/Group.java delete mode 100644 src/main/java/org/pkwmtt/examCalendar/entity/OTPCode.java delete mode 100644 src/main/java/org/pkwmtt/examCalendar/entity/User.java delete mode 100644 src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java delete mode 100644 src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java delete mode 100644 src/main/java/org/pkwmtt/examCalendar/repository/ExamTypeRepository.java delete mode 100644 src/main/java/org/pkwmtt/examCalendar/repository/GroupRepository.java delete mode 100644 src/main/java/org/pkwmtt/exceptions/ExamTypeNotExistsException.java delete mode 100644 src/main/java/org/pkwmtt/exceptions/InvalidGroupIdentifierException.java delete mode 100644 src/main/java/org/pkwmtt/exceptions/NoSuchElementWithProvidedIdException.java delete mode 100644 src/main/java/org/pkwmtt/exceptions/UnsupportedCountOfArgumentsException.java create mode 100644 src/main/java/org/pkwmtt/repository/ExamRepository.java create mode 100644 src/main/java/org/pkwmtt/repository/ExamTypeRepository.java rename src/main/java/org/pkwmtt/{examCalendar => }/repository/GeneralGroupRepository.java (61%) create mode 100644 src/main/java/org/pkwmtt/repository/GroupRepository.java rename src/main/java/org/pkwmtt/{examCalendar => }/repository/OTPCodeRepository.java (60%) rename src/main/java/org/pkwmtt/{examCalendar => }/repository/UserRepository.java (60%) delete mode 100644 src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java delete mode 100644 src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java delete mode 100644 src/test/java/org/pkwmtt/examCalendar/dto/ExamDtoTest.java delete mode 100644 src/test/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapperTest.java delete mode 100644 src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java delete mode 100644 src/test/resources/application.properties delete mode 100644 src/test/resources/schema.sql diff --git a/pom.xml b/pom.xml index 1e54dfb..1ab2d6a 100644 --- a/pom.xml +++ b/pom.xml @@ -1,188 +1,203 @@ - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 3.5.3 - - - org.pkwmtt - PKWMTT-backend - 0.0.1-SNAPSHOT - PKWMTT-backend - PKWMTT-backend - - - - - - - - - - - - - - - 21 - - - - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-devtools - runtime - true - - - org.springframework.boot - spring-boot-starter-test - test - - - org.springframework.boot - spring-boot-starter-data-jpa - - - - - org.projectlombok - lombok - true - + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.5.3 + + + org.pkwmtt + PKWMTT-backend + 0.1.0-ALPHA + PKWMTT-backend + PKWM App (Server) – timetable, exam calendar and ECTS calculator for students of Mechanical Engineering + @ Cracow University of Technology + + https://github.com/PKTTTeam/PKWMTT-backend + + + MIT + + + + + Mikołaj Florczak + https://github.com/florczaq + PKWM Mobile App Team + https://github.com/PKTTTeam + + + + + + + + + + + + 21 + + - - - - - - - + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-data-jpa + - - - org.springframework.boot - spring-boot-starter-security - - - org.springframework.security - spring-security-test - test - - - - - junit - junit - 4.13.1 - - - - - - - - - org.mockito - mockito-core - 5.18.0 - + + + org.projectlombok + lombok + true + - - - com.h2database - h2 - test - + + + com.h2database + h2 + runtime + - - - org.jsoup - jsoup - 1.15.3 - + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.security + spring-security-test + test + + + + junit + junit + 4.13.1 + + + org.mockito + mockito-all + 1.10.19 + + + org.mockito + mockito-core + 5.18.0 + - - - com.github.ben-manes.caffeine - caffeine - 3.1.8 - - - org.springframework.boot - spring-boot-starter-cache - + + + org.jsoup + jsoup + 1.15.3 + - - - org.springdoc - springdoc-openapi-starter-webmvc-ui - 2.6.0 - - - com.mysql - mysql-connector-j - runtime - + + + com.github.ben-manes.caffeine + caffeine + 3.1.8 + + + org.springframework.boot + spring-boot-starter-cache + - - - com.mysql - mysql-connector-j - - - org.springframework.boot - spring-boot-starter-actuator - - - org.springframework.boot - spring-boot-starter-validation - + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.8.9 + - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - - org.projectlombok - lombok - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - org.projectlombok - lombok - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - -javaagent:${settings.localRepository}/org/mockito/mockito-core/5.18.0/mockito-core-5.18.0.jar - - + + + com.mysql + mysql-connector-j + + + org.springframework.boot + spring-boot-starter-actuator + + + + ch.qos.logback + logback-classic + 1.5.13 + + + org.wiremock.integrations + wiremock-spring-boot + 3.10.0 + + + + org.springframework.boot + spring-boot-starter-mail + + + + io.github.cdimascio + dotenv-java + 3.0.0 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.projectlombok + lombok + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + -javaagent:${settings.localRepository}/org/mockito/mockito-core/5.18.0/mockito-core-5.18.0.jar + + + - - + + diff --git a/src/main/java/org/pkwmtt/entity/Exam.java b/src/main/java/org/pkwmtt/entity/Exam.java new file mode 100644 index 0000000..6cda359 --- /dev/null +++ b/src/main/java/org/pkwmtt/entity/Exam.java @@ -0,0 +1,38 @@ +package org.pkwmtt.entity; + +import jakarta.persistence.*; +import lombok.Data; +import java.time.LocalDateTime; +import java.util.HashSet; +import java.util.Set; + +@Entity +@Table(name = "`exams`") +@Data +public class Exam { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "exam_id") + private Integer examId; + + @Column(nullable = false) + private String title; + + private String description; + + @Column(name = "`exam_date`", nullable = false) + private LocalDateTime examDate; + + @ManyToOne + @JoinColumn(name = "exam_type_id", nullable = false) + private ExamType examType; + + @ManyToMany + @JoinTable( + name="exams_groups", + joinColumns = @JoinColumn(name = "exam_id"), + inverseJoinColumns = @JoinColumn(name = "group_id") + ) + private Set groups = new HashSet<>();; + +} diff --git a/src/main/java/org/pkwmtt/entity/ExamType.java b/src/main/java/org/pkwmtt/entity/ExamType.java new file mode 100644 index 0000000..fd971ef --- /dev/null +++ b/src/main/java/org/pkwmtt/entity/ExamType.java @@ -0,0 +1,18 @@ +package org.pkwmtt.entity; + +import jakarta.persistence.*; +import lombok.Data; + +@Entity +@Data +@Table(name = "`exam_type`") +public class ExamType { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "exam_type_id", nullable = false) + private Integer examTypeId; + + @Column(nullable = false) + private String name; +} diff --git a/src/main/java/org/pkwmtt/entity/GeneralGroup.java b/src/main/java/org/pkwmtt/entity/GeneralGroup.java new file mode 100644 index 0000000..eb00158 --- /dev/null +++ b/src/main/java/org/pkwmtt/entity/GeneralGroup.java @@ -0,0 +1,17 @@ +package org.pkwmtt.entity; + +import jakarta.persistence.*; +import lombok.Data; + +@Entity +@Data +@Table(name = "`general_group`") +public class GeneralGroup { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "general_group_id") + private Integer generalGroupId; + + @Column(nullable = false) + private String name; +} diff --git a/src/main/java/org/pkwmtt/entity/OTPCode.java b/src/main/java/org/pkwmtt/entity/OTPCode.java new file mode 100644 index 0000000..a202b42 --- /dev/null +++ b/src/main/java/org/pkwmtt/entity/OTPCode.java @@ -0,0 +1,25 @@ +package org.pkwmtt.entity; + +import jakarta.persistence.*; +import lombok.Data; +import java.time.LocalDateTime; + +@Entity +@Data +@Table(name = "otp_codes") +public class OTPCode { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "otp_code_id") + private Integer otpCodeId; + + @Column(nullable = false) + private String code; + + @Column(nullable = false) + private LocalDateTime expire; + + @OneToOne + @JoinColumn(name = "`general_group_id`", nullable = false) + private GeneralGroup generalGroup; +} diff --git a/src/main/java/org/pkwmtt/entity/StudentGroup.java b/src/main/java/org/pkwmtt/entity/StudentGroup.java new file mode 100644 index 0000000..5c0f10c --- /dev/null +++ b/src/main/java/org/pkwmtt/entity/StudentGroup.java @@ -0,0 +1,23 @@ +package org.pkwmtt.entity; + +import jakarta.persistence.*; +import lombok.Data; + +import java.util.HashSet; +import java.util.Set; + +@Entity +@Data +@Table(name = "`groups`") +public class StudentGroup { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "group_id") + private Integer groupId; + + @Column(nullable = false) + private String name; + + @ManyToMany(mappedBy = "groups") + private Set exams = new HashSet<>(); +} diff --git a/src/main/java/org/pkwmtt/entity/User.java b/src/main/java/org/pkwmtt/entity/User.java new file mode 100644 index 0000000..81a03c8 --- /dev/null +++ b/src/main/java/org/pkwmtt/entity/User.java @@ -0,0 +1,29 @@ +package org.pkwmtt.entity; + +import jakarta.persistence.*; +import lombok.Data; +import org.pkwmtt.enums.Role; + +@Entity +@Data +@Table(name = "`users`") +public class User { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_id") + private Integer userId; + + @OneToOne + @JoinColumn(name = "general_group_id", nullable = false) + private GeneralGroup generalGroup; + + @Column(nullable = false) + private String email; + + @Column(name = "is_active", nullable = false) + private boolean isActive; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private Role role; +} diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamController.java b/src/main/java/org/pkwmtt/examCalendar/ExamController.java deleted file mode 100644 index 7c69cff..0000000 --- a/src/main/java/org/pkwmtt/examCalendar/ExamController.java +++ /dev/null @@ -1,90 +0,0 @@ -package org.pkwmtt.examCalendar; - -import jakarta.validation.Valid; -import jakarta.validation.constraints.Positive; -import lombok.RequiredArgsConstructor; -import org.pkwmtt.examCalendar.dto.ExamDto; -import org.pkwmtt.examCalendar.entity.Exam; -import org.pkwmtt.examCalendar.entity.ExamType; -import org.springframework.http.ResponseEntity; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.servlet.support.ServletUriComponentsBuilder; - -import java.net.URI; -import java.util.List; -import java.util.Set; - -@Validated -@RequiredArgsConstructor -@RequestMapping("/pkwmtt/api/v1/exams") -@RestController -public class ExamController { - - private final ExamService examService; - - /** - * @param examDto details of exam - * @return 201 created with URI to GET method which returns created resource - */ - @PostMapping("") - public ResponseEntity addExam(@RequestBody @Valid ExamDto examDto) { - int id = examService.addExam(examDto); - URI uri = ServletUriComponentsBuilder - .fromCurrentRequest() - .path("/{id}") - .buildAndExpand(id) - .toUri(); - return ResponseEntity.created(uri).build(); -// TODO: test not null validation in controller - } - - /** - * @param id of exam or test - * @param examDto new details of exam or test - * @return 204 no content - */ - @PutMapping("/{id}") - public ResponseEntity modifyExam(@PathVariable @Positive int id, @RequestBody @Valid ExamDto examDto) { - examService.modifyExam(examDto, id); - return ResponseEntity.noContent().build(); - } - - /** - * @param id of exam or test - * @return 204 no content - */ - @DeleteMapping("/{id}") - public ResponseEntity deleteExam(@PathVariable int id) { - examService.deleteExam(id); - return ResponseEntity.noContent().build(); - } - - /** - * @param id of exam or test - * @return 200 ok with single exam or test details - */ - @GetMapping("/{id}") - public ResponseEntity getExam(@PathVariable int id) { - return ResponseEntity.ok(examService.getExamById(id)); - } - - /** - * @param groups set of groups - * @return 200 ok with list of exams for specific group - */ - @GetMapping("/by-groups") - public ResponseEntity> getExams(@RequestParam Set groups){ - return ResponseEntity.ok(examService.getExamByGroup(groups)); - } - - /** - * @return 200 ok with list of available exam types - */ -// should be moved to new controller? - @GetMapping("/exam-types") - public ResponseEntity> getExamTypes(){ - return ResponseEntity.ok(examService.getExamTypes()); - } - -} \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java b/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java deleted file mode 100644 index 845be7a..0000000 --- a/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.pkwmtt.examCalendar; - -import jakarta.validation.ConstraintViolation; -import jakarta.validation.ConstraintViolationException; -import org.pkwmtt.exceptions.ErrorResponseDTO; -import org.pkwmtt.exceptions.ExamTypeNotExistsException; -import org.pkwmtt.exceptions.NoSuchElementWithProvidedIdException; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.MethodArgumentNotValidException; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.RestControllerAdvice; - -import java.util.stream.Collectors; - -@RestControllerAdvice -public class ExamControllerAdvice { - -// TODO: handle or remove UnsupportedCountOfArgumentsException - - @ExceptionHandler(NoSuchElementWithProvidedIdException.class) - public ResponseEntity handleNoSuchElementWithProvidedIdException(NoSuchElementWithProvidedIdException e) { - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponseDTO(e.getMessage())); - } - - @ExceptionHandler(ExamTypeNotExistsException.class) - public ResponseEntity handleExamTypeNotExistsException(ExamTypeNotExistsException e) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorResponseDTO(e.getMessage())); - } - - @ExceptionHandler(MethodArgumentNotValidException.class) - public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { - String message = e.getBindingResult().getFieldErrors().stream() - .map(field -> field.getField() + " : " + field.getDefaultMessage()) - .collect(Collectors.joining(", ")); - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorResponseDTO(message)); - } - - @ExceptionHandler(ConstraintViolationException.class) - public ResponseEntity handleConstraintViolationException(ConstraintViolationException e) { - String message = e.getConstraintViolations().stream() - .map(field -> field.getPropertyPath() + " : " + field.getMessage()) - .collect(Collectors.joining(", ")); - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorResponseDTO(message)); - } - - -} diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamService.java b/src/main/java/org/pkwmtt/examCalendar/ExamService.java deleted file mode 100644 index 8da2396..0000000 --- a/src/main/java/org/pkwmtt/examCalendar/ExamService.java +++ /dev/null @@ -1,86 +0,0 @@ -package org.pkwmtt.examCalendar; - -import jakarta.transaction.Transactional; -import lombok.RequiredArgsConstructor; -import org.pkwmtt.examCalendar.dto.ExamDto; -import org.pkwmtt.examCalendar.entity.Exam; -import org.pkwmtt.examCalendar.entity.ExamType; -import org.pkwmtt.examCalendar.mapper.ExamDtoToExamMapper; -import org.pkwmtt.examCalendar.repository.ExamRepository; -import org.pkwmtt.examCalendar.repository.ExamTypeRepository; -import org.pkwmtt.exceptions.NoSuchElementWithProvidedIdException; -import org.pkwmtt.exceptions.UnsupportedCountOfArgumentsException; -import org.springframework.stereotype.Service; - -import java.util.*; - -@Service -@RequiredArgsConstructor -@Transactional -public class ExamService { - - private final ExamRepository examRepository; - private final ExamDtoToExamMapper examMapper; - private final ExamTypeRepository examTypeRepository; - - /** - * @param examDto details of exam - * @return id of exam added to database - */ - public int addExam(ExamDto examDto) { - return examRepository.save(examMapper.mapToNewExam(examDto)).getExamId(); - } - - /** - * @param examDto new details of exam that overwrite old ones - * @param id of exam that need to be modified - */ - public void modifyExam(ExamDto examDto, int id) { - examRepository.findById(id).orElseThrow(() -> new NoSuchElementWithProvidedIdException(id)); - examRepository.save(examMapper.mapToExistingExam(examDto, id)); - } - - /** - * @param id of exam - */ - public void deleteExam(int id) { - examRepository.findById(id).orElseThrow(() -> new NoSuchElementWithProvidedIdException(id)); - examRepository.deleteById(id); - } - - /** - * @param id of exam - * @return exam - */ - public Exam getExamById(int id) { - return examRepository.findById(id).orElseThrow(() -> new NoSuchElementWithProvidedIdException(id)); - } - - /** - * @param groups set od groups (max 4) - * @return set of exams for specific groups - */ - public Set getExamByGroup(Set groups) { - if (groups.size() > 4 || groups.isEmpty()) - throw new UnsupportedCountOfArgumentsException(1, 5, groups.size()); - List groupList = new ArrayList<>(groups); - return switch (groupList.size()) { - case 4 -> examRepository.findExamsByGroupsIdentifier( - groupList.get(0), groupList.get(1), groupList.get(2), groupList.get(3)); - case 3 -> examRepository.findExamsByGroupsIdentifier( - groupList.get(0), groupList.get(1), groupList.get(2)); - case 2 -> examRepository.findExamsByGroupsIdentifier( - groupList.get(0), groupList.get(1)); - case 1 -> examRepository.findExamsByGroupsIdentifier( - groupList.get(0)); - default -> Set.of(); - }; - } - - /** - * @return list of examTypes - */ - public List getExamTypes() { - return examTypeRepository.findAll(); - } -} diff --git a/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java b/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java deleted file mode 100644 index f1cfdfb..0000000 --- a/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.pkwmtt.examCalendar.dto; - -import jakarta.validation.constraints.Future; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -import java.time.LocalDateTime; - -@Getter -@RequiredArgsConstructor -public class ExamDto { - - @NotBlank - @Size(max = 255, message = "max size of field is 255") - private final String title; - - @Size(max = 255, message = "max size of field is 255") - private final String description; - - @Future(message = "Date must be in the future") - @NotNull - private final LocalDateTime date; - - @NotBlank - @Size(max = 255, message = "max size of field is 255") - private final String examGroups; - - @NotNull - private final String examType; -} diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java b/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java deleted file mode 100644 index 89fe16d..0000000 --- a/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.pkwmtt.examCalendar.entity; - -import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.pkwmtt.exceptions.InvalidGroupIdentifierException; - -import java.time.LocalDateTime; -import java.util.Arrays; - -@Entity -@Getter -@Builder(builderClassName = "Builder", buildMethodName = "build") -@RequiredArgsConstructor -@Table(name = "exams") -@AllArgsConstructor -public class Exam { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Integer examId; - - private String title; - - private String description; - - private LocalDateTime date; - - @Column(name = "`groups`") - private String examGroups; - - @ManyToOne - @JoinColumn(name = "exam_type_id") - private ExamType examType; - - @SuppressWarnings("unused") - public static class Builder { - public Exam build() { - // max length of group identifier is 6 - Arrays.stream(examGroups.split(", ")).forEach(group -> { - if(group.length() > 6) - throw new InvalidGroupIdentifierException(group); - }); - return new Exam(examId, title, description, date, examGroups, examType); - } - } -} diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/ExamType.java b/src/main/java/org/pkwmtt/examCalendar/entity/ExamType.java deleted file mode 100644 index 6c5355f..0000000 --- a/src/main/java/org/pkwmtt/examCalendar/entity/ExamType.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.pkwmtt.examCalendar.entity; - -import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Entity -@Getter -@Builder -@AllArgsConstructor -@RequiredArgsConstructor -@Table(name = "exam_type") -public class ExamType { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Integer exam_type_id; - - private String name; -} \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/GeneralGroup.java b/src/main/java/org/pkwmtt/examCalendar/entity/GeneralGroup.java deleted file mode 100644 index 3eb27af..0000000 --- a/src/main/java/org/pkwmtt/examCalendar/entity/GeneralGroup.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.pkwmtt.examCalendar.entity; - -import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import java.util.Set; - -@Entity -@Getter -@Builder -@AllArgsConstructor -@NoArgsConstructor -@Table(name = "`general_group`") -public class GeneralGroup { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Integer general_group_id; - - private String name; - - @OneToMany(mappedBy = "general_group") - private Set groups; -} diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/Group.java b/src/main/java/org/pkwmtt/examCalendar/entity/Group.java deleted file mode 100644 index 050a5cc..0000000 --- a/src/main/java/org/pkwmtt/examCalendar/entity/Group.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.pkwmtt.examCalendar.entity; - -import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Entity -@Getter -@Builder -@AllArgsConstructor -@NoArgsConstructor -@Table(name = "`groups`") -public class Group { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Integer group_id; - - private String name; - - private int group_count; - - @ManyToOne - @JoinColumn(name = "general_group_id") - private GeneralGroup general_group; -} diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/OTPCode.java b/src/main/java/org/pkwmtt/examCalendar/entity/OTPCode.java deleted file mode 100644 index 47d3a9a..0000000 --- a/src/main/java/org/pkwmtt/examCalendar/entity/OTPCode.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.pkwmtt.examCalendar.entity; - -import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import java.time.LocalDateTime; - -@Entity -@Getter -@Builder -@AllArgsConstructor -@NoArgsConstructor -@Table(name = "otp_codes") -public class OTPCode { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Integer otp_code_id; - - private String code; - - private LocalDateTime timestamp; - - private boolean used; - - @OneToOne - @JoinColumn(name = "user_id", unique = true) - private User user; -} diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/User.java b/src/main/java/org/pkwmtt/examCalendar/entity/User.java deleted file mode 100644 index cb90f87..0000000 --- a/src/main/java/org/pkwmtt/examCalendar/entity/User.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.pkwmtt.examCalendar.entity; - -import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.pkwmtt.enums.Role; - -@Entity -@Getter -@Builder -@AllArgsConstructor -@NoArgsConstructor -@Table(name = "`users`") -public class User { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Integer user_id; - - @ManyToOne - @JoinColumn(name = "general_group_id") - private GeneralGroup general_group; - - private String email; - - private boolean is_active; - - private Role role; - - @OneToOne(mappedBy = "user") - private OTPCode otp_code; -} diff --git a/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java deleted file mode 100644 index 343f70e..0000000 --- a/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.pkwmtt.examCalendar.mapper; - -import lombok.RequiredArgsConstructor; -import org.pkwmtt.examCalendar.dto.ExamDto; -import org.pkwmtt.examCalendar.entity.Exam; -import org.pkwmtt.examCalendar.repository.ExamTypeRepository; -import org.pkwmtt.exceptions.ExamTypeNotExistsException; -import org.springframework.stereotype.Component; - -/** - * maps ExamDto to Exam entity. Couldn't be utility class, because needs ExamTypeRepository to validate exam types - */ -@Component -@RequiredArgsConstructor -public class ExamDtoToExamMapper { - private final ExamTypeRepository examTypeRepository; - - /** - * @param examDto examDto object received from request - * @return Exam entity WITHOUT examId which should be assigned by database - * Also contains examType field converted from String do ExamType - */ - public Exam mapToNewExam(ExamDto examDto) { - return Exam.builder() - .title(examDto.getTitle()) - .description(examDto.getDescription()) - .date(examDto.getDate()) - .examGroups(examDto.getExamGroups()) - .examType(examTypeRepository.findByName(examDto.getExamType()).orElseThrow(() -> new ExamTypeNotExistsException(examDto.getExamType()))) - .build(); - } - - /** - * @param examDto examDto object received from request - * @param id of Exam that need to be modified - * @return Exam entity WITH examId that allow to update entity in database instead of creating new one - * Also contains examType field converted from String do ExamType - */ - public Exam mapToExistingExam(ExamDto examDto, int id) { - return Exam.builder() - .examId(id) - .title(examDto.getTitle()) - .description(examDto.getDescription()) - .date(examDto.getDate()) - .examGroups(examDto.getExamGroups()) - .examType(examTypeRepository.findByName(examDto.getExamType()).orElseThrow(() -> new ExamTypeNotExistsException(examDto.getExamType()))) - .build(); - } -} diff --git a/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java deleted file mode 100644 index e6d3454..0000000 --- a/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java +++ /dev/null @@ -1,74 +0,0 @@ -package org.pkwmtt.examCalendar.repository; - -import org.pkwmtt.examCalendar.entity.Exam; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; - -import java.util.Set; - -public interface ExamRepository extends JpaRepository { - - /** - * fetch all data using one query - * @param group1 group identifier - * @param group2 group identifier - * @param group3 group identifier - * @param group4 group identifier - * @return set of Exams for specific groups - */ - @Query("SELECT e FROM Exam e JOIN FETCH e.examType WHERE " + - "e.examGroups LIKE CONCAT('%', :g1, '%') OR " + - "e.examGroups LIKE CONCAT('%', :g2, '%') OR " + - "e.examGroups LIKE CONCAT('%', :g3, '%') OR " + - "e.examGroups LIKE CONCAT('%', :g4, '%') ") - Set findExamsByGroupsIdentifier( - @Param("g1") String group1, - @Param("g2") String group2, - @Param("g3") String group3, - @Param("g4") String group4 - ); - - /** - * fetch all data using one query - * @param group1 group identifier - * @param group2 group identifier - * @param group3 group identifier - * @return set of Exams for specific groups - */ - @Query("SELECT e FROM Exam e JOIN FETCH e.examType WHERE " + - "e.examGroups LIKE CONCAT('%', :g1, '%') OR " + - "e.examGroups LIKE CONCAT('%', :g2, '%') OR " + - "e.examGroups LIKE CONCAT('%', :g3, '%') ") - Set findExamsByGroupsIdentifier( - @Param("g1") String group1, - @Param("g2") String group2, - @Param("g3") String group3 - ); - - /** - * fetch all data using one query - * @param group1 group identifier - * @param group2 group identifier - * @return set of Exams for specific groups - */ - @Query("SELECT e FROM Exam e JOIN FETCH e.examType WHERE " + - "e.examGroups LIKE CONCAT('%', :g1, '%') OR " + - "e.examGroups LIKE CONCAT('%', :g2, '%')" ) - Set findExamsByGroupsIdentifier( - @Param("g1") String group1, - @Param("g2") String group2 - ); - - /** - * fetch all data using one query - * @param group group identifier - * @return set of Exams for specific group - */ - @Query("SELECT e FROM Exam e JOIN FETCH e.examType WHERE " + - "e.examGroups LIKE CONCAT('%', :gg, '%')") - Set findExamsByGroupsIdentifier( - @Param("gg") String group - ); - -} \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/examCalendar/repository/ExamTypeRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/ExamTypeRepository.java deleted file mode 100644 index c14d733..0000000 --- a/src/main/java/org/pkwmtt/examCalendar/repository/ExamTypeRepository.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.pkwmtt.examCalendar.repository; - -import org.pkwmtt.examCalendar.entity.ExamType; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.Optional; - -public interface ExamTypeRepository extends JpaRepository { - Optional findByName(String name); -} \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/examCalendar/repository/GroupRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/GroupRepository.java deleted file mode 100644 index 98bb7a3..0000000 --- a/src/main/java/org/pkwmtt/examCalendar/repository/GroupRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.pkwmtt.examCalendar.repository; - -import org.pkwmtt.examCalendar.entity.Group; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface GroupRepository extends JpaRepository { -} \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/exceptions/ExamTypeNotExistsException.java b/src/main/java/org/pkwmtt/exceptions/ExamTypeNotExistsException.java deleted file mode 100644 index 5e8171a..0000000 --- a/src/main/java/org/pkwmtt/exceptions/ExamTypeNotExistsException.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.pkwmtt.exceptions; - -public class ExamTypeNotExistsException extends RuntimeException { - public ExamTypeNotExistsException(String examType) { - super("Invalid exam type " + examType); - } -} diff --git a/src/main/java/org/pkwmtt/exceptions/InvalidGroupIdentifierException.java b/src/main/java/org/pkwmtt/exceptions/InvalidGroupIdentifierException.java deleted file mode 100644 index 4faadac..0000000 --- a/src/main/java/org/pkwmtt/exceptions/InvalidGroupIdentifierException.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.pkwmtt.exceptions; - -public class InvalidGroupIdentifierException extends RuntimeException { - public InvalidGroupIdentifierException(String groupIdentifier) { - super("Invalid group identifier: " + groupIdentifier); - } -} diff --git a/src/main/java/org/pkwmtt/exceptions/NoSuchElementWithProvidedIdException.java b/src/main/java/org/pkwmtt/exceptions/NoSuchElementWithProvidedIdException.java deleted file mode 100644 index e17eead..0000000 --- a/src/main/java/org/pkwmtt/exceptions/NoSuchElementWithProvidedIdException.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.pkwmtt.exceptions; - -public class NoSuchElementWithProvidedIdException extends RuntimeException{ - public NoSuchElementWithProvidedIdException(int id) { - super("No such element with id: " + id); - } -} diff --git a/src/main/java/org/pkwmtt/exceptions/UnsupportedCountOfArgumentsException.java b/src/main/java/org/pkwmtt/exceptions/UnsupportedCountOfArgumentsException.java deleted file mode 100644 index 709978a..0000000 --- a/src/main/java/org/pkwmtt/exceptions/UnsupportedCountOfArgumentsException.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.pkwmtt.exceptions; - -public class UnsupportedCountOfArgumentsException extends RuntimeException { - public UnsupportedCountOfArgumentsException(int expectedMin, int expectedMax, int provided) { - super("Invalid count of arguments provided: " + provided + - " expected more than: " + expectedMin + " less than: " + expectedMax); - } -} diff --git a/src/main/java/org/pkwmtt/repository/ExamRepository.java b/src/main/java/org/pkwmtt/repository/ExamRepository.java new file mode 100644 index 0000000..2faafaa --- /dev/null +++ b/src/main/java/org/pkwmtt/repository/ExamRepository.java @@ -0,0 +1,7 @@ +package org.pkwmtt.repository; + +import org.pkwmtt.entity.Exam; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ExamRepository extends JpaRepository { +} \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/repository/ExamTypeRepository.java b/src/main/java/org/pkwmtt/repository/ExamTypeRepository.java new file mode 100644 index 0000000..1b7d38c --- /dev/null +++ b/src/main/java/org/pkwmtt/repository/ExamTypeRepository.java @@ -0,0 +1,7 @@ +package org.pkwmtt.repository; + +import org.pkwmtt.entity.ExamType; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ExamTypeRepository extends JpaRepository { +} \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/examCalendar/repository/GeneralGroupRepository.java b/src/main/java/org/pkwmtt/repository/GeneralGroupRepository.java similarity index 61% rename from src/main/java/org/pkwmtt/examCalendar/repository/GeneralGroupRepository.java rename to src/main/java/org/pkwmtt/repository/GeneralGroupRepository.java index 62f4fbb..a4c1c55 100644 --- a/src/main/java/org/pkwmtt/examCalendar/repository/GeneralGroupRepository.java +++ b/src/main/java/org/pkwmtt/repository/GeneralGroupRepository.java @@ -1,6 +1,6 @@ -package org.pkwmtt.examCalendar.repository; +package org.pkwmtt.repository; -import org.pkwmtt.examCalendar.entity.GeneralGroup; +import org.pkwmtt.entity.GeneralGroup; import org.springframework.data.jpa.repository.JpaRepository; public interface GeneralGroupRepository extends JpaRepository { diff --git a/src/main/java/org/pkwmtt/repository/GroupRepository.java b/src/main/java/org/pkwmtt/repository/GroupRepository.java new file mode 100644 index 0000000..b2396a9 --- /dev/null +++ b/src/main/java/org/pkwmtt/repository/GroupRepository.java @@ -0,0 +1,7 @@ +package org.pkwmtt.repository; + +import org.pkwmtt.entity.StudentGroup; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface GroupRepository extends JpaRepository { +} \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/examCalendar/repository/OTPCodeRepository.java b/src/main/java/org/pkwmtt/repository/OTPCodeRepository.java similarity index 60% rename from src/main/java/org/pkwmtt/examCalendar/repository/OTPCodeRepository.java rename to src/main/java/org/pkwmtt/repository/OTPCodeRepository.java index 848b4d4..4f79485 100644 --- a/src/main/java/org/pkwmtt/examCalendar/repository/OTPCodeRepository.java +++ b/src/main/java/org/pkwmtt/repository/OTPCodeRepository.java @@ -1,6 +1,6 @@ -package org.pkwmtt.examCalendar.repository; +package org.pkwmtt.repository; -import org.pkwmtt.examCalendar.entity.OTPCode; +import org.pkwmtt.entity.OTPCode; import org.springframework.data.jpa.repository.JpaRepository; public interface OTPCodeRepository extends JpaRepository { diff --git a/src/main/java/org/pkwmtt/examCalendar/repository/UserRepository.java b/src/main/java/org/pkwmtt/repository/UserRepository.java similarity index 60% rename from src/main/java/org/pkwmtt/examCalendar/repository/UserRepository.java rename to src/main/java/org/pkwmtt/repository/UserRepository.java index acdf767..71ccd75 100644 --- a/src/main/java/org/pkwmtt/examCalendar/repository/UserRepository.java +++ b/src/main/java/org/pkwmtt/repository/UserRepository.java @@ -1,6 +1,6 @@ -package org.pkwmtt.examCalendar.repository; +package org.pkwmtt.repository; -import org.pkwmtt.examCalendar.entity.User; +import org.pkwmtt.entity.User; import org.springframework.data.jpa.repository.JpaRepository; public interface UserRepository extends JpaRepository { diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java deleted file mode 100644 index 5f7f6ae..0000000 --- a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java +++ /dev/null @@ -1,620 +0,0 @@ -package org.pkwmtt.examCalendar; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.pkwmtt.examCalendar.dto.ExamDto; -import org.pkwmtt.examCalendar.entity.Exam; -import org.pkwmtt.examCalendar.entity.ExamType; -import org.pkwmtt.examCalendar.repository.ExamRepository; -import org.pkwmtt.examCalendar.repository.ExamTypeRepository; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.ResultMatcher; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; - -import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; -import java.util.HashMap; -import java.util.Map; - -import static org.hamcrest.Matchers.containsString; -import static org.junit.jupiter.api.Assertions.*; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * integration tests of ExamCalendar - */ -@SpringBootTest -@AutoConfigureMockMvc -class ExamControllerTest { - - @Autowired - private MockMvc mockMvc; - - @Autowired - private ExamTypeRepository examTypeRepository; - - @Autowired - private ExamRepository examRepository; - - @Autowired - private ObjectMapper mapper; - - @BeforeEach - void setupBeforeEach() { - examRepository.deleteAll(); - examTypeRepository.deleteAll(); - } - - // - - /** - * check if addExam endpoint create new exam with correct URI and correct data - */ - @Test - void addExamWithCorrectData() throws Exception { -// given - createExampleExamType("Project"); - ExamDto examDtoRequest = createExampleExamDto("Project"); - String json = mapper.writeValueAsString(examDtoRequest); - - MvcResult result = mockMvc.perform(MockMvcRequestBuilders - .post("/pkwmtt/api/v1/exams") - .contentType("application/json") - .content(json) - ).andDo(print()) - .andExpect(status().isCreated()) - .andExpect(header().string("Location", containsString("/pkwmtt/api/v1/exams/"))) - .andReturn(); - - String location = result.getResponse().getHeader("Location"); - @SuppressWarnings("DataFlowIssue") - int id = Integer.parseInt(location.substring(location.lastIndexOf("/") + 1)); - - Exam examResponse = examRepository.findById(id).orElseThrow(); - - assertEquals(examDtoRequest.getTitle(), examResponse.getTitle()); - assertEquals(examDtoRequest.getDescription(), examResponse.getDescription()); -// compare dates with minutes level precision - assertEquals( - examDtoRequest.getDate().truncatedTo(ChronoUnit.MINUTES), - examResponse.getDate().truncatedTo(ChronoUnit.MINUTES) - ); - assertEquals(examDtoRequest.getExamGroups(), examResponse.getExamGroups()); - assertEquals(examDtoRequest.getExamType(), examResponse.getExamType().getName()); - } - - @Test - void addExamWithBlankExamTitle() throws Exception { -// given - createExampleExamType("Project"); - Map requestData = new HashMap<>(); -// no exam title - requestData.put("description", "first exam"); - requestData.put("date", LocalDateTime.now().plusDays(1).toString()); - requestData.put("examGroups", "12K2, L04"); - requestData.put("examType", "Project"); - -// when - MvcResult result = assertPostRequest(status().isBadRequest(), requestData); - -// then - assertResponseMessage("title : must not be blank", result); - } - - @Test - void addExamWithBlankExamDescription() throws Exception { -// given - createExampleExamType("Project"); - Map requestData = new HashMap<>(); - requestData.put("title", "Math exam"); -// no exam description - requestData.put("date", LocalDateTime.now().plusDays(1).toString()); - requestData.put("examGroups", "12K2, L04"); - requestData.put("examType", "Project"); - -// when - MvcResult result = assertPostRequest(status().isCreated(), requestData); - - String location = result.getResponse().getHeader("Location"); - @SuppressWarnings("DataFlowIssue") - int id = Integer.parseInt(location.substring(location.lastIndexOf("/") + 1)); - - Exam examResponse = examRepository.findById(id).orElseThrow(); - assertNull(examResponse.getDescription()); - } - - @Test - void addExamWithBlankDate() throws Exception { -// given - createExampleExamType("Project"); - Map requestData = new HashMap<>(); - requestData.put("title", "Math exam"); - requestData.put("description", "first exam"); -// no date - requestData.put("examGroups", "12K2, L04"); - requestData.put("examType", "Project"); - -// when - MvcResult result = assertPostRequest(status().isBadRequest(), requestData); - -// then - assertResponseMessage("date : must not be null", result); - } - - @Test - void addExamWithBlankExamGroups() throws Exception { -// given - createExampleExamType("Project"); - Map requestData = new HashMap<>(); - requestData.put("title", "Math exam"); - requestData.put("description", "first exam"); - requestData.put("date", LocalDateTime.now().plusDays(1).toString()); -// no examGroups - requestData.put("examType", "Project"); - -// when - MvcResult result = assertPostRequest(status().isBadRequest(), requestData); - -// then - assertResponseMessage("examGroups : must not be blank", result); - } - - @Test - void addExamWithNullExamTypes() throws Exception { -// given - createExampleExamType("Project"); - Map requestData = new HashMap<>(); - requestData.put("title", "Math exam"); - requestData.put("description", "first exam"); - requestData.put("date", LocalDateTime.now().plusDays(1).toString()); - requestData.put("examGroups", "12K2, L04"); -// no examType - -// when - MvcResult result = assertPostRequest(status().isBadRequest(), requestData); - -// then - assertResponseMessage("examType : must not be null", result); - } - - @Test - void addExamWithNotFutureDate() throws Exception { -// given - createExampleExamType("Project"); - Map requestData = new HashMap<>(); - requestData.put("title", "Math exam"); - requestData.put("description", "first exam"); - requestData.put("date", LocalDateTime.now().minusDays(1).toString()); - requestData.put("examGroups", "12K2, L04"); - requestData.put("examType", "Project"); - -// when - MvcResult result = assertPostRequest(status().isBadRequest(), requestData); - -// then - assertResponseMessage("date : Date must be in the future", result); - } - - @Test - void addExamWithEmptyStringExamTitle() throws Exception { -// given - createExampleExamType("Project"); - Map requestData = new HashMap<>(); - requestData.put("title", ""); - requestData.put("description", "first exam"); - requestData.put("date", LocalDateTime.now().plusDays(1).toString()); - requestData.put("examGroups", "12K2, L04"); - requestData.put("examType", "Project"); - -// when - MvcResult result = assertPostRequest(status().isBadRequest(), requestData); - -// then - assertResponseMessage("title : must not be blank", result); - } - - @Test - void addExamWithEmptyStringExamGroups() throws Exception { -// given - createExampleExamType("Project"); - Map requestData = new HashMap<>(); - requestData.put("title", "Math exam"); - requestData.put("description", "first exam"); - requestData.put("date", LocalDateTime.now().plusDays(1).toString()); - requestData.put("examGroups", ""); - requestData.put("examType", "Project"); - -// when - MvcResult result = assertPostRequest(status().isBadRequest(), requestData); - -// then - assertResponseMessage("examGroups : must not be blank", result); - } - - @Test - void addExamWithTooLongExamTitle() throws Exception { -// given - createExampleExamType("Project"); - Map requestData = new HashMap<>(); - requestData.put("title", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); - requestData.put("description", "first exam"); - requestData.put("date", LocalDateTime.now().plusDays(1).toString()); - requestData.put("examGroups", "12K2, L04"); - requestData.put("examType", "Project"); - -// when - MvcResult result = assertPostRequest(status().isBadRequest(), requestData); - -// then - assertResponseMessage("title : max size of field is 255", result); - } - - @Test - void addExamWithTooLongDescription() throws Exception { -// given - createExampleExamType("Project"); - Map requestData = new HashMap<>(); - requestData.put("title", "Math exam"); - requestData.put("description", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); - requestData.put("date", LocalDateTime.now().plusDays(1).toString()); - requestData.put("examGroups", "12K2, L04"); - requestData.put("examType", "Project"); - -// when - MvcResult result = assertPostRequest(status().isBadRequest(), requestData); - -// then - assertResponseMessage("description : max size of field is 255", result); - } - - @Test - void addExamWithTooLongExamGroups() throws Exception { -// given - createExampleExamType("Project"); - Map requestData = new HashMap<>(); - requestData.put("title", "Math exam"); - requestData.put("description", "first exam"); - requestData.put("date", LocalDateTime.now().plusDays(1).toString()); - requestData.put("examGroups", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); - requestData.put("examType", "Project"); - -// when - MvcResult result = assertPostRequest(status().isBadRequest(), requestData); - -// then - assertResponseMessage("examGroups : max size of field is 255", result); - } - - @Test - void addExamWithNonExistingExamType() throws Exception { -// given - createExampleExamType("Project"); - Map requestData = new HashMap<>(); - requestData.put("title", "Math exam"); - requestData.put("description", "first exam"); - requestData.put("date", LocalDateTime.now().plusDays(1).toString()); - requestData.put("examGroups", "12K2, L04"); - requestData.put("examType", "NonExistingExamType"); - -// when - MvcResult result = assertPostRequest(status().isBadRequest(), requestData); - -// then - assertResponseMessage("Invalid exam type NonExistingExamType", result); - } - - - // - - // - @Test - void modifyExamWithCorrectData() throws Exception { -// given - ExamType examType = createExampleExamType("Exam"); - Exam exam = createExampleExam(examType); - int id = examRepository.save(exam).getExamId(); - ExamDto examDto = createExampleExamDto(examType.getName()); - -// when - assertPutRequest(status().isNoContent(), examDto, id); - -// then - Exam responseExam = examRepository.findById(id).orElseThrow(); - assertEquals("Math exam", responseExam.getTitle()); - assertEquals("first exam", responseExam.getDescription()); - assertEquals( - LocalDateTime.now().plusDays(1).truncatedTo(ChronoUnit.MINUTES), - responseExam.getDate().truncatedTo(ChronoUnit.MINUTES) - ); - assertEquals("12K2, L04", responseExam.getExamGroups()); - } - - @Test - void modifyExamWithIncorrectExamId() throws Exception { -// given - ExamType examType = createExampleExamType("Exam"); - Exam exam = createExampleExam(examType); - int id = examRepository.save(exam).getExamId(); - ExamDto examDto = createExampleExamDto(examType.getName()); - - int invalidId = Integer.MAX_VALUE - 10; - assertNotEquals(invalidId, id); -// when - MvcResult result = assertPutRequest(status().isNotFound(), examDto, invalidId); - -// then - assertResponseMessage("No such element with id: " + (invalidId), result); - - } -// - - // - @Test - void deleteExamWithCorrectArguments() throws Exception { -// given - ExamType examType = createExampleExamType("Exam"); - Exam exam = createExampleExam(examType); - int id = examRepository.save(exam).getExamId(); - -// when - assertDeleteRequest(status().isNoContent(), id); - -// then - assertTrue(examRepository.findById(id).isEmpty()); - } - - @Test - void deleteNonExistingExam() throws Exception { -// given - ExamType examType = createExampleExamType("Exam"); - Exam exam = createExampleExam(examType); - int id = examRepository.save(exam).getExamId(); - int invalidId = Integer.MAX_VALUE - 10; - assertNotEquals(invalidId, id); - -// when - MvcResult result = assertDeleteRequest(status().isNotFound(), invalidId); - -// then - assertTrue(examRepository.findById(id).isPresent()); - assertResponseMessage("No such element with id: " + (invalidId), result); - } - - // - - // - - @Test - void getExamByIdWithCorrectId() throws Exception { -// given - ExamType examType = createExampleExamType("Exam"); - Exam exam = createExampleExam(examType); - int id = examRepository.save(exam).getExamId(); - -// when - MvcResult result = assertGetByIdRequest(status().isOk(), id); - JsonNode responseNode = mapper.readTree(result.getResponse().getContentAsString()); - -// then - assertEquals(exam.getTitle(), responseNode.get("title").asText()); - assertEquals(exam.getDescription(), responseNode.get("description").asText()); - assertEquals( - exam.getDate().truncatedTo(ChronoUnit.MINUTES), - LocalDateTime.parse(responseNode.get("date").textValue()).truncatedTo(ChronoUnit.MINUTES) - ); - assertEquals(exam.getExamGroups(), responseNode.get("examGroups").asText()); - assertEquals(mapper.readTree(mapper.writeValueAsString(exam.getExamType())), responseNode.get("examType")); - } - - @Test - void getNonExistingExamById() throws Exception { -// given - ExamType examType = createExampleExamType("Exam"); - Exam exam = createExampleExam(examType); - int id = examRepository.save(exam).getExamId(); - int invalidId = Integer.MAX_VALUE - 10; - assertNotEquals(invalidId, id); - -// when - MvcResult result = assertGetByIdRequest(status().isNotFound(), invalidId); - -// then - assertResponseMessage("No such element with id: " + (invalidId), result); - } - -// - - @Test - void getExams() { -// TODO: test getExamsByGroups after implementing new version - } - - // - - @Test - void getExamTypesWhenExamTypesExists() throws Exception { -// given - ExamType exam = createExampleExamType("Exam"); - ExamType project = createExampleExamType("Project"); - -// when - MvcResult result = assertGetExamTypesRequest(status().isOk()); - JsonNode responseArray = mapper.readTree(result.getResponse().getContentAsString()); - -// then - assertEquals(2, responseArray.size()); - assertTrue(responseArray.valueStream().anyMatch(e -> e.get("name").asText().equals(exam.getName()))); - assertTrue(responseArray.valueStream().anyMatch(e -> e.get("name").asText().equals(project.getName()))); - } - - @Test - void getExamTypesWhenExamTypesNotExists() throws Exception { -// given -// when - MvcResult result = mockMvc.perform(MockMvcRequestBuilders - .get("/pkwmtt/api/v1/exams/exam-types") - .contentType("application/json") - ).andDo(print()) - .andExpect(status().isOk()) - .andReturn(); - JsonNode responseArray = mapper.readTree(result.getResponse().getContentAsString()); - -// then - assertEquals(0, responseArray.size()); - } - - // - - // - - /** - * this method create examType object and add it to repository - * @param name of new examType - * @return created examType object - */ - private ExamType createExampleExamType(String name) { - ExamType examType = ExamType.builder().name(name).build(); - examTypeRepository.save(examType); - return examType; - } - - /** - * this method don't add created Exam to repository, because in that case id of created Exam would be unreachable - * @param type ExamType object which is required argument of Exam - * @return created Exam - */ - private Exam createExampleExam(ExamType type) { - return Exam.builder() - .title("Exam") - .description("Exam description") - .date(LocalDateTime.now().plusDays(1)) - .examGroups("11K1, L01") - .examType(type) - .build(); - } - - /** - * @param examTypeName name of type of exam as String - * @return created ExamDto - */ - private ExamDto createExampleExamDto(String examTypeName) { - return new ExamDto( - "Math exam", - "first exam", - LocalDateTime.now().plusDays(1), - "12K2, L04", - examTypeName - ); - } - - /** - * compare error message form response with expected value - * @param expectedMessage full message that is expected in response - * @param result response generated by mockMvc.perform() or one of assert[httpMethod]Request() - * @throws Exception - */ - private void assertResponseMessage(String expectedMessage, MvcResult result) throws Exception { - JsonNode jsonResponse = mapper.readTree(result.getResponse().getContentAsString()); - assertTrue(jsonResponse.has("message")); - assertEquals(expectedMessage, jsonResponse.get("message").asText()); - } - - /** - * method send POST request to ExamController with content as JSON attached to body and then check if response - * code is the same as expected - * @param expectedStatus status().[http response] (example: status().isCreated() ) - * @param content object that would be mapped to JSON by ObjectMapper and then attached to request - * it could be dto object or Map - * @return MvcResult object which could be used to capture response body - * @throws Exception - */ - private MvcResult assertPostRequest(ResultMatcher expectedStatus, Object content) throws Exception { - return mockMvc.perform(MockMvcRequestBuilders - .post("/pkwmtt/api/v1/exams") - .contentType("application/json") - .content(mapper.writeValueAsString(content)) - ).andDo(print()) - .andExpect(expectedStatus) - .andReturn(); - } - - /** - * method send PUT request to ExamController with content as JSON attached to body and examId as pathID. - * Then check if response code is the same as expected - * @param expectedStatus status().[http response] (example: status().isNoContent() ) - * @param content object that would be mapped to JSON by ObjectMapper and then attached to request - * @param pathId id of resource that would be updated - * @return MvcResult object which could be used to capture response body - * @throws Exception - */ - private MvcResult assertPutRequest(ResultMatcher expectedStatus, Object content, int pathId) throws Exception { - return mockMvc.perform(MockMvcRequestBuilders - .put("/pkwmtt/api/v1/exams/{id}", pathId) - .contentType("application/json") - .content(mapper.writeValueAsString(content)) - ).andDo(print()) - .andExpect(expectedStatus) - .andReturn(); - } - - /** - * method send DELETE request to ExamController with examId as pathID. - * Then check if response code is the same as expected - * @param expectedStatus status().[http response] (example: status().isNoContent() ) - * @param pathId id of resource that would be deleted - * @return MvcResult object which could be used to capture response body - * @throws Exception - */ - private MvcResult assertDeleteRequest(ResultMatcher expectedStatus, int pathId) throws Exception { - return mockMvc.perform(MockMvcRequestBuilders - .delete("/pkwmtt/api/v1/exams/{id}", pathId) - .contentType("application/json") - ).andDo(print()) - .andExpect(expectedStatus) - .andReturn(); - } - - /** - * method send GET request to ExamController at /pkwmtt/api/v1/exams/{id} URI with examId as pathID. - * Then check if response code is the same as expected - * @param expectedStatus status().[http response] (example: status().isOk() ) - * @param pathId id of resource that would be returned - * @return MvcResult object which could be used to capture response body - * @throws Exception - */ - private MvcResult assertGetByIdRequest(ResultMatcher expectedStatus, int pathId) throws Exception { - return mockMvc.perform(MockMvcRequestBuilders - .get("/pkwmtt/api/v1/exams/{id}", pathId) - .contentType("application/json") - ).andDo(print()) - .andExpect(expectedStatus) - .andReturn(); - } - - /** - * method send GET request to ExamController at /pkwmtt/api/v1/exams/exam-types URI. - * Then check if response code is the same as expected - * @param expectedStatus expectedStatus status().[http response] (example: status().isOk() ) - * @return MvcResult object which could be used to capture response body - * @throws Exception - */ - private MvcResult assertGetExamTypesRequest(ResultMatcher expectedStatus) throws Exception { - return mockMvc.perform(MockMvcRequestBuilders - .get("/pkwmtt/api/v1/exams/exam-types") - .contentType("application/json") - ).andDo(print()) - .andExpect(expectedStatus) - .andReturn(); - } - -// - -} \ No newline at end of file diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java deleted file mode 100644 index c86b0ab..0000000 --- a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java +++ /dev/null @@ -1,331 +0,0 @@ -package org.pkwmtt.examCalendar; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.pkwmtt.examCalendar.dto.ExamDto; -import org.pkwmtt.examCalendar.entity.Exam; -import org.pkwmtt.examCalendar.entity.ExamType; -import org.pkwmtt.examCalendar.mapper.ExamDtoToExamMapper; -import org.pkwmtt.examCalendar.repository.ExamRepository; -import org.pkwmtt.exceptions.UnsupportedCountOfArgumentsException; - -import java.lang.reflect.Field; -import java.time.LocalDateTime; -import java.util.*; -import java.util.stream.Collectors; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -class ExamServiceTest { - - @Mock - private ExamRepository examRepository; - - @Mock - private ExamDtoToExamMapper examDtoToExamMapper; - - @InjectMocks - private ExamService examService; - - @Test - void addExam() { -// given - int examId = 1; - ExamDto examDto = new ExamDto( - "Math exam", - "desc", - LocalDateTime.now().plusDays(1), - "12K2, 13L1", - "Exam" - ); - Exam exam = Exam.builder() - .title("Math exam") - .description("desc") - .date(LocalDateTime.now().plusDays(1)) - .examGroups("12K2, 13L1") - .examType(new ExamType(1, "Exam")) - .build(); - when(examDtoToExamMapper.mapToNewExam(examDto)).thenReturn(exam); - -// assign exam id in repository - when(examRepository.save(exam)).thenAnswer(invocation -> { - Exam newExam = invocation.getArgument(0, Exam.class); - Field field = Exam.class.getDeclaredField("examId"); - field.setAccessible(true); - field.set(newExam, examId); - return newExam; - }); -// when - int result = examService.addExam(examDto); -// then - assertEquals(examId, result); - verify(examRepository).save(exam); - verify(examDtoToExamMapper).mapToNewExam(examDto); - } - - /************************************************************************************/ -//modify exam - @Test - void shouldModifyExamWhenIdExists() { - // given - int examId = 1; - ExamDto examDto = mock(ExamDto.class); - Exam exam = mock(Exam.class); - - when(examDtoToExamMapper.mapToExistingExam(examDto, examId)).thenReturn(exam); - when(examRepository.findById(examId)).thenReturn(Optional.of(exam)); -// when - examService.modifyExam(examDto, examId); -// then - verify(examDtoToExamMapper).mapToExistingExam(examDto, examId); - verify(examRepository).save(exam); - } - - @Test - void shouldThrowWhenExamIdNotExists() { - // given - int examId = 5; - ExamDto examDto = mock(ExamDto.class); - when(examRepository.findById(examId)).thenThrow(new NoSuchElementException("Exam not found")); -// when - RuntimeException exception = assertThrows( - NoSuchElementException.class, - () -> examService.modifyExam(examDto, examId) - ); -// then - verify(examDtoToExamMapper, never()).mapToExistingExam(examDto, examId); - verify(examRepository, never()).save(any()); - assertEquals("Exam not found", exception.getMessage()); - } - - /************************************************************************************/ -//delete exam - @Test - void shouldDeleteExamWhenIdExists() { -// given - int examId = 1; - when(examRepository.findById(examId)).thenReturn(Optional.of(mock(Exam.class))); -// when - examService.deleteExam(examId); -// then - verify(examRepository).deleteById(examId); - } - - @Test - void shouldThrowExceptionWhenExamIdNotExists() { -// given - int examId = 5; - when(examRepository.findById(examId)).thenThrow(new NoSuchElementException("Exam not found")); -// when - RuntimeException exception = assertThrows( - NoSuchElementException.class, - () -> examService.deleteExam(examId) - ); -// then - verify(examRepository, never()).deleteById(examId); - assertEquals("Exam not found", exception.getMessage()); - } - - /************************************************************************************/ -// getExamById - @Test - void getExamById() { -// given - int examId = 1; - when(examRepository.findById(examId)).thenReturn(Optional.of(mock(Exam.class))); -// when - Exam exam = examService.getExamById(examId); -// then - verify(examRepository).findById(examId); - assertNotNull(exam); - } - - @Test - void shouldThrowExceptionWhenExamNotFound() { -// given - int examId = 5; - when(examRepository.findById(examId)).thenThrow(new NoSuchElementException("Exam not found")); -// when - RuntimeException exception = assertThrows( - NoSuchElementException.class, - () -> examService.getExamById(examId) - ); -// then - assertEquals("Exam not found", exception.getMessage()); - } - - // getExamByGroup - @Test - void shouldThrowWithMoreThan4Arguments() { -// given - Set groups = new HashSet<>(); - groups.add("12K2"); - groups.add("13L1"); - groups.add("13A2"); - groups.add("41S2"); - groups.add("11S3"); -// when - RuntimeException exception = assertThrows( - UnsupportedCountOfArgumentsException.class, - () -> examService.getExamByGroup(groups) - ); -// then - assertEquals( - "Invalid count of arguments provided: 5 expected more than: 1 less than: 5", - exception.getMessage() - ); - } - - - @Test - void shouldCallRepositoryWith4Arguments() { -// given - Set groups = new HashSet<>(); - groups.add("12K2"); - groups.add("13L1"); - groups.add("13A2"); - groups.add("41S2"); - Exam mockExam = mock(Exam.class); - Set exams = new HashSet<>(); - exams.add(mockExam); - when(examRepository.findExamsByGroupsIdentifier(any(), any(), any(), any())).thenReturn(exams); -// when - Set result = examService.getExamByGroup(groups); -// then - List> cap = new ArrayList<>(); - for (int i = 0; i < 4; ++i) - cap.add(ArgumentCaptor.forClass(String.class)); - - verify(examRepository).findExamsByGroupsIdentifier( - cap.get(0).capture(), - cap.get(1).capture(), - cap.get(2).capture(), - cap.get(3).capture() - ); - Set passedGroups = cap.stream().map(ArgumentCaptor::getValue).collect(Collectors.toSet()); - - assertEquals(groups, passedGroups); - assertEquals(exams, result); - } - - - @Test - void shouldCallRepositoryWith3Arguments() { -// given - Set groups = new HashSet<>(); - groups.add("12K2"); - groups.add("13L1"); - groups.add("13A2"); - Exam mockExam = mock(Exam.class); - Set exams = new HashSet<>(); - exams.add(mockExam); - when(examRepository.findExamsByGroupsIdentifier(any(), any(), any())).thenReturn(exams); -// when - Set result = examService.getExamByGroup(groups); -// then - List> cap = new ArrayList<>(); - for (int i = 0; i < 3; ++i) - cap.add(ArgumentCaptor.forClass(String.class)); - - verify(examRepository).findExamsByGroupsIdentifier( - cap.get(0).capture(), - cap.get(1).capture(), - cap.get(2).capture() - ); - Set passedGroups = cap.stream().map(ArgumentCaptor::getValue).collect(Collectors.toSet()); - - assertEquals(groups, passedGroups); - assertEquals(exams, result); - } - - @Test - void shouldCallRepositoryWith2Arguments() { -// given - Set groups = new HashSet<>(); - groups.add("12K2"); - groups.add("13L1"); - Exam mockExam = mock(Exam.class); - Set exams = new HashSet<>(); - exams.add(mockExam); - when(examRepository.findExamsByGroupsIdentifier(any(), any())).thenReturn(exams); -// when - Set result = examService.getExamByGroup(groups); -// then - List> cap = new ArrayList<>(); - for (int i = 0; i < 2; ++i) - cap.add(ArgumentCaptor.forClass(String.class)); - - verify(examRepository).findExamsByGroupsIdentifier( - cap.get(0).capture(), - cap.get(1).capture() - ); - Set passedGroups = cap.stream().map(ArgumentCaptor::getValue).collect(Collectors.toSet()); - - assertEquals(groups, passedGroups); - assertEquals(exams, result); - } - - @Test - void shouldCallRepositoryWithSingleArguments() { -// given - Set groups = new HashSet<>(); - groups.add("12K2"); - Exam mockExam = mock(Exam.class); - Set exams = new HashSet<>(); - exams.add(mockExam); - when(examRepository.findExamsByGroupsIdentifier(any())).thenReturn(exams); -// when - Set result = examService.getExamByGroup(groups); -// then - ArgumentCaptor cap = ArgumentCaptor.forClass(String.class); - - verify(examRepository).findExamsByGroupsIdentifier(cap.capture()); - Set passedGroups = new HashSet<>(); - passedGroups.add(cap.getValue()); - - assertEquals(groups, passedGroups); - assertEquals(exams, result); - } - - - @Test - void shouldCallRepositoryWithDuplicatesOf4UniqueArguments() { -// given - Set groups = new HashSet<>(); - groups.add("12K2"); - groups.add("13L1"); - groups.add("13A2"); - groups.add("41S2"); - groups.add("41S2"); - groups.add("13L1"); - Exam mockExam = mock(Exam.class); - Set exams = new HashSet<>(); - exams.add(mockExam); - when(examRepository.findExamsByGroupsIdentifier(any(), any(), any(), any())).thenReturn(exams); -// when - Set result = examService.getExamByGroup(groups); -// then - List> cap = new ArrayList<>(); - for (int i = 0; i < 4; ++i) - cap.add(ArgumentCaptor.forClass(String.class)); - - verify(examRepository).findExamsByGroupsIdentifier( - cap.get(0).capture(), - cap.get(1).capture(), - cap.get(2).capture(), - cap.get(3).capture() - ); - Set passedGroups = cap.stream().map(ArgumentCaptor::getValue).collect(Collectors.toSet()); - - assertEquals(groups, passedGroups); - assertEquals(exams, result); - assertEquals(4, passedGroups.size()); - } - -} \ No newline at end of file diff --git a/src/test/java/org/pkwmtt/examCalendar/dto/ExamDtoTest.java b/src/test/java/org/pkwmtt/examCalendar/dto/ExamDtoTest.java deleted file mode 100644 index b14edc6..0000000 --- a/src/test/java/org/pkwmtt/examCalendar/dto/ExamDtoTest.java +++ /dev/null @@ -1,150 +0,0 @@ -package org.pkwmtt.examCalendar.dto; - -import jakarta.validation.ConstraintViolation; -import jakarta.validation.Validation; -import jakarta.validation.Validator; -import org.junit.jupiter.api.Test; -import org.mockito.Mock; - -import java.time.LocalDateTime; -import java.util.Set; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -class ExamDtoTest { - - private final Validator validator; - - public ExamDtoTest() { - this.validator = Validation.buildDefaultValidatorFactory().getValidator(); - } - - @Mock - private ExamDto examDto; - - @Test - void validData() { -// given - ExamDto examDto = new ExamDto( - "Math exam", - "First exam", - LocalDateTime.now().plusDays(1), - "12K2, K04", - "exam" - ); -// when, then - assertTrue(validator.validate(examDto).isEmpty()); - } - - - // empty Strings - @Test - void emptyStringTitle() { - // given - ExamDto examDto = new ExamDto( - "", - "First exam", - LocalDateTime.now().plusDays(1), - "12K2, K04", - "exam" - ); -// when - Set> violations = validator.validate(examDto); -// then - assertFalse(validator.validate(examDto).isEmpty()); - assertTrue(violations.stream().anyMatch(v -> v.getPropertyPath().toString().equals("title"))); - } - - @Test - void emptyExamGroups() { - // given - ExamDto examDto = new ExamDto( - "Math exam", - "First exam", - LocalDateTime.now().plusDays(1), - "", - "exam" - ); -// when - Set> violations = validator.validate(examDto); -// then - assertFalse(validator.validate(examDto).isEmpty()); - assertTrue(violations.stream().anyMatch(v -> v.getPropertyPath().toString().equals("examGroups"))); - } - -// to long Strings - - @Test - void toLongStringTitle() { - // given - ExamDto examDto = new ExamDto( -// 256 characters - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "First exam", - LocalDateTime.now().plusDays(1), - "12K2, K04", - "exam" - ); -// when - Set> violations = validator.validate(examDto); -// then - assertFalse(validator.validate(examDto).isEmpty()); - assertTrue(violations.stream().anyMatch(v -> v.getPropertyPath().toString().equals("title"))); - } - - @Test - void toLongDescription() { - // given - ExamDto examDto = new ExamDto( - "Math exam", -// 256 characters - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - LocalDateTime.now().plusDays(1), - "12K2, K04", - "exam" - ); -// when - Set> violations = validator.validate(examDto); -// then - assertFalse(validator.validate(examDto).isEmpty()); - assertTrue(violations.stream().anyMatch(v -> v.getPropertyPath().toString().equals("description"))); - } - - @Test - void toLongExamGroups() { - // given - ExamDto examDto = new ExamDto( - "Math exam", - "First exam", - LocalDateTime.now().plusDays(1), - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "exam" - ); -// when - Set> violations = validator.validate(examDto); -// then - assertFalse(validator.validate(examDto).isEmpty()); - assertTrue(violations.stream().anyMatch(v -> v.getPropertyPath().toString().equals("examGroups"))); - } - -// date not in future - - @Test - void dateNotInFuture() { - // given - ExamDto examDto = new ExamDto( - "Math exam", - "First exam", - LocalDateTime.now().minusHours(1), - "12K2, K04", - "exam" - ); - // when - Set> violations = validator.validate(examDto); -// then - assertFalse(validator.validate(examDto).isEmpty()); - assertTrue(violations.stream().anyMatch(v -> v.getPropertyPath().toString().equals("date"))); - } - -} \ No newline at end of file diff --git a/src/test/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapperTest.java b/src/test/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapperTest.java deleted file mode 100644 index 45081ae..0000000 --- a/src/test/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapperTest.java +++ /dev/null @@ -1,150 +0,0 @@ -package org.pkwmtt.examCalendar.mapper; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.pkwmtt.examCalendar.dto.ExamDto; -import org.pkwmtt.examCalendar.entity.Exam; -import org.pkwmtt.examCalendar.entity.ExamType; -import org.pkwmtt.examCalendar.repository.ExamTypeRepository; -import org.pkwmtt.exceptions.InvalidGroupIdentifierException; - -import java.time.LocalDateTime; -import java.util.Optional; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class ExamDtoToExamMapperTest { - - @Mock - private ExamTypeRepository examTypeRepository; - - @InjectMocks - private ExamDtoToExamMapper examDtoToExamMapper; - - private ExamDto examDto; - private String examTypeName; - -// @BeforeEach -// void setup() { -// -// } - - /**********************************************************************************/ -// mapToNewExam - @Test - void isFieldsMappedProperlyToNewExam() { -// given - String examTypeName = "exam"; - ExamDto examDto = new ExamDto( - "Math exam", - "Linear algebra", - LocalDateTime.now().plusDays(1), - "12K2, 13S1", - examTypeName - ); - when(examTypeRepository.findByName(examTypeName)).thenReturn( - Optional.of(ExamType.builder() - .name(examTypeName) - .build()) - ); -// when - Exam exam = examDtoToExamMapper.mapToNewExam(examDto); -// then -// test fields - assertEquals(examDto.getTitle(), exam.getTitle()); - assertEquals(examDto.getDescription(), exam.getDescription()); - assertEquals(examDto.getDate(), exam.getDate()); - assertEquals(examDto.getExamGroups(), exam.getExamGroups()); - assertEquals(examTypeName, exam.getExamType().getName()); -// test null id - assertNull(exam.getExamId()); - } - - @Test - void ShouldThrowExceptionWhenGroupIdentifierIsLongerThanSixCharactersForNewExam() { - // given - String examTypeName = "exam"; - ExamDto examDto = new ExamDto( - "Math exam", - "Linear algebra", - LocalDateTime.now().plusDays(1), - "12K2, 13S1, Not_Valid_Identifier, 41K1", - examTypeName - ); - when(examTypeRepository.findByName(examTypeName)).thenReturn( - Optional.of(ExamType.builder() - .name(examTypeName) - .build()) - ); -// then - RuntimeException exception = assertThrows( - InvalidGroupIdentifierException.class, - () -> examDtoToExamMapper.mapToNewExam(examDto) - ); - assertEquals("Invalid group identifier: Not_Valid_Identifier", exception.getMessage()); - } - - - /**********************************************************************************/ -// mapToExistingExam - @Test - void isFieldsMappedProperlyToExistingExam() { - // given - int examId = 1; - examTypeName = "exam"; - examDto = new ExamDto( - "Math exam", - "Linear algebra", - LocalDateTime.now().plusDays(1), - "12K2, 13S1", - examTypeName - ); - when(examTypeRepository.findByName(examTypeName)).thenReturn( - Optional.of(ExamType.builder() - .name(examTypeName) - .build()) - ); -// when - Exam exam = examDtoToExamMapper.mapToExistingExam(examDto, examId); -// then -// test fields - assertEquals(examId, exam.getExamId()); - assertEquals(examDto.getTitle(), exam.getTitle()); - assertEquals(examDto.getDescription(), exam.getDescription()); - assertEquals(examDto.getDate(), exam.getDate()); - assertEquals(examDto.getExamGroups(), exam.getExamGroups()); - assertEquals(examTypeName, exam.getExamType().getName()); -// test not null id - assertNotNull(exam.getExamId()); - } - - @Test - void ShouldThrowExceptionWhenGroupIdentifierIsLongerThanSixCharactersForExistingExam() { - // given - int examId = 1; - String examTypeName = "exam"; - ExamDto examDto = new ExamDto( - "Math exam", - "Linear algebra", - LocalDateTime.now().plusDays(1), - "12K2, 13S1, Not_Valid_Identifier, 41K1", - examTypeName - ); - when(examTypeRepository.findByName(examTypeName)).thenReturn( - Optional.of(ExamType.builder() - .name(examTypeName) - .build()) - ); -// then - RuntimeException exception = assertThrows( - InvalidGroupIdentifierException.class, - () -> examDtoToExamMapper.mapToExistingExam(examDto, examId) - ); - assertEquals("Invalid group identifier: Not_Valid_Identifier", exception.getMessage()); - } -} diff --git a/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java b/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java deleted file mode 100644 index 90f1614..0000000 --- a/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java +++ /dev/null @@ -1,182 +0,0 @@ -package org.pkwmtt.examCalendar.repository; - -import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.pkwmtt.examCalendar.entity.Exam; -import org.pkwmtt.examCalendar.entity.ExamType; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Set; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -@Slf4j -@DataJpaTest -class ExamRepositoryTest { - - @Autowired - private ExamRepository examRepository; - - @Autowired - private ExamTypeRepository examTypeRepository; - - private ExamType examType; - - @BeforeEach - void setup(){ - examType = ExamType.builder() - .name("exam") - .build(); - examTypeRepository.save(examType); - } - - /** - * test if method find specific count of exams when 1 or 0 group identifiers match - */ - @Test - void testSingleIdentifierMatch() { -// given - Exam exam1 = Exam.builder() - .title("Exam 1") - .description("Exam 1") - .date(LocalDateTime.now().plusDays(1)) - .examGroups("12K2, K03") - .examType(examType) - .build(); - examRepository.save(exam1); - Exam exam2 = Exam.builder() - .title("Exam 2") - .description("Exam 2") - .date(LocalDateTime.now().plusDays(1)) - .examGroups("12K3, K03, S02") - .examType(examType) - .build(); - examRepository.save(exam2); - Exam exam3 = Exam.builder() - .title("Exam 3") - .description("Exam 3") - .date(LocalDateTime.now().plusDays(1)) - .examGroups("13K1, K05, L05") - .examType(examType) - .build(); - examRepository.save(exam3); - Exam exam4 = Exam.builder() - .title("Exam 4") - .description("Exam 4") - .date(LocalDateTime.now().plusDays(1)) - .examGroups("41K1, L04, P03, I01") - .examType(examType) - .build(); - examRepository.save(exam4); - Exam exam5 = Exam.builder() - .title("Exam 5") - .description("Exam 5") - .date(LocalDateTime.now().plusDays(1)) - .examGroups("11A1, G03, H01, P02") - .examType(examType) - .build(); - examRepository.save(exam5); - - String generalGroup = "12K2"; - String kGroup = "K05"; - String lGroup = "L04"; - String pGroup = "P02"; - -// when - Set exams = examRepository.findExamsByGroupsIdentifier(generalGroup, kGroup, lGroup, pGroup); - List examsTitles = exams.stream().map(Exam::getTitle).toList(); -// then - assertEquals(4, exams.size()); - assertTrue(examsTitles.contains("Exam 1")); - assertTrue(examsTitles.contains("Exam 3")); - assertTrue(examsTitles.contains("Exam 4")); - assertTrue(examsTitles.contains("Exam 5")); - } - - /** - * test if method don't duplicate exams when more than 1 identifier match - */ - @Test - void testMultipleIdentifierMatch() { -// given - Exam exam1 = Exam.builder() - .title("Exam 1") - .description("Exam 1") - .date(LocalDateTime.now().plusDays(1)) - .examGroups("12K2, K01, L04, P03, I01") - .examType(examType) - .build(); - examRepository.save(exam1); - Exam exam2 = Exam.builder() - .title("Exam 2") - .description("Exam 2") - .date(LocalDateTime.now().plusDays(1)) - .examGroups("12K2, K05, L04, P02") - .examType(examType) - .build(); - examRepository.save(exam2); - Exam exam3 = Exam.builder() - .title("Exam 3") - .description("Exam 3") - .date(LocalDateTime.now().plusDays(1)) - .examGroups("12K2, K05, L04, P02, I05") - .examType(examType) - .build(); - examRepository.save(exam3); - - String generalGroup = "12K2"; - String kGroup = "K05"; - String lGroup = "L04"; - String pGroup = "P02"; - -// when - Set exams = examRepository.findExamsByGroupsIdentifier(generalGroup, kGroup, lGroup, pGroup); - List examsTitles = exams.stream().map(Exam::getTitle).toList(); - -// then - assertEquals(3, exams.size()); - assertTrue(examsTitles.contains("Exam 1")); - assertTrue(examsTitles.contains("Exam 2")); - assertTrue(examsTitles.contains("Exam 3")); - } - - /** - * test if method return empty set identifiers don't match - */ - @Test - void testNothingMatch() { -// given - Exam exam1 = Exam.builder() - .title("Exam 1") - .description("Exam 1") - .date(LocalDateTime.now().plusDays(1)) - .examGroups("12K2, K01,") - .examType(examType) - .build(); - examRepository.save(exam1); - Exam exam2 = Exam.builder() - .title("Exam 2") - .description("Exam 2") - .date(LocalDateTime.now().plusDays(1)) - .examGroups("12K3, L05") - .examType(examType) - .build(); - examRepository.save(exam2); - - String generalGroup = "14K3"; - String kGroup = "K05"; - String lGroup = "L02"; - String pGroup = "P02"; - -// when - Set exams = examRepository.findExamsByGroupsIdentifier(generalGroup, kGroup, lGroup, pGroup); - -// then - assertTrue(exams.isEmpty()); - } -} \ No newline at end of file diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties deleted file mode 100644 index bb5b179..0000000 --- a/src/test/resources/application.properties +++ /dev/null @@ -1,13 +0,0 @@ -#casue issue by some reason -#spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false -#spring.datasource.username=sa -#spring.datasource.password=sa - -spring.jpa.show-sql=true -spring.jpa.database-platform=org.hibernate.dialect.H2Dialect -spring.datasource.driver-class-name=org.h2.Driver -spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false -spring.jpa.hibernate.ddl-auto=none -spring.datasource.hikari.initialization-fail-timeout=0 - - diff --git a/src/test/resources/schema.sql b/src/test/resources/schema.sql deleted file mode 100644 index 760aaf7..0000000 --- a/src/test/resources/schema.sql +++ /dev/null @@ -1,60 +0,0 @@ -DROP TABLE IF EXISTS exams; -DROP TABLE IF EXISTS exam_type; -DROP TABLE IF EXISTS general_group; -DROP TABLE IF EXISTS groups; -DROP TABLE IF EXISTS otp_codes; -DROP TABLE IF EXISTS users; - -CREATE TABLE exam_type -( - exam_type_id INT PRIMARY KEY AUTO_INCREMENT, - name VARCHAR(255) -); - -CREATE TABLE general_group -( - general_group_id INT PRIMARY KEY AUTO_INCREMENT, - name VARCHAR(255) -); - -CREATE TABLE exams -( - exam_id INT PRIMARY KEY AUTO_INCREMENT, - title VARCHAR(255), - description VARCHAR(255), - date TIMESTAMP(6), - "groups" VARCHAR(255), - exam_type_id INT NOT NULL, - FOREIGN KEY (exam_type_id) REFERENCES exam_type (exam_type_id) -); - -CREATE TABLE groups -( - group_id INT PRIMARY KEY AUTO_INCREMENT, - letter CHAR(1) NOT NULL, - group_count INT NOT NULL, - general_group_id INT NOT NULL, - name VARCHAR(255), - FOREIGN KEY (general_group_id) REFERENCES general_group (general_group_id) -); - -CREATE TABLE users -( - user_id INT PRIMARY KEY AUTO_INCREMENT, - general_group_id INT NOT NULL, - email VARCHAR(254) NOT NULL, - is_active BOOLEAN NOT NULL, - role VARCHAR(20) NOT NULL, -- enum zamieniony na VARCHAR - FOREIGN KEY (general_group_id) REFERENCES general_group (general_group_id) -); - -CREATE TABLE otp_codes -( - otp_code_id INT PRIMARY KEY AUTO_INCREMENT, - code VARCHAR(255), - expire TIMESTAMP NOT NULL, - used BOOLEAN NOT NULL, - user_id INT NOT NULL, - timestamp TIMESTAMP(6), - FOREIGN KEY (user_id) REFERENCES users (user_id) -); \ No newline at end of file From 999608cf81916b934d659f51d4a7464fe58e81e6 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Tue, 19 Aug 2025 11:59:16 +0200 Subject: [PATCH 033/116] import new database --- init.sql | 204 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 149 insertions(+), 55 deletions(-) diff --git a/init.sql b/init.sql index ee4fcb3..7d91d29 100644 --- a/init.sql +++ b/init.sql @@ -3,7 +3,7 @@ -- https://www.phpmyadmin.net/ -- -- Host: db --- Generation Time: Aug 01, 2025 at 01:55 PM +-- Generation Time: Aug 18, 2025 at 07:00 PM -- Wersja serwera: 9.3.0 -- Wersja PHP: 8.2.27 @@ -32,21 +32,66 @@ USE `pktt`; DROP TABLE IF EXISTS `exams`; CREATE TABLE `exams` ( `exam_id` int NOT NULL, - `title` varchar(255) DEFAULT NULL, + `title` varchar(255) NOT NULL, `description` varchar(255) DEFAULT NULL, - `date` datetime(6) DEFAULT NULL, - `groups` varchar(255) DEFAULT NULL, + `exam_date` datetime NOT NULL, `exam_type_id` int NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +-- +-- Tabela Truncate przed wstawieniem `exams` +-- + +TRUNCATE TABLE `exams`; -- -- Zrzut danych tabeli `exams` -- -INSERT INTO `exams` (`exam_id`, `title`, `description`, `date`, `groups`, `exam_type_id`) VALUES -(1, 'Matematyka Dyskretna', 'Egzamin końcowy z matematyki dyskretnej', '2025-07-30 00:00:00.000000', '12K3,11L1', 2), -(2, 'Programowanie C++', 'Kolokwium z programowania w C++', '2025-08-05 00:00:00.000000', '12K2,13S3', 1), -(3, 'Sieci Komputerowe', 'Projekt zespołowy na sieciach komputerowych', '2025-09-10 00:00:00.000000', '14S4,12K1', 3); +INSERT INTO `exams` (`exam_id`, `title`, `description`, `exam_date`, `exam_type_id`) VALUES +(1, 'Kolokwium z matematyki', 'Pierwsze kolokwium obejmujące rozdziały 1–3', '2025-10-01 10:00:00', 1), +(2, 'Egzamin końcowy z programowania', 'Egzamin pisemny i praktyczny', '2025-01-20 09:00:00', 2), +(3, 'Projekt z baz danych', 'Oddanie projektu grupowego', '2025-06-15 23:59:00', 3), +(4, 'Kolokwium z fizyki', 'Druga część materiału: mechanika', '2025-11-05 12:00:00', 1), +(5, 'Egzamin końcowy z ekonomii', 'Egzamin pisemny testowy', '2025-02-10 08:30:00', 2), +(6, 'Projekt z systemów operacyjnych', 'Prezentacja projektu semestralnego', '2025-06-25 14:00:00', 3); + +-- -------------------------------------------------------- + +-- +-- Struktura tabeli dla tabeli `exams_groups` +-- + +DROP TABLE IF EXISTS `exams_groups`; +CREATE TABLE `exams_groups` ( + `exam_group_id` int NOT NULL, + `exam_id` int NOT NULL, + `group_id` int NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; + +-- +-- Tabela Truncate przed wstawieniem `exams_groups` +-- + +TRUNCATE TABLE `exams_groups`; +-- +-- Zrzut danych tabeli `exams_groups` +-- + +INSERT INTO `exams_groups` (`exam_group_id`, `exam_id`, `group_id`) VALUES +(7, 1, 9), +(8, 1, 10), +(9, 2, 12), +(10, 2, 13), +(11, 2, 14), +(12, 3, 15), +(13, 3, 16), +(14, 3, 17), +(15, 4, 9), +(16, 4, 10), +(17, 5, 12), +(18, 5, 13), +(19, 6, 15), +(20, 6, 16); -- -------------------------------------------------------- @@ -57,9 +102,14 @@ INSERT INTO `exams` (`exam_id`, `title`, `description`, `date`, `groups`, `exam_ DROP TABLE IF EXISTS `exam_type`; CREATE TABLE `exam_type` ( `exam_type_id` int NOT NULL, - `name` varchar(255) DEFAULT NULL + `name` varchar(255) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +-- +-- Tabela Truncate przed wstawieniem `exam_type` +-- + +TRUNCATE TABLE `exam_type`; -- -- Zrzut danych tabeli `exam_type` -- @@ -78,18 +128,23 @@ INSERT INTO `exam_type` (`exam_type_id`, `name`) VALUES DROP TABLE IF EXISTS `general_group`; CREATE TABLE `general_group` ( `general_group_id` int NOT NULL, - `name` varchar(255) DEFAULT NULL + `name` varchar(255) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +-- +-- Tabela Truncate przed wstawieniem `general_group` +-- + +TRUNCATE TABLE `general_group`; -- -- Zrzut danych tabeli `general_group` -- INSERT INTO `general_group` (`general_group_id`, `name`) VALUES -(11, '1'), -(12, '2'), -(13, '3'), -(14, '4'); +(17, '11A'), +(18, '12E'), +(19, '13K'), +(20, '14M'); -- -------------------------------------------------------- @@ -100,25 +155,28 @@ INSERT INTO `general_group` (`general_group_id`, `name`) VALUES DROP TABLE IF EXISTS `groups`; CREATE TABLE `groups` ( `group_id` int NOT NULL, - `letter` char(1) NOT NULL, - `group_count` int NOT NULL, - `general_group_id` int NOT NULL, - `name` varchar(255) DEFAULT NULL + `name` varchar(255) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +-- +-- Tabela Truncate przed wstawieniem `groups` +-- + +TRUNCATE TABLE `groups`; -- -- Zrzut danych tabeli `groups` -- -INSERT INTO `groups` (`group_id`, `letter`, `group_count`, `general_group_id`, `name`) VALUES -(1, 'K', 1, 11, NULL), -(2, 'K', 2, 12, NULL), -(3, 'L', 1, 11, NULL), -(4, 'L', 2, 12, NULL), -(5, 'S', 3, 13, NULL), -(6, 'S', 4, 14, NULL), -(7, 'K', 3, 12, NULL), -(8, 'L', 4, 14, NULL); +INSERT INTO `groups` (`group_id`, `name`) VALUES +(9, '11A1'), +(10, '11A2'), +(12, '12E1'), +(13, '12E2'), +(14, '12E3'), +(15, '13K1'), +(16, '13K2'), +(17, '13K3'), +(18, '14M1'); -- -------------------------------------------------------- @@ -128,14 +186,27 @@ INSERT INTO `groups` (`group_id`, `letter`, `group_count`, `general_group_id`, ` DROP TABLE IF EXISTS `otp_codes`; CREATE TABLE `otp_codes` ( - `code` varchar(255) DEFAULT NULL, - `expire` timestamp NOT NULL, - `used` tinyint(1) NOT NULL, - `user_id` int NOT NULL, `otp_code_id` int NOT NULL, - `timestamp` datetime(6) DEFAULT NULL + `code` varchar(255) NOT NULL, + `expire` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `general_group_id` int NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +-- +-- Tabela Truncate przed wstawieniem `otp_codes` +-- + +TRUNCATE TABLE `otp_codes`; +-- +-- Zrzut danych tabeli `otp_codes` +-- + +INSERT INTO `otp_codes` (`otp_code_id`, `code`, `expire`, `general_group_id`) VALUES +(1, 'ABC123', '2025-08-18 19:51:40', 17), +(2, 'XYZ789', '2025-08-18 20:51:40', 18), +(3, 'QWE456', '2025-08-18 21:51:40', 19), +(4, 'JKL999', '2025-08-18 22:51:40', 20); + -- -------------------------------------------------------- -- @@ -146,20 +217,25 @@ DROP TABLE IF EXISTS `users`; CREATE TABLE `users` ( `user_id` int NOT NULL, `general_group_id` int NOT NULL, - `email` varchar(254) NOT NULL, - `is_active` tinyint(1) NOT NULL, - `role` enum('ADMIN','REPRESENTATIVE') NOT NULL + `email` varchar(255) NOT NULL, + `is_active` tinyint(1) NOT NULL DEFAULT '1', + `role` enum('ADMIN','REPRESENTATIVE') NOT NULL DEFAULT 'REPRESENTATIVE' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +-- +-- Tabela Truncate przed wstawieniem `users` +-- + +TRUNCATE TABLE `users`; -- -- Zrzut danych tabeli `users` -- INSERT INTO `users` (`user_id`, `general_group_id`, `email`, `is_active`, `role`) VALUES -(1, 12, 'jan.kowalski@example.com', 1, 'ADMIN'), -(2, 11, 'anna.nowak@example.com', 1, 'REPRESENTATIVE'), -(3, 13, 'piotr.zielinski@example.com', 0, 'REPRESENTATIVE'), -(4, 14, 'ewa.wisniewska@example.com', 1, 'ADMIN'); +(1, 17, 'user11a@example.com', 1, 'REPRESENTATIVE'), +(2, 18, 'user12e@example.com', 1, 'REPRESENTATIVE'), +(3, 19, 'user13k@example.com', 1, 'REPRESENTATIVE'), +(4, 20, 'user14m@example.com', 1, 'ADMIN'); -- -- Indeksy dla zrzutów tabel @@ -170,8 +246,15 @@ INSERT INTO `users` (`user_id`, `general_group_id`, `email`, `is_active`, `role` -- ALTER TABLE `exams` ADD PRIMARY KEY (`exam_id`), - ADD KEY `exam_type` (`exam_type_id`), - ADD KEY `exam_type_id` (`exam_type_id`); + ADD KEY `exam_type_id_idx` (`exam_type_id`); + +-- +-- Indeksy dla tabeli `exams_groups` +-- +ALTER TABLE `exams_groups` + ADD PRIMARY KEY (`exam_group_id`), + ADD KEY `exam_id_idx` (`exam_id`), + ADD KEY `group_id_idx` (`group_id`); -- -- Indeksy dla tabeli `exam_type` @@ -189,23 +272,21 @@ ALTER TABLE `general_group` -- Indeksy dla tabeli `groups` -- ALTER TABLE `groups` - ADD PRIMARY KEY (`group_id`), - ADD KEY `general_group` (`general_group_id`), - ADD KEY `general_group_id` (`general_group_id`); + ADD PRIMARY KEY (`group_id`); -- -- Indeksy dla tabeli `otp_codes` -- ALTER TABLE `otp_codes` ADD PRIMARY KEY (`otp_code_id`), - ADD KEY `user_id` (`user_id`); + ADD KEY `general_group_id_idx` (`general_group_id`); -- -- Indeksy dla tabeli `users` -- ALTER TABLE `users` ADD PRIMARY KEY (`user_id`), - ADD KEY `general_group_id` (`general_group_id`); + ADD KEY `general_group_id_idx` (`general_group_id`); -- -- AUTO_INCREMENT dla zrzuconych tabel @@ -215,7 +296,13 @@ ALTER TABLE `users` -- AUTO_INCREMENT dla tabeli `exams` -- ALTER TABLE `exams` - MODIFY `exam_id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=9; + MODIFY `exam_id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=7; + +-- +-- AUTO_INCREMENT dla tabeli `exams_groups` +-- +ALTER TABLE `exams_groups` + MODIFY `exam_group_id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=21; -- -- AUTO_INCREMENT dla tabeli `exam_type` @@ -227,13 +314,19 @@ ALTER TABLE `exam_type` -- AUTO_INCREMENT dla tabeli `general_group` -- ALTER TABLE `general_group` - MODIFY `general_group_id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=15; + MODIFY `general_group_id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=21; -- -- AUTO_INCREMENT dla tabeli `groups` -- ALTER TABLE `groups` - MODIFY `group_id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=9; + MODIFY `group_id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=21; + +-- +-- AUTO_INCREMENT dla tabeli `otp_codes` +-- +ALTER TABLE `otp_codes` + MODIFY `otp_code_id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=5; -- -- AUTO_INCREMENT dla tabeli `users` @@ -249,25 +342,26 @@ ALTER TABLE `users` -- Ograniczenia dla tabeli `exams` -- ALTER TABLE `exams` - ADD CONSTRAINT `exams_ibfk_1` FOREIGN KEY (`exam_type_id`) REFERENCES `exam_type` (`exam_type_id`); + ADD CONSTRAINT `exams_ibfk_1` FOREIGN KEY (`exam_type_id`) REFERENCES `exam_type` (`exam_type_id`) ON DELETE CASCADE; -- --- Ograniczenia dla tabeli `groups` +-- Ograniczenia dla tabeli `exams_groups` -- -ALTER TABLE `groups` - ADD CONSTRAINT `groups_ibfk_1` FOREIGN KEY (`general_group_id`) REFERENCES `general_group` (`general_group_id`); +ALTER TABLE `exams_groups` + ADD CONSTRAINT `exams_groups_ibfk_1` FOREIGN KEY (`exam_id`) REFERENCES `exams` (`exam_id`) ON DELETE CASCADE, + ADD CONSTRAINT `exams_groups_ibfk_2` FOREIGN KEY (`group_id`) REFERENCES `groups` (`group_id`) ON DELETE CASCADE; -- -- Ograniczenia dla tabeli `otp_codes` -- ALTER TABLE `otp_codes` - ADD CONSTRAINT `otp_codes_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`user_id`); + ADD CONSTRAINT `otp_codes_ibfk_1` FOREIGN KEY (`general_group_id`) REFERENCES `general_group` (`general_group_id`) ON DELETE CASCADE; -- -- Ograniczenia dla tabeli `users` -- ALTER TABLE `users` - ADD CONSTRAINT `users_ibfk_1` FOREIGN KEY (`general_group_id`) REFERENCES `general_group` (`general_group_id`); + ADD CONSTRAINT `users_ibfk_1` FOREIGN KEY (`general_group_id`) REFERENCES `general_group` (`general_group_id`) ON DELETE CASCADE; COMMIT; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; From 936578b7f86503804c4451eea2f9e02a83a873c6 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Tue, 19 Aug 2025 16:13:29 +0200 Subject: [PATCH 034/116] adapting classes to new database --- logs/app.log | 217 +++++++++++ .../pkwmtt/examCalendar/ExamController.java | 8 +- .../org/pkwmtt/examCalendar/ExamService.java | 40 +- .../org/pkwmtt/examCalendar/dto/ExamDto.java | 9 +- .../examCalendar/entity/StudentGroup.java | 8 +- .../org/pkwmtt/examCalendar/entity/User.java | 2 +- .../pkwmtt/{ => examCalendar}/enums/Role.java | 2 +- .../{ => examCalendar}/enums/SubjectType.java | 2 +- .../mapper/ExamDtoToExamMapper.java | 4 +- .../repository/ExamRepository.java | 122 +++---- .../org/pkwmtt/timetable/dto/SubjectDTO.java | 2 +- .../parser/TimetableParserService.java | 2 +- .../examCalendar/ExamControllerTest.java | 29 +- .../pkwmtt/examCalendar/ExamServiceTest.java | 345 +++++++++--------- .../pkwmtt/examCalendar/dto/ExamDtoTest.java | 73 ++-- .../mapper/ExamDtoToExamMapperTest.java | 48 ++- .../repository/ExamRepositoryTest.java | 309 ++++++++-------- 17 files changed, 745 insertions(+), 477 deletions(-) rename src/main/java/org/pkwmtt/{ => examCalendar}/enums/Role.java (56%) rename src/main/java/org/pkwmtt/{ => examCalendar}/enums/SubjectType.java (77%) diff --git a/logs/app.log b/logs/app.log index 60535a3..f965341 100644 --- a/logs/app.log +++ b/logs/app.log @@ -39,3 +39,220 @@ Caused by: org.hibernate.AnnotationException: Collection 'org.pkwmtt.examCalenda at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1873) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1822) ... 18 common frames omitted +2025-08-19 13:49:42 ERROR o.s.boot.SpringApplication - Application run failed +org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'examController' defined in file [D:\Java\PKWMTT\target\classes\org\pkwmtt\examCalendar\ExamController.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'examService' defined in file [D:\Java\PKWMTT\target\classes\org\pkwmtt\examCalendar\ExamService.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'examRepository' defined in org.pkwmtt.examCalendar.repository.ExamRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: Could not create query for public abstract java.util.Set org.pkwmtt.examCalendar.repository.ExamRepository.findExamsByGroupsIdentifier(java.lang.String); Reason: Validation failed for query for method public abstract java.util.Set org.pkwmtt.examCalendar.repository.ExamRepository.findExamsByGroupsIdentifier(java.lang.String) + at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:804) + at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:240) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1395) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1232) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529) + at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) + at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373) + at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) + at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) + at org.springframework.beans.factory.support.DefaultListableBeanFactory.instantiateSingleton(DefaultListableBeanFactory.java:1222) + at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingleton(DefaultListableBeanFactory.java:1188) + at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:1123) + at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:987) + at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:627) + at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) + at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752) + at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439) + at org.springframework.boot.SpringApplication.run(SpringApplication.java:318) + at org.springframework.boot.SpringApplication.run(SpringApplication.java:1361) + at org.springframework.boot.SpringApplication.run(SpringApplication.java:1350) + at org.pkwmtt.PkwmttBackendApplication.main(PkwmttBackendApplication.java:12) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:50) +Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'examService' defined in file [D:\Java\PKWMTT\target\classes\org\pkwmtt\examCalendar\ExamService.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'examRepository' defined in org.pkwmtt.examCalendar.repository.ExamRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: Could not create query for public abstract java.util.Set org.pkwmtt.examCalendar.repository.ExamRepository.findExamsByGroupsIdentifier(java.lang.String); Reason: Validation failed for query for method public abstract java.util.Set org.pkwmtt.examCalendar.repository.ExamRepository.findExamsByGroupsIdentifier(java.lang.String) + at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:804) + at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:240) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1395) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1232) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529) + at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) + at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373) + at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) + at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) + at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1683) + at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1628) + at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:913) + at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791) + ... 24 common frames omitted +Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'examRepository' defined in org.pkwmtt.examCalendar.repository.ExamRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: Could not create query for public abstract java.util.Set org.pkwmtt.examCalendar.repository.ExamRepository.findExamsByGroupsIdentifier(java.lang.String); Reason: Validation failed for query for method public abstract java.util.Set org.pkwmtt.examCalendar.repository.ExamRepository.findExamsByGroupsIdentifier(java.lang.String) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1826) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:607) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529) + at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) + at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373) + at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) + at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) + at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1683) + at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1628) + at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:913) + at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791) + ... 37 common frames omitted +Caused by: org.springframework.data.repository.query.QueryCreationException: Could not create query for public abstract java.util.Set org.pkwmtt.examCalendar.repository.ExamRepository.findExamsByGroupsIdentifier(java.lang.String); Reason: Validation failed for query for method public abstract java.util.Set org.pkwmtt.examCalendar.repository.ExamRepository.findExamsByGroupsIdentifier(java.lang.String) + at org.springframework.data.repository.query.QueryCreationException.create(QueryCreationException.java:101) + at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.lookupQuery(QueryExecutorMethodInterceptor.java:120) + at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.mapMethodsToQuery(QueryExecutorMethodInterceptor.java:104) + at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.lambda$new$0(QueryExecutorMethodInterceptor.java:92) + at java.base/java.util.Optional.map(Optional.java:260) + at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.(QueryExecutorMethodInterceptor.java:92) + at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:434) + at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.lambda$afterPropertiesSet$4(RepositoryFactoryBeanSupport.java:350) + at org.springframework.data.util.Lazy.getNullable(Lazy.java:135) + at org.springframework.data.util.Lazy.get(Lazy.java:113) + at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.afterPropertiesSet(RepositoryFactoryBeanSupport.java:356) + at org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean.afterPropertiesSet(JpaRepositoryFactoryBean.java:132) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1873) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1822) + ... 47 common frames omitted +Caused by: java.lang.IllegalArgumentException: Validation failed for query for method public abstract java.util.Set org.pkwmtt.examCalendar.repository.ExamRepository.findExamsByGroupsIdentifier(java.lang.String) + at org.springframework.data.jpa.repository.query.SimpleJpaQuery.validateQuery(SimpleJpaQuery.java:97) + at org.springframework.data.jpa.repository.query.SimpleJpaQuery.(SimpleJpaQuery.java:67) + at org.springframework.data.jpa.repository.query.JpaQueryFactory.fromMethodWithQueryString(JpaQueryFactory.java:49) + at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$DeclaredQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:174) + at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$CreateIfNotFoundQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:254) + at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$AbstractQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:99) + at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.lookupQuery(QueryExecutorMethodInterceptor.java:116) + ... 59 common frames omitted +Caused by: java.lang.IllegalArgumentException: org.hibernate.query.sqm.UnknownPathException: Could not resolve attribute 'examGroups' of 'org.pkwmtt.examCalendar.entity.Exam' [SELECT e FROM Exam e JOIN FETCH e.examType WHERE e.examGroups LIKE CONCAT('%', :gg, '%')] + at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:143) + at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:167) + at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:173) + at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:886) + at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:796) + at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:143) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:364) + at jdk.proxy3/jdk.proxy3.$Proxy159.createQuery(Unknown Source) + at org.springframework.data.jpa.repository.query.SimpleJpaQuery.validateQuery(SimpleJpaQuery.java:91) + ... 65 common frames omitted +Caused by: org.hibernate.query.sqm.UnknownPathException: Could not resolve attribute 'examGroups' of 'org.pkwmtt.examCalendar.entity.Exam' [SELECT e FROM Exam e JOIN FETCH e.examType WHERE e.examGroups LIKE CONCAT('%', :gg, '%')] + at org.hibernate.query.hql.internal.StandardHqlTranslator.translate(StandardHqlTranslator.java:88) + at org.hibernate.query.internal.QueryInterpretationCacheStandardImpl.createHqlInterpretation(QueryInterpretationCacheStandardImpl.java:145) + at org.hibernate.query.internal.QueryInterpretationCacheStandardImpl.resolveHqlInterpretation(QueryInterpretationCacheStandardImpl.java:132) + at org.hibernate.query.spi.QueryEngine.interpretHql(QueryEngine.java:54) + at org.hibernate.internal.AbstractSharedSessionContract.interpretHql(AbstractSharedSessionContract.java:832) + at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:878) + ... 72 common frames omitted +Caused by: org.hibernate.query.sqm.PathElementException: Could not resolve attribute 'examGroups' of 'org.pkwmtt.examCalendar.entity.Exam' + at org.hibernate.query.sqm.SqmPathSource.getSubPathSource(SqmPathSource.java:95) + at org.hibernate.query.sqm.tree.domain.AbstractSqmPath.get(AbstractSqmPath.java:198) + at org.hibernate.query.sqm.tree.domain.AbstractSqmFrom.resolvePathPart(AbstractSqmFrom.java:198) + at org.hibernate.query.hql.internal.DomainPathPart.resolvePathPart(DomainPathPart.java:42) + at org.hibernate.query.hql.internal.BasicDotIdentifierConsumer.consumeIdentifier(BasicDotIdentifierConsumer.java:92) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitSimplePath(SemanticQueryBuilder.java:5465) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitGeneralPathFragment(SemanticQueryBuilder.java:5298) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitGeneralPathExpression(SemanticQueryBuilder.java:1891) + at org.hibernate.grammars.hql.HqlParser$GeneralPathExpressionContext.accept(HqlParser.java:8268) + at org.antlr.v4.runtime.tree.AbstractParseTreeVisitor.visitChildren(AbstractParseTreeVisitor.java:46) + at org.hibernate.grammars.hql.HqlParserBaseVisitor.visitBarePrimaryExpression(HqlParserBaseVisitor.java:812) + at org.hibernate.grammars.hql.HqlParser$BarePrimaryExpressionContext.accept(HqlParser.java:7726) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitLikePredicate(SemanticQueryBuilder.java:2721) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitLikePredicate(SemanticQueryBuilder.java:277) + at org.hibernate.grammars.hql.HqlParser$LikePredicateContext.accept(HqlParser.java:6535) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitWhereClause(SemanticQueryBuilder.java:2361) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitWhereClause(SemanticQueryBuilder.java:277) + at org.hibernate.grammars.hql.HqlParser$WhereClauseContext.accept(HqlParser.java:6302) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitQuery(SemanticQueryBuilder.java:1259) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitQuerySpecExpression(SemanticQueryBuilder.java:1040) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitQuerySpecExpression(SemanticQueryBuilder.java:277) + at org.hibernate.grammars.hql.HqlParser$QuerySpecExpressionContext.accept(HqlParser.java:2134) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitSimpleQueryGroup(SemanticQueryBuilder.java:1025) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitSimpleQueryGroup(SemanticQueryBuilder.java:277) + at org.hibernate.grammars.hql.HqlParser$SimpleQueryGroupContext.accept(HqlParser.java:2005) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitSelectStatement(SemanticQueryBuilder.java:492) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitStatement(SemanticQueryBuilder.java:451) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.buildSemanticModel(SemanticQueryBuilder.java:324) + at org.hibernate.query.hql.internal.StandardHqlTranslator.translate(StandardHqlTranslator.java:71) + ... 77 common frames omitted +2025-08-19 13:52:44 ERROR o.s.b.d.LoggingFailureAnalysisReporter - + +*************************** +APPLICATION FAILED TO START +*************************** + +Description: + +Parameter 0 of constructor in org.pkwmtt.examCalendar.ExamController required a bean of type 'org.pkwmtt.examCalendar.ExamService' that could not be found. + + +Action: + +Consider defining a bean of type 'org.pkwmtt.examCalendar.ExamService' in your configuration. + +2025-08-19 16:09:01 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-19 16:09:03 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-19 16:09:03 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-19 16:09:03 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-19 16:09:03 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-19 16:09:03 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-19 16:09:03 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-19 16:09:03 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-19 16:09:03 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-19 16:09:03 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-19 16:09:03 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-19 16:09:03 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-19 16:09:03 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-19 16:09:03 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-19 16:09:03 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-19 16:09:03 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-19 16:09:03 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-19 16:09:03 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-19 16:09:03 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-19 16:09:03 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-19 16:09:03 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-19 16:09:03 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-19 16:09:03 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-19 16:09:03 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-19 16:09:03 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamController.java b/src/main/java/org/pkwmtt/examCalendar/ExamController.java index 7c69cff..ad8bc5c 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamController.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamController.java @@ -73,10 +73,10 @@ public ResponseEntity getExam(@PathVariable int id) { * @param groups set of groups * @return 200 ok with list of exams for specific group */ - @GetMapping("/by-groups") - public ResponseEntity> getExams(@RequestParam Set groups){ - return ResponseEntity.ok(examService.getExamByGroup(groups)); - } +// @GetMapping("/by-groups") +// public ResponseEntity> getExams(@RequestParam Set groups){ +// return ResponseEntity.ok(examService.getExamByGroup(groups)); +// } /** * @return 200 ok with list of available exam types diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamService.java b/src/main/java/org/pkwmtt/examCalendar/ExamService.java index 8da2396..3c7b509 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamService.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamService.java @@ -56,26 +56,26 @@ public Exam getExamById(int id) { return examRepository.findById(id).orElseThrow(() -> new NoSuchElementWithProvidedIdException(id)); } - /** - * @param groups set od groups (max 4) - * @return set of exams for specific groups - */ - public Set getExamByGroup(Set groups) { - if (groups.size() > 4 || groups.isEmpty()) - throw new UnsupportedCountOfArgumentsException(1, 5, groups.size()); - List groupList = new ArrayList<>(groups); - return switch (groupList.size()) { - case 4 -> examRepository.findExamsByGroupsIdentifier( - groupList.get(0), groupList.get(1), groupList.get(2), groupList.get(3)); - case 3 -> examRepository.findExamsByGroupsIdentifier( - groupList.get(0), groupList.get(1), groupList.get(2)); - case 2 -> examRepository.findExamsByGroupsIdentifier( - groupList.get(0), groupList.get(1)); - case 1 -> examRepository.findExamsByGroupsIdentifier( - groupList.get(0)); - default -> Set.of(); - }; - } +// /** +// * @param groups set od groups (max 4) +// * @return set of exams for specific groups +// */ +// public Set getExamByGroup(Set groups) { +// if (groups.size() > 4 || groups.isEmpty()) +// throw new UnsupportedCountOfArgumentsException(1, 5, groups.size()); +// List groupList = new ArrayList<>(groups); +// return switch (groupList.size()) { +// case 4 -> examRepository.findExamsByGroupsIdentifier( +// groupList.get(0), groupList.get(1), groupList.get(2), groupList.get(3)); +// case 3 -> examRepository.findExamsByGroupsIdentifier( +// groupList.get(0), groupList.get(1), groupList.get(2)); +// case 2 -> examRepository.findExamsByGroupsIdentifier( +// groupList.get(0), groupList.get(1)); +// case 1 -> examRepository.findExamsByGroupsIdentifier( +// groupList.get(0)); +// default -> Set.of(); +// }; +// } /** * @return list of examTypes diff --git a/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java b/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java index f1cfdfb..bd390ff 100644 --- a/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java +++ b/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java @@ -6,8 +6,10 @@ import jakarta.validation.constraints.Size; import lombok.Getter; import lombok.RequiredArgsConstructor; +import org.pkwmtt.examCalendar.entity.StudentGroup; import java.time.LocalDateTime; +import java.util.Set; @Getter @RequiredArgsConstructor @@ -24,10 +26,9 @@ public class ExamDto { @NotNull private final LocalDateTime date; - @NotBlank - @Size(max = 255, message = "max size of field is 255") - private final String examGroups; - @NotNull private final String examType; + + @NotNull + private final Set examGroups; } diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/StudentGroup.java b/src/main/java/org/pkwmtt/examCalendar/entity/StudentGroup.java index a9b0e22..1733807 100644 --- a/src/main/java/org/pkwmtt/examCalendar/entity/StudentGroup.java +++ b/src/main/java/org/pkwmtt/examCalendar/entity/StudentGroup.java @@ -1,10 +1,7 @@ package org.pkwmtt.examCalendar.entity; import jakarta.persistence.*; -import lombok.Data; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.Setter; +import lombok.*; import java.util.HashSet; import java.util.Set; @@ -13,6 +10,8 @@ @Getter @Setter @RequiredArgsConstructor +@AllArgsConstructor +@Builder @Table(name = "`groups`") public class StudentGroup { @Id @@ -23,6 +22,7 @@ public class StudentGroup { @Column(nullable = false) private String name; +// FIXME: remove? @ManyToMany(mappedBy = "groups") private Set exams = new HashSet<>(); } diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/User.java b/src/main/java/org/pkwmtt/examCalendar/entity/User.java index 22d7e5d..4cdfbc9 100644 --- a/src/main/java/org/pkwmtt/examCalendar/entity/User.java +++ b/src/main/java/org/pkwmtt/examCalendar/entity/User.java @@ -5,7 +5,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import org.pkwmtt.enums.Role; +import org.pkwmtt.examCalendar.enums.Role; @Entity @Getter diff --git a/src/main/java/org/pkwmtt/enums/Role.java b/src/main/java/org/pkwmtt/examCalendar/enums/Role.java similarity index 56% rename from src/main/java/org/pkwmtt/enums/Role.java rename to src/main/java/org/pkwmtt/examCalendar/enums/Role.java index 0a72aa0..aafdf12 100644 --- a/src/main/java/org/pkwmtt/enums/Role.java +++ b/src/main/java/org/pkwmtt/examCalendar/enums/Role.java @@ -1,4 +1,4 @@ -package org.pkwmtt.enums; +package org.pkwmtt.examCalendar.enums; public enum Role { ADMIN, diff --git a/src/main/java/org/pkwmtt/enums/SubjectType.java b/src/main/java/org/pkwmtt/examCalendar/enums/SubjectType.java similarity index 77% rename from src/main/java/org/pkwmtt/enums/SubjectType.java rename to src/main/java/org/pkwmtt/examCalendar/enums/SubjectType.java index f43282e..0aad3d5 100644 --- a/src/main/java/org/pkwmtt/enums/SubjectType.java +++ b/src/main/java/org/pkwmtt/examCalendar/enums/SubjectType.java @@ -1,4 +1,4 @@ -package org.pkwmtt.enums; +package org.pkwmtt.examCalendar.enums; public enum SubjectType { LECTURE, diff --git a/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java index 6fe3abd..6e300e5 100644 --- a/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java +++ b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java @@ -25,8 +25,8 @@ public Exam mapToNewExam(ExamDto examDto) { .title(examDto.getTitle()) .description(examDto.getDescription()) .examDate(examDto.getDate()) - .examGroups(examDto.getExamGroups()) .examType(examTypeRepository.findByName(examDto.getExamType()).orElseThrow(() -> new ExamTypeNotExistsException(examDto.getExamType()))) + .groups(examDto.getExamGroups()) .build(); } @@ -42,8 +42,8 @@ public Exam mapToExistingExam(ExamDto examDto, int id) { .title(examDto.getTitle()) .description(examDto.getDescription()) .examDate(examDto.getDate()) - .examGroups(examDto.getExamGroups()) .examType(examTypeRepository.findByName(examDto.getExamType()).orElseThrow(() -> new ExamTypeNotExistsException(examDto.getExamType()))) + .groups(examDto.getExamGroups()) .build(); } } diff --git a/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java index e6d3454..46eb47e 100644 --- a/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java +++ b/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java @@ -9,66 +9,66 @@ public interface ExamRepository extends JpaRepository { - /** - * fetch all data using one query - * @param group1 group identifier - * @param group2 group identifier - * @param group3 group identifier - * @param group4 group identifier - * @return set of Exams for specific groups - */ - @Query("SELECT e FROM Exam e JOIN FETCH e.examType WHERE " + - "e.examGroups LIKE CONCAT('%', :g1, '%') OR " + - "e.examGroups LIKE CONCAT('%', :g2, '%') OR " + - "e.examGroups LIKE CONCAT('%', :g3, '%') OR " + - "e.examGroups LIKE CONCAT('%', :g4, '%') ") - Set findExamsByGroupsIdentifier( - @Param("g1") String group1, - @Param("g2") String group2, - @Param("g3") String group3, - @Param("g4") String group4 - ); - - /** - * fetch all data using one query - * @param group1 group identifier - * @param group2 group identifier - * @param group3 group identifier - * @return set of Exams for specific groups - */ - @Query("SELECT e FROM Exam e JOIN FETCH e.examType WHERE " + - "e.examGroups LIKE CONCAT('%', :g1, '%') OR " + - "e.examGroups LIKE CONCAT('%', :g2, '%') OR " + - "e.examGroups LIKE CONCAT('%', :g3, '%') ") - Set findExamsByGroupsIdentifier( - @Param("g1") String group1, - @Param("g2") String group2, - @Param("g3") String group3 - ); - - /** - * fetch all data using one query - * @param group1 group identifier - * @param group2 group identifier - * @return set of Exams for specific groups - */ - @Query("SELECT e FROM Exam e JOIN FETCH e.examType WHERE " + - "e.examGroups LIKE CONCAT('%', :g1, '%') OR " + - "e.examGroups LIKE CONCAT('%', :g2, '%')" ) - Set findExamsByGroupsIdentifier( - @Param("g1") String group1, - @Param("g2") String group2 - ); - - /** - * fetch all data using one query - * @param group group identifier - * @return set of Exams for specific group - */ - @Query("SELECT e FROM Exam e JOIN FETCH e.examType WHERE " + - "e.examGroups LIKE CONCAT('%', :gg, '%')") - Set findExamsByGroupsIdentifier( - @Param("gg") String group - ); +// /** +// * fetch all data using one query +// * @param group1 group identifier +// * @param group2 group identifier +// * @param group3 group identifier +// * @param group4 group identifier +// * @return set of Exams for specific groups +// */ +// @Query("SELECT e FROM Exam e JOIN FETCH e.examType WHERE " + +// "e.examGroups LIKE CONCAT('%', :g1, '%') OR " + +// "e.examGroups LIKE CONCAT('%', :g2, '%') OR " + +// "e.examGroups LIKE CONCAT('%', :g3, '%') OR " + +// "e.examGroups LIKE CONCAT('%', :g4, '%') ") +// Set findExamsByGroupsIdentifier( +// @Param("g1") String group1, +// @Param("g2") String group2, +// @Param("g3") String group3, +// @Param("g4") String group4 +// ); +// +// /** +// * fetch all data using one query +// * @param group1 group identifier +// * @param group2 group identifier +// * @param group3 group identifier +// * @return set of Exams for specific groups +// */ +// @Query("SELECT e FROM Exam e JOIN FETCH e.examType WHERE " + +// "e.examGroups LIKE CONCAT('%', :g1, '%') OR " + +// "e.examGroups LIKE CONCAT('%', :g2, '%') OR " + +// "e.examGroups LIKE CONCAT('%', :g3, '%') ") +// Set findExamsByGroupsIdentifier( +// @Param("g1") String group1, +// @Param("g2") String group2, +// @Param("g3") String group3 +// ); +// +// /** +// * fetch all data using one query +// * @param group1 group identifier +// * @param group2 group identifier +// * @return set of Exams for specific groups +// */ +// @Query("SELECT e FROM Exam e JOIN FETCH e.examType WHERE " + +// "e.examGroups LIKE CONCAT('%', :g1, '%') OR " + +// "e.examGroups LIKE CONCAT('%', :g2, '%')" ) +// Set findExamsByGroupsIdentifier( +// @Param("g1") String group1, +// @Param("g2") String group2 +// ); +// +// /** +// * fetch all data using one query +// * @param group group identifier +// * @return set of Exams for specific group +// */ +// @Query("SELECT e FROM Exam e JOIN FETCH e.examType WHERE " + +// "e.examGroups LIKE CONCAT('%', :gg, '%')") +// Set findExamsByGroupsIdentifier( +// @Param("gg") String group +// ); } \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/timetable/dto/SubjectDTO.java b/src/main/java/org/pkwmtt/timetable/dto/SubjectDTO.java index 7b71ce3..e81f89b 100644 --- a/src/main/java/org/pkwmtt/timetable/dto/SubjectDTO.java +++ b/src/main/java/org/pkwmtt/timetable/dto/SubjectDTO.java @@ -2,7 +2,7 @@ import lombok.*; import lombok.experimental.Accessors; -import org.pkwmtt.enums.SubjectType; +import org.pkwmtt.examCalendar.enums.SubjectType; import java.util.regex.Pattern; diff --git a/src/main/java/org/pkwmtt/timetable/parser/TimetableParserService.java b/src/main/java/org/pkwmtt/timetable/parser/TimetableParserService.java index af32752..46605d3 100644 --- a/src/main/java/org/pkwmtt/timetable/parser/TimetableParserService.java +++ b/src/main/java/org/pkwmtt/timetable/parser/TimetableParserService.java @@ -7,7 +7,7 @@ import org.jsoup.select.Elements; import org.pkwmtt.timetable.dto.DayOfWeekDTO; import org.pkwmtt.timetable.dto.SubjectDTO; -import org.pkwmtt.enums.SubjectType; +import org.pkwmtt.examCalendar.enums.SubjectType; import org.springframework.stereotype.Service; import java.util.ArrayList; diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java index 5f7f6ae..6cd65b7 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java @@ -7,6 +7,7 @@ import org.pkwmtt.examCalendar.dto.ExamDto; import org.pkwmtt.examCalendar.entity.Exam; import org.pkwmtt.examCalendar.entity.ExamType; +import org.pkwmtt.examCalendar.entity.StudentGroup; import org.pkwmtt.examCalendar.repository.ExamRepository; import org.pkwmtt.examCalendar.repository.ExamTypeRepository; import org.springframework.beans.factory.annotation.Autowired; @@ -20,7 +21,9 @@ import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import static org.hamcrest.Matchers.containsString; import static org.junit.jupiter.api.Assertions.*; @@ -85,9 +88,9 @@ void addExamWithCorrectData() throws Exception { // compare dates with minutes level precision assertEquals( examDtoRequest.getDate().truncatedTo(ChronoUnit.MINUTES), - examResponse.getDate().truncatedTo(ChronoUnit.MINUTES) + examResponse.getExamDate().truncatedTo(ChronoUnit.MINUTES) ); - assertEquals(examDtoRequest.getExamGroups(), examResponse.getExamGroups()); + assertEquals(examDtoRequest.getExamGroups(), examResponse.getGroups()); assertEquals(examDtoRequest.getExamType(), examResponse.getExamType().getName()); } @@ -332,9 +335,9 @@ void modifyExamWithCorrectData() throws Exception { assertEquals("first exam", responseExam.getDescription()); assertEquals( LocalDateTime.now().plusDays(1).truncatedTo(ChronoUnit.MINUTES), - responseExam.getDate().truncatedTo(ChronoUnit.MINUTES) + responseExam.getExamDate().truncatedTo(ChronoUnit.MINUTES) ); - assertEquals("12K2, L04", responseExam.getExamGroups()); + assertEquals("12K2, L04", responseExam.getGroups()); } @Test @@ -407,10 +410,10 @@ void getExamByIdWithCorrectId() throws Exception { assertEquals(exam.getTitle(), responseNode.get("title").asText()); assertEquals(exam.getDescription(), responseNode.get("description").asText()); assertEquals( - exam.getDate().truncatedTo(ChronoUnit.MINUTES), + exam.getExamDate().truncatedTo(ChronoUnit.MINUTES), LocalDateTime.parse(responseNode.get("date").textValue()).truncatedTo(ChronoUnit.MINUTES) ); - assertEquals(exam.getExamGroups(), responseNode.get("examGroups").asText()); + assertEquals(exam.getGroups(), responseNode.get("examGroups").asText()); assertEquals(mapper.readTree(mapper.writeValueAsString(exam.getExamType())), responseNode.get("examType")); } @@ -492,11 +495,14 @@ private ExamType createExampleExamType(String name) { * @return created Exam */ private Exam createExampleExam(ExamType type) { + Set examGroups = new HashSet<>(); + examGroups.add(StudentGroup.builder().name("11K1").build()); + examGroups.add(StudentGroup.builder().name("L01").build()); return Exam.builder() .title("Exam") .description("Exam description") - .date(LocalDateTime.now().plusDays(1)) - .examGroups("11K1, L01") + .examDate(LocalDateTime.now().plusDays(1)) + .groups(examGroups) .examType(type) .build(); } @@ -506,12 +512,15 @@ private Exam createExampleExam(ExamType type) { * @return created ExamDto */ private ExamDto createExampleExamDto(String examTypeName) { + Set examGroups = new HashSet<>(); + examGroups.add(StudentGroup.builder().name("11K1").build()); + examGroups.add(StudentGroup.builder().name("L01").build()); return new ExamDto( "Math exam", "first exam", LocalDateTime.now().plusDays(1), - "12K2, L04", - examTypeName + examTypeName, + examGroups ); } diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java index c86b0ab..0c59328 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java @@ -9,6 +9,7 @@ import org.pkwmtt.examCalendar.dto.ExamDto; import org.pkwmtt.examCalendar.entity.Exam; import org.pkwmtt.examCalendar.entity.ExamType; +import org.pkwmtt.examCalendar.entity.StudentGroup; import org.pkwmtt.examCalendar.mapper.ExamDtoToExamMapper; import org.pkwmtt.examCalendar.repository.ExamRepository; import org.pkwmtt.exceptions.UnsupportedCountOfArgumentsException; @@ -37,18 +38,21 @@ class ExamServiceTest { void addExam() { // given int examId = 1; + Set examGroups = new HashSet<>(); + examGroups.add(StudentGroup.builder().name("12K2").build()); + examGroups.add(StudentGroup.builder().name("13L1").build()); ExamDto examDto = new ExamDto( "Math exam", "desc", LocalDateTime.now().plusDays(1), - "12K2, 13L1", - "Exam" + "Exam", + examGroups ); Exam exam = Exam.builder() .title("Math exam") .description("desc") - .date(LocalDateTime.now().plusDays(1)) - .examGroups("12K2, 13L1") + .examDate(LocalDateTime.now().plusDays(1)) + .groups(examGroups) .examType(new ExamType(1, "Exam")) .build(); when(examDtoToExamMapper.mapToNewExam(examDto)).thenReturn(exam); @@ -161,171 +165,172 @@ void shouldThrowExceptionWhenExamNotFound() { } // getExamByGroup - @Test - void shouldThrowWithMoreThan4Arguments() { -// given - Set groups = new HashSet<>(); - groups.add("12K2"); - groups.add("13L1"); - groups.add("13A2"); - groups.add("41S2"); - groups.add("11S3"); -// when - RuntimeException exception = assertThrows( - UnsupportedCountOfArgumentsException.class, - () -> examService.getExamByGroup(groups) - ); -// then - assertEquals( - "Invalid count of arguments provided: 5 expected more than: 1 less than: 5", - exception.getMessage() - ); - } - - - @Test - void shouldCallRepositoryWith4Arguments() { -// given - Set groups = new HashSet<>(); - groups.add("12K2"); - groups.add("13L1"); - groups.add("13A2"); - groups.add("41S2"); - Exam mockExam = mock(Exam.class); - Set exams = new HashSet<>(); - exams.add(mockExam); - when(examRepository.findExamsByGroupsIdentifier(any(), any(), any(), any())).thenReturn(exams); -// when - Set result = examService.getExamByGroup(groups); -// then - List> cap = new ArrayList<>(); - for (int i = 0; i < 4; ++i) - cap.add(ArgumentCaptor.forClass(String.class)); - - verify(examRepository).findExamsByGroupsIdentifier( - cap.get(0).capture(), - cap.get(1).capture(), - cap.get(2).capture(), - cap.get(3).capture() - ); - Set passedGroups = cap.stream().map(ArgumentCaptor::getValue).collect(Collectors.toSet()); - - assertEquals(groups, passedGroups); - assertEquals(exams, result); - } - - - @Test - void shouldCallRepositoryWith3Arguments() { -// given - Set groups = new HashSet<>(); - groups.add("12K2"); - groups.add("13L1"); - groups.add("13A2"); - Exam mockExam = mock(Exam.class); - Set exams = new HashSet<>(); - exams.add(mockExam); - when(examRepository.findExamsByGroupsIdentifier(any(), any(), any())).thenReturn(exams); -// when - Set result = examService.getExamByGroup(groups); -// then - List> cap = new ArrayList<>(); - for (int i = 0; i < 3; ++i) - cap.add(ArgumentCaptor.forClass(String.class)); - - verify(examRepository).findExamsByGroupsIdentifier( - cap.get(0).capture(), - cap.get(1).capture(), - cap.get(2).capture() - ); - Set passedGroups = cap.stream().map(ArgumentCaptor::getValue).collect(Collectors.toSet()); - - assertEquals(groups, passedGroups); - assertEquals(exams, result); - } - - @Test - void shouldCallRepositoryWith2Arguments() { -// given - Set groups = new HashSet<>(); - groups.add("12K2"); - groups.add("13L1"); - Exam mockExam = mock(Exam.class); - Set exams = new HashSet<>(); - exams.add(mockExam); - when(examRepository.findExamsByGroupsIdentifier(any(), any())).thenReturn(exams); -// when - Set result = examService.getExamByGroup(groups); -// then - List> cap = new ArrayList<>(); - for (int i = 0; i < 2; ++i) - cap.add(ArgumentCaptor.forClass(String.class)); - - verify(examRepository).findExamsByGroupsIdentifier( - cap.get(0).capture(), - cap.get(1).capture() - ); - Set passedGroups = cap.stream().map(ArgumentCaptor::getValue).collect(Collectors.toSet()); - - assertEquals(groups, passedGroups); - assertEquals(exams, result); - } - - @Test - void shouldCallRepositoryWithSingleArguments() { -// given - Set groups = new HashSet<>(); - groups.add("12K2"); - Exam mockExam = mock(Exam.class); - Set exams = new HashSet<>(); - exams.add(mockExam); - when(examRepository.findExamsByGroupsIdentifier(any())).thenReturn(exams); -// when - Set result = examService.getExamByGroup(groups); -// then - ArgumentCaptor cap = ArgumentCaptor.forClass(String.class); - - verify(examRepository).findExamsByGroupsIdentifier(cap.capture()); - Set passedGroups = new HashSet<>(); - passedGroups.add(cap.getValue()); - - assertEquals(groups, passedGroups); - assertEquals(exams, result); - } - - - @Test - void shouldCallRepositoryWithDuplicatesOf4UniqueArguments() { -// given - Set groups = new HashSet<>(); - groups.add("12K2"); - groups.add("13L1"); - groups.add("13A2"); - groups.add("41S2"); - groups.add("41S2"); - groups.add("13L1"); - Exam mockExam = mock(Exam.class); - Set exams = new HashSet<>(); - exams.add(mockExam); - when(examRepository.findExamsByGroupsIdentifier(any(), any(), any(), any())).thenReturn(exams); -// when - Set result = examService.getExamByGroup(groups); -// then - List> cap = new ArrayList<>(); - for (int i = 0; i < 4; ++i) - cap.add(ArgumentCaptor.forClass(String.class)); - - verify(examRepository).findExamsByGroupsIdentifier( - cap.get(0).capture(), - cap.get(1).capture(), - cap.get(2).capture(), - cap.get(3).capture() - ); - Set passedGroups = cap.stream().map(ArgumentCaptor::getValue).collect(Collectors.toSet()); - - assertEquals(groups, passedGroups); - assertEquals(exams, result); - assertEquals(4, passedGroups.size()); - } + // FIXME: write test for new version of this method +// @Test +// void shouldThrowWithMoreThan4Arguments() { +//// given +// Set groups = new HashSet<>(); +// groups.add("12K2"); +// groups.add("13L1"); +// groups.add("13A2"); +// groups.add("41S2"); +// groups.add("11S3"); +//// when +// RuntimeException exception = assertThrows( +// UnsupportedCountOfArgumentsException.class, +// () -> examService.getExamByGroup(groups) +// ); +//// then +// assertEquals( +// "Invalid count of arguments provided: 5 expected more than: 1 less than: 5", +// exception.getMessage() +// ); +// } + + +// @Test +// void shouldCallRepositoryWith4Arguments() { +//// given +// Set groups = new HashSet<>(); +// groups.add("12K2"); +// groups.add("13L1"); +// groups.add("13A2"); +// groups.add("41S2"); +// Exam mockExam = mock(Exam.class); +// Set exams = new HashSet<>(); +// exams.add(mockExam); +// when(examRepository.findExamsByGroupsIdentifier(any(), any(), any(), any())).thenReturn(exams); +//// when +// Set result = examService.getExamByGroup(groups); +//// then +// List> cap = new ArrayList<>(); +// for (int i = 0; i < 4; ++i) +// cap.add(ArgumentCaptor.forClass(String.class)); +// +// verify(examRepository).findExamsByGroupsIdentifier( +// cap.get(0).capture(), +// cap.get(1).capture(), +// cap.get(2).capture(), +// cap.get(3).capture() +// ); +// Set passedGroups = cap.stream().map(ArgumentCaptor::getValue).collect(Collectors.toSet()); +// +// assertEquals(groups, passedGroups); +// assertEquals(exams, result); +// } +// +// +// @Test +// void shouldCallRepositoryWith3Arguments() { +//// given +// Set groups = new HashSet<>(); +// groups.add("12K2"); +// groups.add("13L1"); +// groups.add("13A2"); +// Exam mockExam = mock(Exam.class); +// Set exams = new HashSet<>(); +// exams.add(mockExam); +// when(examRepository.findExamsByGroupsIdentifier(any(), any(), any())).thenReturn(exams); +//// when +// Set result = examService.getExamByGroup(groups); +//// then +// List> cap = new ArrayList<>(); +// for (int i = 0; i < 3; ++i) +// cap.add(ArgumentCaptor.forClass(String.class)); +// +// verify(examRepository).findExamsByGroupsIdentifier( +// cap.get(0).capture(), +// cap.get(1).capture(), +// cap.get(2).capture() +// ); +// Set passedGroups = cap.stream().map(ArgumentCaptor::getValue).collect(Collectors.toSet()); +// +// assertEquals(groups, passedGroups); +// assertEquals(exams, result); +// } +// +// @Test +// void shouldCallRepositoryWith2Arguments() { +//// given +// Set groups = new HashSet<>(); +// groups.add("12K2"); +// groups.add("13L1"); +// Exam mockExam = mock(Exam.class); +// Set exams = new HashSet<>(); +// exams.add(mockExam); +// when(examRepository.findExamsByGroupsIdentifier(any(), any())).thenReturn(exams); +//// when +// Set result = examService.getExamByGroup(groups); +//// then +// List> cap = new ArrayList<>(); +// for (int i = 0; i < 2; ++i) +// cap.add(ArgumentCaptor.forClass(String.class)); +// +// verify(examRepository).findExamsByGroupsIdentifier( +// cap.get(0).capture(), +// cap.get(1).capture() +// ); +// Set passedGroups = cap.stream().map(ArgumentCaptor::getValue).collect(Collectors.toSet()); +// +// assertEquals(groups, passedGroups); +// assertEquals(exams, result); +// } +// +// @Test +// void shouldCallRepositoryWithSingleArguments() { +//// given +// Set groups = new HashSet<>(); +// groups.add("12K2"); +// Exam mockExam = mock(Exam.class); +// Set exams = new HashSet<>(); +// exams.add(mockExam); +// when(examRepository.findExamsByGroupsIdentifier(any())).thenReturn(exams); +//// when +// Set result = examService.getExamByGroup(groups); +//// then +// ArgumentCaptor cap = ArgumentCaptor.forClass(String.class); +// +// verify(examRepository).findExamsByGroupsIdentifier(cap.capture()); +// Set passedGroups = new HashSet<>(); +// passedGroups.add(cap.getValue()); +// +// assertEquals(groups, passedGroups); +// assertEquals(exams, result); +// } +// +// +// @Test +// void shouldCallRepositoryWithDuplicatesOf4UniqueArguments() { +//// given +// Set groups = new HashSet<>(); +// groups.add("12K2"); +// groups.add("13L1"); +// groups.add("13A2"); +// groups.add("41S2"); +// groups.add("41S2"); +// groups.add("13L1"); +// Exam mockExam = mock(Exam.class); +// Set exams = new HashSet<>(); +// exams.add(mockExam); +// when(examRepository.findExamsByGroupsIdentifier(any(), any(), any(), any())).thenReturn(exams); +//// when +// Set result = examService.getExamByGroup(groups); +//// then +// List> cap = new ArrayList<>(); +// for (int i = 0; i < 4; ++i) +// cap.add(ArgumentCaptor.forClass(String.class)); +// +// verify(examRepository).findExamsByGroupsIdentifier( +// cap.get(0).capture(), +// cap.get(1).capture(), +// cap.get(2).capture(), +// cap.get(3).capture() +// ); +// Set passedGroups = cap.stream().map(ArgumentCaptor::getValue).collect(Collectors.toSet()); +// +// assertEquals(groups, passedGroups); +// assertEquals(exams, result); +// assertEquals(4, passedGroups.size()); +// } } \ No newline at end of file diff --git a/src/test/java/org/pkwmtt/examCalendar/dto/ExamDtoTest.java b/src/test/java/org/pkwmtt/examCalendar/dto/ExamDtoTest.java index b14edc6..e92418c 100644 --- a/src/test/java/org/pkwmtt/examCalendar/dto/ExamDtoTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/dto/ExamDtoTest.java @@ -3,10 +3,13 @@ import jakarta.validation.ConstraintViolation; import jakarta.validation.Validation; import jakarta.validation.Validator; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; +import org.pkwmtt.examCalendar.entity.StudentGroup; import java.time.LocalDateTime; +import java.util.HashSet; import java.util.Set; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -23,6 +26,17 @@ public ExamDtoTest() { @Mock private ExamDto examDto; + private Set groups; + + @BeforeEach + void setup(){ + StudentGroup group = StudentGroup.builder() + .name("12K2") + .build(); + groups = new HashSet<>(); + groups.add(group); + } + @Test void validData() { // given @@ -30,8 +44,8 @@ void validData() { "Math exam", "First exam", LocalDateTime.now().plusDays(1), - "12K2, K04", - "exam" + "exam", + groups ); // when, then assertTrue(validator.validate(examDto).isEmpty()); @@ -46,8 +60,8 @@ void emptyStringTitle() { "", "First exam", LocalDateTime.now().plusDays(1), - "12K2, K04", - "exam" + "exam", + groups ); // when Set> violations = validator.validate(examDto); @@ -59,12 +73,14 @@ void emptyStringTitle() { @Test void emptyExamGroups() { // given + // clear groups set + groups.clear(); ExamDto examDto = new ExamDto( "Math exam", "First exam", LocalDateTime.now().plusDays(1), - "", - "exam" + "exam", + groups ); // when Set> violations = validator.validate(examDto); @@ -83,8 +99,8 @@ void toLongStringTitle() { "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "First exam", LocalDateTime.now().plusDays(1), - "12K2, K04", - "exam" + "exam", + groups ); // when Set> violations = validator.validate(examDto); @@ -101,8 +117,8 @@ void toLongDescription() { // 256 characters "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", LocalDateTime.now().plusDays(1), - "12K2, K04", - "exam" + "exam", + groups ); // when Set> violations = validator.validate(examDto); @@ -111,22 +127,23 @@ void toLongDescription() { assertTrue(violations.stream().anyMatch(v -> v.getPropertyPath().toString().equals("description"))); } - @Test - void toLongExamGroups() { - // given - ExamDto examDto = new ExamDto( - "Math exam", - "First exam", - LocalDateTime.now().plusDays(1), - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "exam" - ); -// when - Set> violations = validator.validate(examDto); -// then - assertFalse(validator.validate(examDto).isEmpty()); - assertTrue(violations.stream().anyMatch(v -> v.getPropertyPath().toString().equals("examGroups"))); - } + // TODO: change to too large set +// @Test +// void toLongExamGroups() { +// // given +// ExamDto examDto = new ExamDto( +// "Math exam", +// "First exam", +// LocalDateTime.now().plusDays(1), +// "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", +// "exam" +// ); +//// when +// Set> violations = validator.validate(examDto); +//// then +// assertFalse(validator.validate(examDto).isEmpty()); +// assertTrue(violations.stream().anyMatch(v -> v.getPropertyPath().toString().equals("examGroups"))); +// } // date not in future @@ -137,8 +154,8 @@ void dateNotInFuture() { "Math exam", "First exam", LocalDateTime.now().minusHours(1), - "12K2, K04", - "exam" + "exam", + groups ); // when Set> violations = validator.validate(examDto); diff --git a/src/test/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapperTest.java b/src/test/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapperTest.java index 45081ae..0dd27f3 100644 --- a/src/test/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapperTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapperTest.java @@ -1,5 +1,6 @@ package org.pkwmtt.examCalendar.mapper; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -8,11 +9,14 @@ import org.pkwmtt.examCalendar.dto.ExamDto; import org.pkwmtt.examCalendar.entity.Exam; import org.pkwmtt.examCalendar.entity.ExamType; +import org.pkwmtt.examCalendar.entity.StudentGroup; import org.pkwmtt.examCalendar.repository.ExamTypeRepository; import org.pkwmtt.exceptions.InvalidGroupIdentifierException; import java.time.LocalDateTime; +import java.util.HashSet; import java.util.Optional; +import java.util.Set; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.when; @@ -29,10 +33,16 @@ class ExamDtoToExamMapperTest { private ExamDto examDto; private String examTypeName; -// @BeforeEach -// void setup() { -// -// } + private Set groups; + + @BeforeEach + void setup(){ + StudentGroup group = StudentGroup.builder() + .name("12K2") + .build(); + groups = new HashSet<>(); + groups.add(group); + } /**********************************************************************************/ // mapToNewExam @@ -45,7 +55,7 @@ void isFieldsMappedProperlyToNewExam() { "Linear algebra", LocalDateTime.now().plusDays(1), "12K2, 13S1", - examTypeName + groups ); when(examTypeRepository.findByName(examTypeName)).thenReturn( Optional.of(ExamType.builder() @@ -58,8 +68,8 @@ void isFieldsMappedProperlyToNewExam() { // test fields assertEquals(examDto.getTitle(), exam.getTitle()); assertEquals(examDto.getDescription(), exam.getDescription()); - assertEquals(examDto.getDate(), exam.getDate()); - assertEquals(examDto.getExamGroups(), exam.getExamGroups()); + assertEquals(examDto.getDate(), exam.getExamDate()); + assertEquals(examDto.getExamGroups(), exam.getGroups()); assertEquals(examTypeName, exam.getExamType().getName()); // test null id assertNull(exam.getExamId()); @@ -68,13 +78,17 @@ void isFieldsMappedProperlyToNewExam() { @Test void ShouldThrowExceptionWhenGroupIdentifierIsLongerThanSixCharactersForNewExam() { // given + StudentGroup group = StudentGroup.builder() + .name("Not_Valid_Identifier") + .build(); + groups.add(group); String examTypeName = "exam"; ExamDto examDto = new ExamDto( "Math exam", "Linear algebra", LocalDateTime.now().plusDays(1), - "12K2, 13S1, Not_Valid_Identifier, 41K1", - examTypeName + examTypeName, + groups ); when(examTypeRepository.findByName(examTypeName)).thenReturn( Optional.of(ExamType.builder() @@ -101,8 +115,8 @@ void isFieldsMappedProperlyToExistingExam() { "Math exam", "Linear algebra", LocalDateTime.now().plusDays(1), - "12K2, 13S1", - examTypeName + examTypeName, + groups ); when(examTypeRepository.findByName(examTypeName)).thenReturn( Optional.of(ExamType.builder() @@ -116,8 +130,8 @@ void isFieldsMappedProperlyToExistingExam() { assertEquals(examId, exam.getExamId()); assertEquals(examDto.getTitle(), exam.getTitle()); assertEquals(examDto.getDescription(), exam.getDescription()); - assertEquals(examDto.getDate(), exam.getDate()); - assertEquals(examDto.getExamGroups(), exam.getExamGroups()); + assertEquals(examDto.getDate(), exam.getExamDate()); + assertEquals(examDto.getExamGroups(), exam.getGroups()); assertEquals(examTypeName, exam.getExamType().getName()); // test not null id assertNotNull(exam.getExamId()); @@ -127,13 +141,17 @@ void isFieldsMappedProperlyToExistingExam() { void ShouldThrowExceptionWhenGroupIdentifierIsLongerThanSixCharactersForExistingExam() { // given int examId = 1; + StudentGroup group = StudentGroup.builder() + .name("Not_Valid_Identifier") + .build(); + groups.add(group); String examTypeName = "exam"; ExamDto examDto = new ExamDto( "Math exam", "Linear algebra", LocalDateTime.now().plusDays(1), - "12K2, 13S1, Not_Valid_Identifier, 41K1", - examTypeName + examTypeName, + groups ); when(examTypeRepository.findByName(examTypeName)).thenReturn( Optional.of(ExamType.builder() diff --git a/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java b/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java index 90f1614..192c8b4 100644 --- a/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java @@ -25,158 +25,159 @@ class ExamRepositoryTest { @Autowired private ExamTypeRepository examTypeRepository; - private ExamType examType; - - @BeforeEach - void setup(){ - examType = ExamType.builder() - .name("exam") - .build(); - examTypeRepository.save(examType); - } - - /** - * test if method find specific count of exams when 1 or 0 group identifiers match - */ - @Test - void testSingleIdentifierMatch() { -// given - Exam exam1 = Exam.builder() - .title("Exam 1") - .description("Exam 1") - .date(LocalDateTime.now().plusDays(1)) - .examGroups("12K2, K03") - .examType(examType) - .build(); - examRepository.save(exam1); - Exam exam2 = Exam.builder() - .title("Exam 2") - .description("Exam 2") - .date(LocalDateTime.now().plusDays(1)) - .examGroups("12K3, K03, S02") - .examType(examType) - .build(); - examRepository.save(exam2); - Exam exam3 = Exam.builder() - .title("Exam 3") - .description("Exam 3") - .date(LocalDateTime.now().plusDays(1)) - .examGroups("13K1, K05, L05") - .examType(examType) - .build(); - examRepository.save(exam3); - Exam exam4 = Exam.builder() - .title("Exam 4") - .description("Exam 4") - .date(LocalDateTime.now().plusDays(1)) - .examGroups("41K1, L04, P03, I01") - .examType(examType) - .build(); - examRepository.save(exam4); - Exam exam5 = Exam.builder() - .title("Exam 5") - .description("Exam 5") - .date(LocalDateTime.now().plusDays(1)) - .examGroups("11A1, G03, H01, P02") - .examType(examType) - .build(); - examRepository.save(exam5); - - String generalGroup = "12K2"; - String kGroup = "K05"; - String lGroup = "L04"; - String pGroup = "P02"; - -// when - Set exams = examRepository.findExamsByGroupsIdentifier(generalGroup, kGroup, lGroup, pGroup); - List examsTitles = exams.stream().map(Exam::getTitle).toList(); -// then - assertEquals(4, exams.size()); - assertTrue(examsTitles.contains("Exam 1")); - assertTrue(examsTitles.contains("Exam 3")); - assertTrue(examsTitles.contains("Exam 4")); - assertTrue(examsTitles.contains("Exam 5")); - } - - /** - * test if method don't duplicate exams when more than 1 identifier match - */ - @Test - void testMultipleIdentifierMatch() { -// given - Exam exam1 = Exam.builder() - .title("Exam 1") - .description("Exam 1") - .date(LocalDateTime.now().plusDays(1)) - .examGroups("12K2, K01, L04, P03, I01") - .examType(examType) - .build(); - examRepository.save(exam1); - Exam exam2 = Exam.builder() - .title("Exam 2") - .description("Exam 2") - .date(LocalDateTime.now().plusDays(1)) - .examGroups("12K2, K05, L04, P02") - .examType(examType) - .build(); - examRepository.save(exam2); - Exam exam3 = Exam.builder() - .title("Exam 3") - .description("Exam 3") - .date(LocalDateTime.now().plusDays(1)) - .examGroups("12K2, K05, L04, P02, I05") - .examType(examType) - .build(); - examRepository.save(exam3); - - String generalGroup = "12K2"; - String kGroup = "K05"; - String lGroup = "L04"; - String pGroup = "P02"; - -// when - Set exams = examRepository.findExamsByGroupsIdentifier(generalGroup, kGroup, lGroup, pGroup); - List examsTitles = exams.stream().map(Exam::getTitle).toList(); - -// then - assertEquals(3, exams.size()); - assertTrue(examsTitles.contains("Exam 1")); - assertTrue(examsTitles.contains("Exam 2")); - assertTrue(examsTitles.contains("Exam 3")); - } - - /** - * test if method return empty set identifiers don't match - */ - @Test - void testNothingMatch() { -// given - Exam exam1 = Exam.builder() - .title("Exam 1") - .description("Exam 1") - .date(LocalDateTime.now().plusDays(1)) - .examGroups("12K2, K01,") - .examType(examType) - .build(); - examRepository.save(exam1); - Exam exam2 = Exam.builder() - .title("Exam 2") - .description("Exam 2") - .date(LocalDateTime.now().plusDays(1)) - .examGroups("12K3, L05") - .examType(examType) - .build(); - examRepository.save(exam2); - - String generalGroup = "14K3"; - String kGroup = "K05"; - String lGroup = "L02"; - String pGroup = "P02"; - -// when - Set exams = examRepository.findExamsByGroupsIdentifier(generalGroup, kGroup, lGroup, pGroup); - -// then - assertTrue(exams.isEmpty()); - } +// TODO: change exam repository and test new version +// private ExamType examType; +// +// @BeforeEach +// void setup(){ +// examType = ExamType.builder() +// .name("exam") +// .build(); +// examTypeRepository.save(examType); +// } +// +// /** +// * test if method find specific count of exams when 1 or 0 group identifiers match +// */ +// @Test +// void testSingleIdentifierMatch() { +//// given +// Exam exam1 = Exam.builder() +// .title("Exam 1") +// .description("Exam 1") +// .examDate(LocalDateTime.now().plusDays(1)) +// .examGroups("12K2, K03") +// .examType(examType) +// .build(); +// examRepository.save(exam1); +// Exam exam2 = Exam.builder() +// .title("Exam 2") +// .description("Exam 2") +// .examDate(LocalDateTime.now().plusDays(1)) +// .examGroups("12K3, K03, S02") +// .examType(examType) +// .build(); +// examRepository.save(exam2); +// Exam exam3 = Exam.builder() +// .title("Exam 3") +// .description("Exam 3") +// .examDate(LocalDateTime.now().plusDays(1)) +// .examGroups("13K1, K05, L05") +// .examType(examType) +// .build(); +// examRepository.save(exam3); +// Exam exam4 = Exam.builder() +// .title("Exam 4") +// .description("Exam 4") +// .examDate(LocalDateTime.now().plusDays(1)) +// .examGroups("41K1, L04, P03, I01") +// .examType(examType) +// .build(); +// examRepository.save(exam4); +// Exam exam5 = Exam.builder() +// .title("Exam 5") +// .description("Exam 5") +// .examDate(LocalDateTime.now().plusDays(1)) +// .examGroups("11A1, G03, H01, P02") +// .examType(examType) +// .build(); +// examRepository.save(exam5); +// +// String generalGroup = "12K2"; +// String kGroup = "K05"; +// String lGroup = "L04"; +// String pGroup = "P02"; +// +//// when +// Set exams = examRepository.findExamsByGroupsIdentifier(generalGroup, kGroup, lGroup, pGroup); +// List examsTitles = exams.stream().map(Exam::getTitle).toList(); +//// then +// assertEquals(4, exams.size()); +// assertTrue(examsTitles.contains("Exam 1")); +// assertTrue(examsTitles.contains("Exam 3")); +// assertTrue(examsTitles.contains("Exam 4")); +// assertTrue(examsTitles.contains("Exam 5")); +// } +// +// /** +// * test if method don't duplicate exams when more than 1 identifier match +// */ +// @Test +// void testMultipleIdentifierMatch() { +//// given +// Exam exam1 = Exam.builder() +// .title("Exam 1") +// .description("Exam 1") +// .date(LocalDateTime.now().plusDays(1)) +// .examGroups("12K2, K01, L04, P03, I01") +// .examType(examType) +// .build(); +// examRepository.save(exam1); +// Exam exam2 = Exam.builder() +// .title("Exam 2") +// .description("Exam 2") +// .date(LocalDateTime.now().plusDays(1)) +// .examGroups("12K2, K05, L04, P02") +// .examType(examType) +// .build(); +// examRepository.save(exam2); +// Exam exam3 = Exam.builder() +// .title("Exam 3") +// .description("Exam 3") +// .date(LocalDateTime.now().plusDays(1)) +// .examGroups("12K2, K05, L04, P02, I05") +// .examType(examType) +// .build(); +// examRepository.save(exam3); +// +// String generalGroup = "12K2"; +// String kGroup = "K05"; +// String lGroup = "L04"; +// String pGroup = "P02"; +// +//// when +// Set exams = examRepository.findExamsByGroupsIdentifier(generalGroup, kGroup, lGroup, pGroup); +// List examsTitles = exams.stream().map(Exam::getTitle).toList(); +// +//// then +// assertEquals(3, exams.size()); +// assertTrue(examsTitles.contains("Exam 1")); +// assertTrue(examsTitles.contains("Exam 2")); +// assertTrue(examsTitles.contains("Exam 3")); +// } +// +// /** +// * test if method return empty set identifiers don't match +// */ +// @Test +// void testNothingMatch() { +//// given +// Exam exam1 = Exam.builder() +// .title("Exam 1") +// .description("Exam 1") +// .date(LocalDateTime.now().plusDays(1)) +// .examGroups("12K2, K01,") +// .examType(examType) +// .build(); +// examRepository.save(exam1); +// Exam exam2 = Exam.builder() +// .title("Exam 2") +// .description("Exam 2") +// .date(LocalDateTime.now().plusDays(1)) +// .examGroups("12K3, L05") +// .examType(examType) +// .build(); +// examRepository.save(exam2); +// +// String generalGroup = "14K3"; +// String kGroup = "K05"; +// String lGroup = "L02"; +// String pGroup = "P02"; +// +//// when +// Set exams = examRepository.findExamsByGroupsIdentifier(generalGroup, kGroup, lGroup, pGroup); +// +//// then +// assertTrue(exams.isEmpty()); +// } } \ No newline at end of file From bf3fdc25aac1d7d3a55bb9a2ff122c183e926992 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Wed, 20 Aug 2025 13:01:23 +0200 Subject: [PATCH 035/116] implement getExamByGroups with variable number of arguments --- logs/app.log | 121 ++++++++++++++++++ .../pkwmtt/examCalendar/ExamController.java | 11 +- .../org/pkwmtt/examCalendar/ExamService.java | 23 +++- .../org/pkwmtt/examCalendar/dto/ExamDto.java | 6 +- .../examCalendar/entity/StudentGroup.java | 4 +- .../mapper/ExamDtoToExamMapper.java | 10 +- .../mapper/ExamToExamDtoMapper.java | 26 ++++ .../repository/ExamRepository.java | 4 + .../repository/GroupRepository.java | 3 + .../InvalidGroupIdentifierException.java | 6 + .../pkwmtt/examCalendar/ExamServiceTest.java | 3 - 11 files changed, 198 insertions(+), 19 deletions(-) create mode 100644 src/main/java/org/pkwmtt/examCalendar/mapper/ExamToExamDtoMapper.java diff --git a/logs/app.log b/logs/app.log index f965341..993075d 100644 --- a/logs/app.log +++ b/logs/app.log @@ -256,3 +256,124 @@ select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.tit Column "E1_0.exam_date" not found; SQL statement: select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] 2025-08-19 16:09:03 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-20 12:35:10 ERROR o.s.boot.SpringApplication - Application run failed +org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'examController' defined in file [D:\Java\PKWMTT\target\classes\org\pkwmtt\examCalendar\ExamController.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'examService' defined in file [D:\Java\PKWMTT\target\classes\org\pkwmtt\examCalendar\ExamService.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'examRepository' defined in org.pkwmtt.examCalendar.repository.ExamRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: Could not create query for public abstract java.util.Set org.pkwmtt.examCalendar.repository.ExamRepository.findByGroupsIn(java.util.Set); Reason: Validation failed for query for method public abstract java.util.Set org.pkwmtt.examCalendar.repository.ExamRepository.findByGroupsIn(java.util.Set) + at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:804) + at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:240) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1395) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1232) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529) + at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) + at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373) + at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) + at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) + at org.springframework.beans.factory.support.DefaultListableBeanFactory.instantiateSingleton(DefaultListableBeanFactory.java:1222) + at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingleton(DefaultListableBeanFactory.java:1188) + at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:1123) + at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:987) + at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:627) + at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) + at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752) + at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439) + at org.springframework.boot.SpringApplication.run(SpringApplication.java:318) + at org.springframework.boot.SpringApplication.run(SpringApplication.java:1361) + at org.springframework.boot.SpringApplication.run(SpringApplication.java:1350) + at org.pkwmtt.PkwmttBackendApplication.main(PkwmttBackendApplication.java:12) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:50) +Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'examService' defined in file [D:\Java\PKWMTT\target\classes\org\pkwmtt\examCalendar\ExamService.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'examRepository' defined in org.pkwmtt.examCalendar.repository.ExamRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: Could not create query for public abstract java.util.Set org.pkwmtt.examCalendar.repository.ExamRepository.findByGroupsIn(java.util.Set); Reason: Validation failed for query for method public abstract java.util.Set org.pkwmtt.examCalendar.repository.ExamRepository.findByGroupsIn(java.util.Set) + at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:804) + at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:240) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1395) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1232) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529) + at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) + at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373) + at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) + at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) + at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1683) + at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1628) + at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:913) + at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791) + ... 24 common frames omitted +Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'examRepository' defined in org.pkwmtt.examCalendar.repository.ExamRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: Could not create query for public abstract java.util.Set org.pkwmtt.examCalendar.repository.ExamRepository.findByGroupsIn(java.util.Set); Reason: Validation failed for query for method public abstract java.util.Set org.pkwmtt.examCalendar.repository.ExamRepository.findByGroupsIn(java.util.Set) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1826) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:607) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529) + at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) + at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373) + at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) + at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) + at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1683) + at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1628) + at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:913) + at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791) + ... 37 common frames omitted +Caused by: org.springframework.data.repository.query.QueryCreationException: Could not create query for public abstract java.util.Set org.pkwmtt.examCalendar.repository.ExamRepository.findByGroupsIn(java.util.Set); Reason: Validation failed for query for method public abstract java.util.Set org.pkwmtt.examCalendar.repository.ExamRepository.findByGroupsIn(java.util.Set) + at org.springframework.data.repository.query.QueryCreationException.create(QueryCreationException.java:101) + at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.lookupQuery(QueryExecutorMethodInterceptor.java:120) + at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.mapMethodsToQuery(QueryExecutorMethodInterceptor.java:104) + at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.lambda$new$0(QueryExecutorMethodInterceptor.java:92) + at java.base/java.util.Optional.map(Optional.java:260) + at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.(QueryExecutorMethodInterceptor.java:92) + at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:434) + at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.lambda$afterPropertiesSet$4(RepositoryFactoryBeanSupport.java:350) + at org.springframework.data.util.Lazy.getNullable(Lazy.java:135) + at org.springframework.data.util.Lazy.get(Lazy.java:113) + at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.afterPropertiesSet(RepositoryFactoryBeanSupport.java:356) + at org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean.afterPropertiesSet(JpaRepositoryFactoryBean.java:132) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1873) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1822) + ... 47 common frames omitted +Caused by: java.lang.IllegalArgumentException: Validation failed for query for method public abstract java.util.Set org.pkwmtt.examCalendar.repository.ExamRepository.findByGroupsIn(java.util.Set) + at org.springframework.data.jpa.repository.query.SimpleJpaQuery.validateQuery(SimpleJpaQuery.java:97) + at org.springframework.data.jpa.repository.query.SimpleJpaQuery.(SimpleJpaQuery.java:67) + at org.springframework.data.jpa.repository.query.JpaQueryFactory.fromMethodWithQueryString(JpaQueryFactory.java:49) + at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$DeclaredQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:174) + at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$CreateIfNotFoundQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:254) + at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$AbstractQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:99) + at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.lookupQuery(QueryExecutorMethodInterceptor.java:116) + ... 59 common frames omitted +Caused by: java.lang.IllegalArgumentException: org.hibernate.query.SemanticException: Multi valued paths are only allowed for the member of operator + at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:143) + at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:167) + at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:173) + at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:886) + at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:796) + at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:143) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:364) + at jdk.proxy3/jdk.proxy3.$Proxy159.createQuery(Unknown Source) + at org.springframework.data.jpa.repository.query.SimpleJpaQuery.validateQuery(SimpleJpaQuery.java:91) + ... 65 common frames omitted +Caused by: org.hibernate.query.SemanticException: Multi valued paths are only allowed for the member of operator + at org.hibernate.query.sqm.internal.TypecheckUtil.assertComparable(TypecheckUtil.java:397) + at org.hibernate.query.sqm.tree.predicate.SqmInListPredicate.implyListElementType(SqmInListPredicate.java:139) + at org.hibernate.query.sqm.tree.predicate.SqmInListPredicate.(SqmInListPredicate.java:61) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitInPredicate(SemanticQueryBuilder.java:2847) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitInPredicate(SemanticQueryBuilder.java:277) + at org.hibernate.grammars.hql.HqlParser$InPredicateContext.accept(HqlParser.java:6560) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitWhereClause(SemanticQueryBuilder.java:2361) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitWhereClause(SemanticQueryBuilder.java:277) + at org.hibernate.grammars.hql.HqlParser$WhereClauseContext.accept(HqlParser.java:6302) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitQuery(SemanticQueryBuilder.java:1259) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitQuerySpecExpression(SemanticQueryBuilder.java:1040) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitQuerySpecExpression(SemanticQueryBuilder.java:277) + at org.hibernate.grammars.hql.HqlParser$QuerySpecExpressionContext.accept(HqlParser.java:2134) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitSimpleQueryGroup(SemanticQueryBuilder.java:1025) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitSimpleQueryGroup(SemanticQueryBuilder.java:277) + at org.hibernate.grammars.hql.HqlParser$SimpleQueryGroupContext.accept(HqlParser.java:2005) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitSelectStatement(SemanticQueryBuilder.java:492) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitStatement(SemanticQueryBuilder.java:451) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.buildSemanticModel(SemanticQueryBuilder.java:324) + at org.hibernate.query.hql.internal.StandardHqlTranslator.translate(StandardHqlTranslator.java:71) + at org.hibernate.query.internal.QueryInterpretationCacheStandardImpl.createHqlInterpretation(QueryInterpretationCacheStandardImpl.java:145) + at org.hibernate.query.internal.QueryInterpretationCacheStandardImpl.resolveHqlInterpretation(QueryInterpretationCacheStandardImpl.java:132) + at org.hibernate.query.spi.QueryEngine.interpretHql(QueryEngine.java:54) + at org.hibernate.internal.AbstractSharedSessionContract.interpretHql(AbstractSharedSessionContract.java:832) + at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:878) + ... 72 common frames omitted diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamController.java b/src/main/java/org/pkwmtt/examCalendar/ExamController.java index ad8bc5c..805bc59 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamController.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamController.java @@ -6,6 +6,7 @@ import org.pkwmtt.examCalendar.dto.ExamDto; import org.pkwmtt.examCalendar.entity.Exam; import org.pkwmtt.examCalendar.entity.ExamType; +import org.pkwmtt.examCalendar.mapper.ExamToExamDtoMapper; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -36,7 +37,6 @@ public ResponseEntity addExam(@RequestBody @Valid ExamDto examDto) { .buildAndExpand(id) .toUri(); return ResponseEntity.created(uri).build(); -// TODO: test not null validation in controller } /** @@ -73,15 +73,14 @@ public ResponseEntity getExam(@PathVariable int id) { * @param groups set of groups * @return 200 ok with list of exams for specific group */ -// @GetMapping("/by-groups") -// public ResponseEntity> getExams(@RequestParam Set groups){ -// return ResponseEntity.ok(examService.getExamByGroup(groups)); -// } + @GetMapping("/by-groups") + public ResponseEntity> getExams(@RequestParam Set groups){ + return ResponseEntity.ok(ExamToExamDtoMapper.mapToExamDto(examService.getExamByGroups(groups))); + } /** * @return 200 ok with list of available exam types */ -// should be moved to new controller? @GetMapping("/exam-types") public ResponseEntity> getExamTypes(){ return ResponseEntity.ok(examService.getExamTypes()); diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamService.java b/src/main/java/org/pkwmtt/examCalendar/ExamService.java index 3c7b509..4c2d7b9 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamService.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamService.java @@ -5,14 +5,18 @@ import org.pkwmtt.examCalendar.dto.ExamDto; import org.pkwmtt.examCalendar.entity.Exam; import org.pkwmtt.examCalendar.entity.ExamType; +import org.pkwmtt.examCalendar.entity.StudentGroup; import org.pkwmtt.examCalendar.mapper.ExamDtoToExamMapper; +import org.pkwmtt.examCalendar.mapper.ExamToExamDtoMapper; import org.pkwmtt.examCalendar.repository.ExamRepository; import org.pkwmtt.examCalendar.repository.ExamTypeRepository; +import org.pkwmtt.examCalendar.repository.GroupRepository; +import org.pkwmtt.exceptions.InvalidGroupIdentifierException; import org.pkwmtt.exceptions.NoSuchElementWithProvidedIdException; -import org.pkwmtt.exceptions.UnsupportedCountOfArgumentsException; import org.springframework.stereotype.Service; import java.util.*; +import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -20,15 +24,16 @@ public class ExamService { private final ExamRepository examRepository; - private final ExamDtoToExamMapper examMapper; + private final ExamDtoToExamMapper examDtoToExamMapper; private final ExamTypeRepository examTypeRepository; + private final GroupRepository groupRepository; /** * @param examDto details of exam * @return id of exam added to database */ public int addExam(ExamDto examDto) { - return examRepository.save(examMapper.mapToNewExam(examDto)).getExamId(); + return examRepository.save(examDtoToExamMapper.mapToNewExam(examDto)).getExamId(); } /** @@ -37,7 +42,7 @@ public int addExam(ExamDto examDto) { */ public void modifyExam(ExamDto examDto, int id) { examRepository.findById(id).orElseThrow(() -> new NoSuchElementWithProvidedIdException(id)); - examRepository.save(examMapper.mapToExistingExam(examDto, id)); + examRepository.save(examDtoToExamMapper.mapToExistingExam(examDto, id)); } /** @@ -56,6 +61,16 @@ public Exam getExamById(int id) { return examRepository.findById(id).orElseThrow(() -> new NoSuchElementWithProvidedIdException(id)); } + public Set getExamByGroups(Set groupNames){ + Set studentGroups = groupRepository.findAllByNameIn(groupNames); + Set groupNamesFromDatabase = studentGroups.stream().map(StudentGroup::getName).collect(Collectors.toSet()); + if(!groupNamesFromDatabase.equals(groupNames)){ + groupNames.removeAll(groupNamesFromDatabase); + throw new InvalidGroupIdentifierException(groupNames); + } + return examRepository.findByGroupsIn(studentGroups); + } + // /** // * @param groups set od groups (max 4) // * @return set of exams for specific groups diff --git a/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java b/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java index bd390ff..8374e85 100644 --- a/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java +++ b/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java @@ -4,6 +4,7 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; +import lombok.Builder; import lombok.Getter; import lombok.RequiredArgsConstructor; import org.pkwmtt.examCalendar.entity.StudentGroup; @@ -13,6 +14,7 @@ @Getter @RequiredArgsConstructor +@Builder public class ExamDto { @NotBlank @@ -29,6 +31,6 @@ public class ExamDto { @NotNull private final String examType; - @NotNull - private final Set examGroups; + @NotBlank + private final Set examGroups; } diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/StudentGroup.java b/src/main/java/org/pkwmtt/examCalendar/entity/StudentGroup.java index 1733807..c8568c1 100644 --- a/src/main/java/org/pkwmtt/examCalendar/entity/StudentGroup.java +++ b/src/main/java/org/pkwmtt/examCalendar/entity/StudentGroup.java @@ -23,6 +23,6 @@ public class StudentGroup { private String name; // FIXME: remove? - @ManyToMany(mappedBy = "groups") - private Set exams = new HashSet<>(); +// @ManyToMany(mappedBy = "groups") +// private Set exams = new HashSet<>(); } diff --git a/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java index 6e300e5..603e4f1 100644 --- a/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java +++ b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java @@ -3,10 +3,15 @@ import lombok.RequiredArgsConstructor; import org.pkwmtt.examCalendar.dto.ExamDto; import org.pkwmtt.examCalendar.entity.Exam; +import org.pkwmtt.examCalendar.entity.StudentGroup; import org.pkwmtt.examCalendar.repository.ExamTypeRepository; +import org.pkwmtt.examCalendar.repository.GroupRepository; import org.pkwmtt.exceptions.ExamTypeNotExistsException; import org.springframework.stereotype.Component; +import java.util.Set; +import java.util.stream.Collectors; + /** * maps ExamDto to Exam entity. Couldn't be utility class, because needs ExamTypeRepository to validate exam types */ @@ -14,6 +19,7 @@ @RequiredArgsConstructor public class ExamDtoToExamMapper { private final ExamTypeRepository examTypeRepository; + private final GroupRepository groupRepository; /** * @param examDto examDto object received from request @@ -26,7 +32,7 @@ public Exam mapToNewExam(ExamDto examDto) { .description(examDto.getDescription()) .examDate(examDto.getDate()) .examType(examTypeRepository.findByName(examDto.getExamType()).orElseThrow(() -> new ExamTypeNotExistsException(examDto.getExamType()))) - .groups(examDto.getExamGroups()) + .groups(groupRepository.findAllByNameIn(examDto.getExamGroups())) //TODO: validate if groups is not null .build(); } @@ -43,7 +49,7 @@ public Exam mapToExistingExam(ExamDto examDto, int id) { .description(examDto.getDescription()) .examDate(examDto.getDate()) .examType(examTypeRepository.findByName(examDto.getExamType()).orElseThrow(() -> new ExamTypeNotExistsException(examDto.getExamType()))) - .groups(examDto.getExamGroups()) + .groups(groupRepository.findAllByNameIn(examDto.getExamGroups())) .build(); } } diff --git a/src/main/java/org/pkwmtt/examCalendar/mapper/ExamToExamDtoMapper.java b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamToExamDtoMapper.java new file mode 100644 index 0000000..f249f8e --- /dev/null +++ b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamToExamDtoMapper.java @@ -0,0 +1,26 @@ +package org.pkwmtt.examCalendar.mapper; + +import org.pkwmtt.examCalendar.dto.ExamDto; +import org.pkwmtt.examCalendar.entity.Exam; +import org.pkwmtt.examCalendar.entity.StudentGroup; + +import java.util.Set; +import java.util.stream.Collectors; + +public class ExamToExamDtoMapper { + private ExamToExamDtoMapper() {} + + public static Set mapToExamDto(Set exams) { + return exams.stream().map(ExamToExamDtoMapper::mapToExamDto).collect(Collectors.toSet()); + } + + public static ExamDto mapToExamDto(Exam exam) { + return ExamDto.builder() + .title(exam.getTitle()) + .description(exam.getDescription()) + .date(exam.getExamDate()) + .examType(exam.getExamType().getName()) + .examGroups(exam.getGroups().stream().map(StudentGroup::getName).collect(Collectors.toSet())) + .build(); + } +} diff --git a/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java index 46eb47e..9da5247 100644 --- a/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java +++ b/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java @@ -1,6 +1,7 @@ package org.pkwmtt.examCalendar.repository; import org.pkwmtt.examCalendar.entity.Exam; +import org.pkwmtt.examCalendar.entity.StudentGroup; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -9,6 +10,9 @@ public interface ExamRepository extends JpaRepository { + @Query("SELECT e FROM Exam e JOIN FETCH e.examType JOIN FETCH e.groups g WHERE g IN :gr") + Set findByGroupsIn(@Param("gr") Set groups); + // /** // * fetch all data using one query // * @param group1 group identifier diff --git a/src/main/java/org/pkwmtt/examCalendar/repository/GroupRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/GroupRepository.java index ee6b322..7a9e4dd 100644 --- a/src/main/java/org/pkwmtt/examCalendar/repository/GroupRepository.java +++ b/src/main/java/org/pkwmtt/examCalendar/repository/GroupRepository.java @@ -3,5 +3,8 @@ import org.pkwmtt.examCalendar.entity.StudentGroup; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Set; + public interface GroupRepository extends JpaRepository { + Set findAllByNameIn(Set names); } \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/exceptions/InvalidGroupIdentifierException.java b/src/main/java/org/pkwmtt/exceptions/InvalidGroupIdentifierException.java index 4faadac..a47bf28 100644 --- a/src/main/java/org/pkwmtt/exceptions/InvalidGroupIdentifierException.java +++ b/src/main/java/org/pkwmtt/exceptions/InvalidGroupIdentifierException.java @@ -1,7 +1,13 @@ package org.pkwmtt.exceptions; +import java.util.Set; + public class InvalidGroupIdentifierException extends RuntimeException { public InvalidGroupIdentifierException(String groupIdentifier) { super("Invalid group identifier: " + groupIdentifier); } + + public InvalidGroupIdentifierException(Set groupIdentifiers) { + super("Invalid group identifiers: " + groupIdentifiers.toString()); + } } diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java index 0c59328..b27640d 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java @@ -2,7 +2,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -12,12 +11,10 @@ import org.pkwmtt.examCalendar.entity.StudentGroup; import org.pkwmtt.examCalendar.mapper.ExamDtoToExamMapper; import org.pkwmtt.examCalendar.repository.ExamRepository; -import org.pkwmtt.exceptions.UnsupportedCountOfArgumentsException; import java.lang.reflect.Field; import java.time.LocalDateTime; import java.util.*; -import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; From 2179a2354ce82ee31e50544b3761a7a9b699e003 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Wed, 20 Aug 2025 14:38:58 +0200 Subject: [PATCH 036/116] fix some tests --- logs/app.log | 414 ++++++++++++++++++ .../org/pkwmtt/examCalendar/dto/ExamDto.java | 7 +- .../org/pkwmtt/examCalendar/entity/Exam.java | 23 +- .../mapper/ExamDtoToExamMapper.java | 19 +- .../exceptions/NoGroupsProvidedException.java | 7 + .../examCalendar/ExamControllerTest.java | 5 +- .../pkwmtt/examCalendar/ExamServiceTest.java | 2 +- .../pkwmtt/examCalendar/dto/ExamDtoTest.java | 29 +- .../mapper/ExamDtoToExamMapperTest.java | 41 +- 9 files changed, 489 insertions(+), 58 deletions(-) create mode 100644 src/main/java/org/pkwmtt/exceptions/NoGroupsProvidedException.java diff --git a/logs/app.log b/logs/app.log index 993075d..2e8f428 100644 --- a/logs/app.log +++ b/logs/app.log @@ -377,3 +377,417 @@ Caused by: org.hibernate.query.SemanticException: Multi valued paths are only al at org.hibernate.internal.AbstractSharedSessionContract.interpretHql(AbstractSharedSessionContract.java:832) at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:878) ... 72 common frames omitted +2025-08-20 13:10:59 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-20 13:11:01 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-20 13:11:01 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:11:01 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:11:01 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:11:01 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:11:01 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:11:01 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:11:01 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:11:01 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:11:01 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:11:01 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:11:01 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:11:01 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:11:01 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:11:01 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:11:01 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:11:01 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:11:01 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:11:01 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:11:01 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:11:01 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:11:01 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:11:01 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:11:02 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-20 13:23:25 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-20 13:23:27 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-20 13:23:27 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:23:27 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:23:27 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:23:27 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:23:27 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:23:27 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:23:27 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:23:27 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:23:27 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:23:27 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:23:27 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:23:27 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:23:27 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:23:27 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:23:27 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:23:27 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:23:27 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:23:27 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:23:27 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:23:27 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:23:27 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:23:27 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 13:23:28 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-20 14:04:04 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-20 14:04:05 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-20 14:04:05 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:04:05 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:04:05 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:04:05 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:04:05 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:04:05 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:04:05 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:04:05 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:04:05 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:04:05 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:04:05 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:04:05 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:04:05 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:04:05 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:04:05 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:04:05 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:04:05 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:04:06 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:04:06 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:04:06 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:04:06 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:04:06 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:04:06 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-20 14:18:36 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-20 14:18:37 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-20 14:18:37 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:18:37 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:18:37 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:18:37 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:18:37 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:18:37 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:18:37 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:18:37 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:18:37 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:18:37 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:18:37 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:18:37 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:18:37 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:18:37 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:18:37 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:18:37 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:18:37 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:18:37 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:18:37 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:18:38 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:18:38 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:18:38 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:18:38 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-20 14:19:20 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-20 14:19:21 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-20 14:19:21 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:19:21 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:19:21 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:19:21 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:19:21 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:19:21 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:19:22 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:19:22 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:19:22 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:19:22 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:19:22 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:19:22 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:19:22 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:19:22 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:19:22 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:19:22 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:19:22 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:19:22 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:19:22 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:19:22 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:19:22 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:19:22 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:19:22 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-20 14:22:16 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-20 14:22:18 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-20 14:22:18 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:22:18 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:22:18 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:22:18 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:22:18 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:22:18 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:22:18 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:22:18 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:22:18 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:22:18 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:22:18 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:22:18 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:22:18 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:22:18 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:22:18 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:22:18 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:22:18 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:22:18 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:22:18 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:22:18 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:22:18 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:22:18 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-20 14:22:19 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties diff --git a/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java b/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java index 8374e85..e1c9d84 100644 --- a/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java +++ b/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java @@ -1,9 +1,6 @@ package org.pkwmtt.examCalendar.dto; -import jakarta.validation.constraints.Future; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; +import jakarta.validation.constraints.*; import lombok.Builder; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -31,6 +28,6 @@ public class ExamDto { @NotNull private final String examType; - @NotBlank + @NotEmpty private final Set examGroups; } diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java b/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java index 9888a3d..82b0698 100644 --- a/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java +++ b/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java @@ -5,6 +5,7 @@ import lombok.Builder; import lombok.Getter; import lombok.RequiredArgsConstructor; +import org.pkwmtt.exceptions.InvalidGroupIdentifierException; import java.time.LocalDateTime; import java.util.HashSet; @@ -42,15 +43,15 @@ public class Exam { ) private Set groups = new HashSet<>(); -// @SuppressWarnings("unused") -// public static class Builder { -// public Exam build() { -// // max length of group identifier is 6 -// Arrays.stream(examGroups.split(", ")).forEach(group -> { -// if(group.length() > 6) -// throw new InvalidGroupIdentifierException(group); -// }); -// return new Exam(examId, title, description, date, examGroups, examType); -// } -// } + @SuppressWarnings("unused") + public static class Builder { + public Exam build() { + // max length of group identifier is 6 + groups.forEach(group -> { + if(group.getName().length() > 6) + throw new InvalidGroupIdentifierException(group.getName()); + }); + return new Exam(examId, title, description, examDate, examType, groups); + } + } } \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java index 603e4f1..75fcb56 100644 --- a/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java +++ b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java @@ -7,6 +7,7 @@ import org.pkwmtt.examCalendar.repository.ExamTypeRepository; import org.pkwmtt.examCalendar.repository.GroupRepository; import org.pkwmtt.exceptions.ExamTypeNotExistsException; +import org.pkwmtt.exceptions.NoGroupsProvidedException; import org.springframework.stereotype.Component; import java.util.Set; @@ -27,12 +28,17 @@ public class ExamDtoToExamMapper { * Also contains examType field converted from String do ExamType */ public Exam mapToNewExam(ExamDto examDto) { + Set studentGroups = groupRepository.findAllByNameIn(examDto.getExamGroups()); + if(studentGroups.isEmpty()) + throw new NoGroupsProvidedException(); return Exam.builder() .title(examDto.getTitle()) .description(examDto.getDescription()) .examDate(examDto.getDate()) - .examType(examTypeRepository.findByName(examDto.getExamType()).orElseThrow(() -> new ExamTypeNotExistsException(examDto.getExamType()))) - .groups(groupRepository.findAllByNameIn(examDto.getExamGroups())) //TODO: validate if groups is not null + .examType(examTypeRepository.findByName(examDto.getExamType()) + .orElseThrow(() -> new ExamTypeNotExistsException(examDto.getExamType())) + ) + .groups(studentGroups) .build(); } @@ -43,13 +49,18 @@ public Exam mapToNewExam(ExamDto examDto) { * Also contains examType field converted from String do ExamType */ public Exam mapToExistingExam(ExamDto examDto, int id) { + Set studentGroups = groupRepository.findAllByNameIn(examDto.getExamGroups()); + if(studentGroups.isEmpty()) + throw new NoGroupsProvidedException(); return Exam.builder() .examId(id) .title(examDto.getTitle()) .description(examDto.getDescription()) .examDate(examDto.getDate()) - .examType(examTypeRepository.findByName(examDto.getExamType()).orElseThrow(() -> new ExamTypeNotExistsException(examDto.getExamType()))) - .groups(groupRepository.findAllByNameIn(examDto.getExamGroups())) + .examType(examTypeRepository.findByName(examDto.getExamType()) + .orElseThrow(() -> new ExamTypeNotExistsException(examDto.getExamType())) + ) + .groups(studentGroups) .build(); } } diff --git a/src/main/java/org/pkwmtt/exceptions/NoGroupsProvidedException.java b/src/main/java/org/pkwmtt/exceptions/NoGroupsProvidedException.java new file mode 100644 index 0000000..b99f44b --- /dev/null +++ b/src/main/java/org/pkwmtt/exceptions/NoGroupsProvidedException.java @@ -0,0 +1,7 @@ +package org.pkwmtt.exceptions; + +public class NoGroupsProvidedException extends RuntimeException { + public NoGroupsProvidedException() { + super("groups set is empty"); + } +} diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java index 6cd65b7..3fb89c8 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java @@ -512,15 +512,12 @@ private Exam createExampleExam(ExamType type) { * @return created ExamDto */ private ExamDto createExampleExamDto(String examTypeName) { - Set examGroups = new HashSet<>(); - examGroups.add(StudentGroup.builder().name("11K1").build()); - examGroups.add(StudentGroup.builder().name("L01").build()); return new ExamDto( "Math exam", "first exam", LocalDateTime.now().plusDays(1), examTypeName, - examGroups + Set.of("11k1", "L01") ); } diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java index b27640d..8afc870 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java @@ -43,7 +43,7 @@ void addExam() { "desc", LocalDateTime.now().plusDays(1), "Exam", - examGroups + Set.of("12k2", "13L1") ); Exam exam = Exam.builder() .title("Math exam") diff --git a/src/test/java/org/pkwmtt/examCalendar/dto/ExamDtoTest.java b/src/test/java/org/pkwmtt/examCalendar/dto/ExamDtoTest.java index e92418c..0972256 100644 --- a/src/test/java/org/pkwmtt/examCalendar/dto/ExamDtoTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/dto/ExamDtoTest.java @@ -23,20 +23,6 @@ public ExamDtoTest() { this.validator = Validation.buildDefaultValidatorFactory().getValidator(); } - @Mock - private ExamDto examDto; - - private Set groups; - - @BeforeEach - void setup(){ - StudentGroup group = StudentGroup.builder() - .name("12K2") - .build(); - groups = new HashSet<>(); - groups.add(group); - } - @Test void validData() { // given @@ -45,7 +31,7 @@ void validData() { "First exam", LocalDateTime.now().plusDays(1), "exam", - groups + Set.of("12K2") ); // when, then assertTrue(validator.validate(examDto).isEmpty()); @@ -61,7 +47,7 @@ void emptyStringTitle() { "First exam", LocalDateTime.now().plusDays(1), "exam", - groups + Set.of("12K2") ); // when Set> violations = validator.validate(examDto); @@ -73,14 +59,12 @@ void emptyStringTitle() { @Test void emptyExamGroups() { // given - // clear groups set - groups.clear(); ExamDto examDto = new ExamDto( "Math exam", "First exam", LocalDateTime.now().plusDays(1), "exam", - groups + Set.of() ); // when Set> violations = validator.validate(examDto); @@ -100,7 +84,7 @@ void toLongStringTitle() { "First exam", LocalDateTime.now().plusDays(1), "exam", - groups + Set.of("12K2") ); // when Set> violations = validator.validate(examDto); @@ -118,7 +102,7 @@ void toLongDescription() { "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", LocalDateTime.now().plusDays(1), "exam", - groups + Set.of("12K2") ); // when Set> violations = validator.validate(examDto); @@ -128,6 +112,7 @@ void toLongDescription() { } // TODO: change to too large set +// TODO: check if groups exists // @Test // void toLongExamGroups() { // // given @@ -155,7 +140,7 @@ void dateNotInFuture() { "First exam", LocalDateTime.now().minusHours(1), "exam", - groups + Set.of("12K2") ); // when Set> violations = validator.validate(examDto); diff --git a/src/test/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapperTest.java b/src/test/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapperTest.java index 0dd27f3..3ecc9c1 100644 --- a/src/test/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapperTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapperTest.java @@ -1,6 +1,7 @@ package org.pkwmtt.examCalendar.mapper; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -11,12 +12,14 @@ import org.pkwmtt.examCalendar.entity.ExamType; import org.pkwmtt.examCalendar.entity.StudentGroup; import org.pkwmtt.examCalendar.repository.ExamTypeRepository; +import org.pkwmtt.examCalendar.repository.GroupRepository; import org.pkwmtt.exceptions.InvalidGroupIdentifierException; import java.time.LocalDateTime; import java.util.HashSet; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.when; @@ -27,6 +30,9 @@ class ExamDtoToExamMapperTest { @Mock private ExamTypeRepository examTypeRepository; + @Mock + private GroupRepository groupRepository; + @InjectMocks private ExamDtoToExamMapper examDtoToExamMapper; @@ -50,18 +56,23 @@ void setup(){ void isFieldsMappedProperlyToNewExam() { // given String examTypeName = "exam"; + String groupIdentifier = "12K2"; ExamDto examDto = new ExamDto( "Math exam", "Linear algebra", LocalDateTime.now().plusDays(1), - "12K2, 13S1", - groups + examTypeName, + Set.of("12K2") ); when(examTypeRepository.findByName(examTypeName)).thenReturn( Optional.of(ExamType.builder() .name(examTypeName) .build()) ); + + when(groupRepository.findAllByNameIn(Set.of(groupIdentifier))).thenReturn( + new HashSet<>(groups) + ); // when Exam exam = examDtoToExamMapper.mapToNewExam(examDto); // then @@ -69,26 +80,28 @@ void isFieldsMappedProperlyToNewExam() { assertEquals(examDto.getTitle(), exam.getTitle()); assertEquals(examDto.getDescription(), exam.getDescription()); assertEquals(examDto.getDate(), exam.getExamDate()); - assertEquals(examDto.getExamGroups(), exam.getGroups()); + assertEquals(examDto.getExamGroups(), exam.getGroups().stream().map(StudentGroup::getName).collect(Collectors.toSet())); assertEquals(examTypeName, exam.getExamType().getName()); // test null id assertNull(exam.getExamId()); } +// TODO: change to checking if exam group exists @Test + @Disabled("new version required") void ShouldThrowExceptionWhenGroupIdentifierIsLongerThanSixCharactersForNewExam() { // given - StudentGroup group = StudentGroup.builder() - .name("Not_Valid_Identifier") - .build(); - groups.add(group); +// StudentGroup group = StudentGroup.builder() +// .name("Not_Valid_Identifier") +// .build(); +// groups.add(group); String examTypeName = "exam"; ExamDto examDto = new ExamDto( "Math exam", "Linear algebra", LocalDateTime.now().plusDays(1), examTypeName, - groups + Set.of("Not_Valid_Identifier") ); when(examTypeRepository.findByName(examTypeName)).thenReturn( Optional.of(ExamType.builder() @@ -111,18 +124,23 @@ void isFieldsMappedProperlyToExistingExam() { // given int examId = 1; examTypeName = "exam"; + String groupIdentifier = "12K2"; examDto = new ExamDto( "Math exam", "Linear algebra", LocalDateTime.now().plusDays(1), examTypeName, - groups + Set.of(groupIdentifier) ); when(examTypeRepository.findByName(examTypeName)).thenReturn( Optional.of(ExamType.builder() .name(examTypeName) .build()) ); + + when(groupRepository.findAllByNameIn(Set.of(groupIdentifier))).thenReturn( + new HashSet<>(groups) + ); // when Exam exam = examDtoToExamMapper.mapToExistingExam(examDto, examId); // then @@ -131,13 +149,14 @@ void isFieldsMappedProperlyToExistingExam() { assertEquals(examDto.getTitle(), exam.getTitle()); assertEquals(examDto.getDescription(), exam.getDescription()); assertEquals(examDto.getDate(), exam.getExamDate()); - assertEquals(examDto.getExamGroups(), exam.getGroups()); + assertEquals(examDto.getExamGroups(), exam.getGroups().stream().map(StudentGroup::getName).collect(Collectors.toSet())); assertEquals(examTypeName, exam.getExamType().getName()); // test not null id assertNotNull(exam.getExamId()); } @Test + @Disabled("new version required") void ShouldThrowExceptionWhenGroupIdentifierIsLongerThanSixCharactersForExistingExam() { // given int examId = 1; @@ -151,7 +170,7 @@ void ShouldThrowExceptionWhenGroupIdentifierIsLongerThanSixCharactersForExisting "Linear algebra", LocalDateTime.now().plusDays(1), examTypeName, - groups + Set.of("12K2") ); when(examTypeRepository.findByName(examTypeName)).thenReturn( Optional.of(ExamType.builder() From e59a1e2b140f215469b8e273f4a231134a5daf71 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Wed, 20 Aug 2025 14:59:23 +0200 Subject: [PATCH 037/116] move verification from mapper to service --- .../pkwmtt/examCalendar/ExamController.java | 4 +- .../org/pkwmtt/examCalendar/ExamService.java | 35 +++++++++++--- ...toToExamMapper.java => ExamDtoMapper.java} | 46 +++++++++---------- .../mapper/ExamToExamDtoMapper.java | 26 ----------- .../pkwmtt/examCalendar/ExamServiceTest.java | 14 +++--- ...MapperTest.java => ExamDtoMapperTest.java} | 12 ++--- 6 files changed, 66 insertions(+), 71 deletions(-) rename src/main/java/org/pkwmtt/examCalendar/mapper/{ExamDtoToExamMapper.java => ExamDtoMapper.java} (52%) delete mode 100644 src/main/java/org/pkwmtt/examCalendar/mapper/ExamToExamDtoMapper.java rename src/test/java/org/pkwmtt/examCalendar/mapper/{ExamDtoToExamMapperTest.java => ExamDtoMapperTest.java} (94%) diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamController.java b/src/main/java/org/pkwmtt/examCalendar/ExamController.java index 805bc59..5b59274 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamController.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamController.java @@ -6,7 +6,7 @@ import org.pkwmtt.examCalendar.dto.ExamDto; import org.pkwmtt.examCalendar.entity.Exam; import org.pkwmtt.examCalendar.entity.ExamType; -import org.pkwmtt.examCalendar.mapper.ExamToExamDtoMapper; +import org.pkwmtt.examCalendar.mapper.ExamDtoMapper; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -75,7 +75,7 @@ public ResponseEntity getExam(@PathVariable int id) { */ @GetMapping("/by-groups") public ResponseEntity> getExams(@RequestParam Set groups){ - return ResponseEntity.ok(ExamToExamDtoMapper.mapToExamDto(examService.getExamByGroups(groups))); + return ResponseEntity.ok(ExamDtoMapper.mapToExamDto(examService.getExamByGroups(groups))); } /** diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamService.java b/src/main/java/org/pkwmtt/examCalendar/ExamService.java index 4c2d7b9..e02dff0 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamService.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamService.java @@ -6,12 +6,13 @@ import org.pkwmtt.examCalendar.entity.Exam; import org.pkwmtt.examCalendar.entity.ExamType; import org.pkwmtt.examCalendar.entity.StudentGroup; -import org.pkwmtt.examCalendar.mapper.ExamDtoToExamMapper; -import org.pkwmtt.examCalendar.mapper.ExamToExamDtoMapper; +import org.pkwmtt.examCalendar.mapper.ExamDtoMapper; import org.pkwmtt.examCalendar.repository.ExamRepository; import org.pkwmtt.examCalendar.repository.ExamTypeRepository; import org.pkwmtt.examCalendar.repository.GroupRepository; +import org.pkwmtt.exceptions.ExamTypeNotExistsException; import org.pkwmtt.exceptions.InvalidGroupIdentifierException; +import org.pkwmtt.exceptions.NoGroupsProvidedException; import org.pkwmtt.exceptions.NoSuchElementWithProvidedIdException; import org.springframework.stereotype.Service; @@ -24,7 +25,6 @@ public class ExamService { private final ExamRepository examRepository; - private final ExamDtoToExamMapper examDtoToExamMapper; private final ExamTypeRepository examTypeRepository; private final GroupRepository groupRepository; @@ -33,7 +33,17 @@ public class ExamService { * @return id of exam added to database */ public int addExam(ExamDto examDto) { - return examRepository.save(examDtoToExamMapper.mapToNewExam(examDto)).getExamId(); +// check if groups set isn't empty + Set groups = groupRepository.findAllByNameIn(examDto.getExamGroups()); + if (groups.isEmpty()) + throw new NoGroupsProvidedException(); + +// check if exam type exists + ExamType examType = examTypeRepository.findByName(examDto.getExamType()) + .orElseThrow(() -> new ExamTypeNotExistsException(examDto.getExamType())); + +// save exam in repository and return id of created exam + return examRepository.save(ExamDtoMapper.mapToNewExam(examDto, groups, examType)).getExamId(); } /** @@ -41,8 +51,19 @@ public int addExam(ExamDto examDto) { * @param id of exam that need to be modified */ public void modifyExam(ExamDto examDto, int id) { +// check if exam which would be modified exists examRepository.findById(id).orElseThrow(() -> new NoSuchElementWithProvidedIdException(id)); - examRepository.save(examDtoToExamMapper.mapToExistingExam(examDto, id)); + +// check if groups set isn't empty + Set groups = groupRepository.findAllByNameIn(examDto.getExamGroups()); + if (groups.isEmpty()) + throw new NoGroupsProvidedException(); + +// check if exam type exists + ExamType examType = examTypeRepository.findByName(examDto.getExamType()) + .orElseThrow(() -> new ExamTypeNotExistsException(examDto.getExamType())); + + examRepository.save(ExamDtoMapper.mapToExistingExam(examDto, groups, examType, id)); } /** @@ -61,10 +82,10 @@ public Exam getExamById(int id) { return examRepository.findById(id).orElseThrow(() -> new NoSuchElementWithProvidedIdException(id)); } - public Set getExamByGroups(Set groupNames){ + public Set getExamByGroups(Set groupNames) { Set studentGroups = groupRepository.findAllByNameIn(groupNames); Set groupNamesFromDatabase = studentGroups.stream().map(StudentGroup::getName).collect(Collectors.toSet()); - if(!groupNamesFromDatabase.equals(groupNames)){ + if (!groupNamesFromDatabase.equals(groupNames)) { groupNames.removeAll(groupNamesFromDatabase); throw new InvalidGroupIdentifierException(groupNames); } diff --git a/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoMapper.java similarity index 52% rename from src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java rename to src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoMapper.java index 75fcb56..01dae15 100644 --- a/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapper.java +++ b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoMapper.java @@ -3,11 +3,8 @@ import lombok.RequiredArgsConstructor; import org.pkwmtt.examCalendar.dto.ExamDto; import org.pkwmtt.examCalendar.entity.Exam; +import org.pkwmtt.examCalendar.entity.ExamType; import org.pkwmtt.examCalendar.entity.StudentGroup; -import org.pkwmtt.examCalendar.repository.ExamTypeRepository; -import org.pkwmtt.examCalendar.repository.GroupRepository; -import org.pkwmtt.exceptions.ExamTypeNotExistsException; -import org.pkwmtt.exceptions.NoGroupsProvidedException; import org.springframework.stereotype.Component; import java.util.Set; @@ -18,27 +15,21 @@ */ @Component @RequiredArgsConstructor -public class ExamDtoToExamMapper { - private final ExamTypeRepository examTypeRepository; - private final GroupRepository groupRepository; +public class ExamDtoMapper { + private ExamDtoMapper examDtoMapper; /** * @param examDto examDto object received from request * @return Exam entity WITHOUT examId which should be assigned by database * Also contains examType field converted from String do ExamType */ - public Exam mapToNewExam(ExamDto examDto) { - Set studentGroups = groupRepository.findAllByNameIn(examDto.getExamGroups()); - if(studentGroups.isEmpty()) - throw new NoGroupsProvidedException(); + public static Exam mapToNewExam(ExamDto examDto, Set groups, ExamType examType) { return Exam.builder() .title(examDto.getTitle()) .description(examDto.getDescription()) .examDate(examDto.getDate()) - .examType(examTypeRepository.findByName(examDto.getExamType()) - .orElseThrow(() -> new ExamTypeNotExistsException(examDto.getExamType())) - ) - .groups(studentGroups) + .examType(examType) + .groups(groups) .build(); } @@ -48,19 +39,28 @@ public Exam mapToNewExam(ExamDto examDto) { * @return Exam entity WITH examId that allow to update entity in database instead of creating new one * Also contains examType field converted from String do ExamType */ - public Exam mapToExistingExam(ExamDto examDto, int id) { - Set studentGroups = groupRepository.findAllByNameIn(examDto.getExamGroups()); - if(studentGroups.isEmpty()) - throw new NoGroupsProvidedException(); + public static Exam mapToExistingExam(ExamDto examDto, Set groups, ExamType examType, int id) { return Exam.builder() .examId(id) .title(examDto.getTitle()) .description(examDto.getDescription()) .examDate(examDto.getDate()) - .examType(examTypeRepository.findByName(examDto.getExamType()) - .orElseThrow(() -> new ExamTypeNotExistsException(examDto.getExamType())) - ) - .groups(studentGroups) + .examType(examType) + .groups(groups) + .build(); + } + + public static Set mapToExamDto(Set exams) { + return exams.stream().map(ExamDtoMapper::mapToExamDto).collect(Collectors.toSet()); + } + + public static ExamDto mapToExamDto(Exam exam) { + return ExamDto.builder() + .title(exam.getTitle()) + .description(exam.getDescription()) + .date(exam.getExamDate()) + .examType(exam.getExamType().getName()) + .examGroups(exam.getGroups().stream().map(StudentGroup::getName).collect(Collectors.toSet())) .build(); } } diff --git a/src/main/java/org/pkwmtt/examCalendar/mapper/ExamToExamDtoMapper.java b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamToExamDtoMapper.java deleted file mode 100644 index f249f8e..0000000 --- a/src/main/java/org/pkwmtt/examCalendar/mapper/ExamToExamDtoMapper.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.pkwmtt.examCalendar.mapper; - -import org.pkwmtt.examCalendar.dto.ExamDto; -import org.pkwmtt.examCalendar.entity.Exam; -import org.pkwmtt.examCalendar.entity.StudentGroup; - -import java.util.Set; -import java.util.stream.Collectors; - -public class ExamToExamDtoMapper { - private ExamToExamDtoMapper() {} - - public static Set mapToExamDto(Set exams) { - return exams.stream().map(ExamToExamDtoMapper::mapToExamDto).collect(Collectors.toSet()); - } - - public static ExamDto mapToExamDto(Exam exam) { - return ExamDto.builder() - .title(exam.getTitle()) - .description(exam.getDescription()) - .date(exam.getExamDate()) - .examType(exam.getExamType().getName()) - .examGroups(exam.getGroups().stream().map(StudentGroup::getName).collect(Collectors.toSet())) - .build(); - } -} diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java index 8afc870..0617441 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java @@ -9,7 +9,7 @@ import org.pkwmtt.examCalendar.entity.Exam; import org.pkwmtt.examCalendar.entity.ExamType; import org.pkwmtt.examCalendar.entity.StudentGroup; -import org.pkwmtt.examCalendar.mapper.ExamDtoToExamMapper; +import org.pkwmtt.examCalendar.mapper.ExamDtoMapper; import org.pkwmtt.examCalendar.repository.ExamRepository; import java.lang.reflect.Field; @@ -26,7 +26,7 @@ class ExamServiceTest { private ExamRepository examRepository; @Mock - private ExamDtoToExamMapper examDtoToExamMapper; + private ExamDtoMapper examDtoMapper; @InjectMocks private ExamService examService; @@ -52,7 +52,7 @@ void addExam() { .groups(examGroups) .examType(new ExamType(1, "Exam")) .build(); - when(examDtoToExamMapper.mapToNewExam(examDto)).thenReturn(exam); + when(examDtoMapper.mapToNewExam(examDto)).thenReturn(exam); // assign exam id in repository when(examRepository.save(exam)).thenAnswer(invocation -> { @@ -67,7 +67,7 @@ void addExam() { // then assertEquals(examId, result); verify(examRepository).save(exam); - verify(examDtoToExamMapper).mapToNewExam(examDto); + verify(examDtoMapper).mapToNewExam(examDto); } /************************************************************************************/ @@ -79,12 +79,12 @@ void shouldModifyExamWhenIdExists() { ExamDto examDto = mock(ExamDto.class); Exam exam = mock(Exam.class); - when(examDtoToExamMapper.mapToExistingExam(examDto, examId)).thenReturn(exam); + when(examDtoMapper.mapToExistingExam(examDto, examId)).thenReturn(exam); when(examRepository.findById(examId)).thenReturn(Optional.of(exam)); // when examService.modifyExam(examDto, examId); // then - verify(examDtoToExamMapper).mapToExistingExam(examDto, examId); + verify(examDtoMapper).mapToExistingExam(examDto, examId); verify(examRepository).save(exam); } @@ -100,7 +100,7 @@ void shouldThrowWhenExamIdNotExists() { () -> examService.modifyExam(examDto, examId) ); // then - verify(examDtoToExamMapper, never()).mapToExistingExam(examDto, examId); + verify(examDtoMapper, never()).mapToExistingExam(examDto, examId); verify(examRepository, never()).save(any()); assertEquals("Exam not found", exception.getMessage()); } diff --git a/src/test/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapperTest.java b/src/test/java/org/pkwmtt/examCalendar/mapper/ExamDtoMapperTest.java similarity index 94% rename from src/test/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapperTest.java rename to src/test/java/org/pkwmtt/examCalendar/mapper/ExamDtoMapperTest.java index 3ecc9c1..2224e5c 100644 --- a/src/test/java/org/pkwmtt/examCalendar/mapper/ExamDtoToExamMapperTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/mapper/ExamDtoMapperTest.java @@ -25,7 +25,7 @@ import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) -class ExamDtoToExamMapperTest { +class ExamDtoMapperTest { @Mock private ExamTypeRepository examTypeRepository; @@ -34,7 +34,7 @@ class ExamDtoToExamMapperTest { private GroupRepository groupRepository; @InjectMocks - private ExamDtoToExamMapper examDtoToExamMapper; + private ExamDtoMapper examDtoMapper; private ExamDto examDto; private String examTypeName; @@ -74,7 +74,7 @@ void isFieldsMappedProperlyToNewExam() { new HashSet<>(groups) ); // when - Exam exam = examDtoToExamMapper.mapToNewExam(examDto); + Exam exam = examDtoMapper.mapToNewExam(examDto); // then // test fields assertEquals(examDto.getTitle(), exam.getTitle()); @@ -111,7 +111,7 @@ void ShouldThrowExceptionWhenGroupIdentifierIsLongerThanSixCharactersForNewExam( // then RuntimeException exception = assertThrows( InvalidGroupIdentifierException.class, - () -> examDtoToExamMapper.mapToNewExam(examDto) + () -> examDtoMapper.mapToNewExam(examDto) ); assertEquals("Invalid group identifier: Not_Valid_Identifier", exception.getMessage()); } @@ -142,7 +142,7 @@ void isFieldsMappedProperlyToExistingExam() { new HashSet<>(groups) ); // when - Exam exam = examDtoToExamMapper.mapToExistingExam(examDto, examId); + Exam exam = examDtoMapper.mapToExistingExam(examDto, examId); // then // test fields assertEquals(examId, exam.getExamId()); @@ -180,7 +180,7 @@ void ShouldThrowExceptionWhenGroupIdentifierIsLongerThanSixCharactersForExisting // then RuntimeException exception = assertThrows( InvalidGroupIdentifierException.class, - () -> examDtoToExamMapper.mapToExistingExam(examDto, examId) + () -> examDtoMapper.mapToExistingExam(examDto, examId) ); assertEquals("Invalid group identifier: Not_Valid_Identifier", exception.getMessage()); } From 923da72c6e61426b3fe935d679e58ea1207c8045 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Wed, 20 Aug 2025 19:20:08 +0200 Subject: [PATCH 038/116] add examGroups verification --- .../org/pkwmtt/examCalendar/ExamService.java | 77 +++++++++++-------- .../org/pkwmtt/examCalendar/entity/Exam.java | 9 +-- 2 files changed, 50 insertions(+), 36 deletions(-) diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamService.java b/src/main/java/org/pkwmtt/examCalendar/ExamService.java index e02dff0..e1db9ab 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamService.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamService.java @@ -1,5 +1,6 @@ package org.pkwmtt.examCalendar; +import com.fasterxml.jackson.core.JsonProcessingException; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.pkwmtt.examCalendar.dto.ExamDto; @@ -10,10 +11,8 @@ import org.pkwmtt.examCalendar.repository.ExamRepository; import org.pkwmtt.examCalendar.repository.ExamTypeRepository; import org.pkwmtt.examCalendar.repository.GroupRepository; -import org.pkwmtt.exceptions.ExamTypeNotExistsException; -import org.pkwmtt.exceptions.InvalidGroupIdentifierException; -import org.pkwmtt.exceptions.NoGroupsProvidedException; -import org.pkwmtt.exceptions.NoSuchElementWithProvidedIdException; +import org.pkwmtt.exceptions.*; +import org.pkwmtt.timetable.TimetableService; import org.springframework.stereotype.Service; import java.util.*; @@ -27,16 +26,16 @@ public class ExamService { private final ExamRepository examRepository; private final ExamTypeRepository examTypeRepository; private final GroupRepository groupRepository; + private final TimetableService timetableService; /** * @param examDto details of exam * @return id of exam added to database */ public int addExam(ExamDto examDto) { -// check if groups set isn't empty + + verifyAndUpdateExamGroups(examDto); Set groups = groupRepository.findAllByNameIn(examDto.getExamGroups()); - if (groups.isEmpty()) - throw new NoGroupsProvidedException(); // check if exam type exists ExamType examType = examTypeRepository.findByName(examDto.getExamType()) @@ -54,10 +53,8 @@ public void modifyExam(ExamDto examDto, int id) { // check if exam which would be modified exists examRepository.findById(id).orElseThrow(() -> new NoSuchElementWithProvidedIdException(id)); -// check if groups set isn't empty + verifyAndUpdateExamGroups(examDto); Set groups = groupRepository.findAllByNameIn(examDto.getExamGroups()); - if (groups.isEmpty()) - throw new NoGroupsProvidedException(); // check if exam type exists ExamType examType = examTypeRepository.findByName(examDto.getExamType()) @@ -83,6 +80,7 @@ public Exam getExamById(int id) { } public Set getExamByGroups(Set groupNames) { +// validate provided groups Set studentGroups = groupRepository.findAllByNameIn(groupNames); Set groupNamesFromDatabase = studentGroups.stream().map(StudentGroup::getName).collect(Collectors.toSet()); if (!groupNamesFromDatabase.equals(groupNames)) { @@ -92,31 +90,48 @@ public Set getExamByGroups(Set groupNames) { return examRepository.findByGroupsIn(studentGroups); } -// /** -// * @param groups set od groups (max 4) -// * @return set of exams for specific groups -// */ -// public Set getExamByGroup(Set groups) { -// if (groups.size() > 4 || groups.isEmpty()) -// throw new UnsupportedCountOfArgumentsException(1, 5, groups.size()); -// List groupList = new ArrayList<>(groups); -// return switch (groupList.size()) { -// case 4 -> examRepository.findExamsByGroupsIdentifier( -// groupList.get(0), groupList.get(1), groupList.get(2), groupList.get(3)); -// case 3 -> examRepository.findExamsByGroupsIdentifier( -// groupList.get(0), groupList.get(1), groupList.get(2)); -// case 2 -> examRepository.findExamsByGroupsIdentifier( -// groupList.get(0), groupList.get(1)); -// case 1 -> examRepository.findExamsByGroupsIdentifier( -// groupList.get(0)); -// default -> Set.of(); -// }; -// } - /** * @return list of examTypes */ public List getExamTypes() { return examTypeRepository.findAll(); } + + + private Set getGroupsFromTimetableService() throws JsonProcessingException { + List generalGroups = timetableService.getGeneralGroupList(); + Set allGroups = new HashSet<>(generalGroups); + for (String groupName : generalGroups) + allGroups.addAll(timetableService.getAvailableSubGroups(groupName)); + return allGroups; + } + + /** + * verify if groups exists in timetable if exist updates database. + * when timetable service is unavailable check groups of existing exams for verification + * @param examDto + */ + private void verifyAndUpdateExamGroups(ExamDto examDto) { + Set allGroups; + try { + allGroups = getGroupsFromTimetableService(); + if (!allGroups.containsAll(examDto.getExamGroups())) { + examDto.getExamGroups().removeAll(allGroups); + throw new InvalidGroupIdentifierException(examDto.getExamGroups()); + } else + groupRepository.saveAll(examDto.getExamGroups().stream() + .map(g -> StudentGroup.builder() + .name(g) + .build()) + .collect(Collectors.toList()) + ); + } catch (JsonProcessingException e) { + allGroups = groupRepository.findAllByNameIn(examDto.getExamGroups()) + .stream() + .map(StudentGroup::getName) + .collect(Collectors.toSet()); + if (!allGroups.containsAll(examDto.getExamGroups())) + throw new WebPageContentNotAvailableException(); + } + } } diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java b/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java index 82b0698..0853041 100644 --- a/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java +++ b/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java @@ -6,6 +6,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; import org.pkwmtt.exceptions.InvalidGroupIdentifierException; +import org.pkwmtt.exceptions.UnsupportedCountOfArgumentsException; import java.time.LocalDateTime; import java.util.HashSet; @@ -46,11 +47,9 @@ public class Exam { @SuppressWarnings("unused") public static class Builder { public Exam build() { - // max length of group identifier is 6 - groups.forEach(group -> { - if(group.getName().length() > 6) - throw new InvalidGroupIdentifierException(group.getName()); - }); +// min 1 max 100 elements of set + if(groups.isEmpty() || groups.size() > 100) + throw new UnsupportedCountOfArgumentsException(1, 100, groups.size()); return new Exam(examId, title, description, examDate, examType, groups); } } From 2d8b16308fe2baef7c069ba24104aeaf85b693c6 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Wed, 20 Aug 2025 20:13:05 +0200 Subject: [PATCH 039/116] fix examRepository tests --- .../repository/ExamRepository.java | 61 ---- .../pkwmtt/examCalendar/ExamServiceTest.java | 7 +- .../pkwmtt/examCalendar/dto/ExamDtoTest.java | 22 +- .../mapper/ExamDtoMapperTest.java | 187 ------------ .../repository/ExamRepositoryTest.java | 288 ++++++++---------- 5 files changed, 135 insertions(+), 430 deletions(-) delete mode 100644 src/test/java/org/pkwmtt/examCalendar/mapper/ExamDtoMapperTest.java diff --git a/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java index 9da5247..bcc1c9f 100644 --- a/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java +++ b/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java @@ -13,66 +13,5 @@ public interface ExamRepository extends JpaRepository { @Query("SELECT e FROM Exam e JOIN FETCH e.examType JOIN FETCH e.groups g WHERE g IN :gr") Set findByGroupsIn(@Param("gr") Set groups); -// /** -// * fetch all data using one query -// * @param group1 group identifier -// * @param group2 group identifier -// * @param group3 group identifier -// * @param group4 group identifier -// * @return set of Exams for specific groups -// */ -// @Query("SELECT e FROM Exam e JOIN FETCH e.examType WHERE " + -// "e.examGroups LIKE CONCAT('%', :g1, '%') OR " + -// "e.examGroups LIKE CONCAT('%', :g2, '%') OR " + -// "e.examGroups LIKE CONCAT('%', :g3, '%') OR " + -// "e.examGroups LIKE CONCAT('%', :g4, '%') ") -// Set findExamsByGroupsIdentifier( -// @Param("g1") String group1, -// @Param("g2") String group2, -// @Param("g3") String group3, -// @Param("g4") String group4 -// ); -// -// /** -// * fetch all data using one query -// * @param group1 group identifier -// * @param group2 group identifier -// * @param group3 group identifier -// * @return set of Exams for specific groups -// */ -// @Query("SELECT e FROM Exam e JOIN FETCH e.examType WHERE " + -// "e.examGroups LIKE CONCAT('%', :g1, '%') OR " + -// "e.examGroups LIKE CONCAT('%', :g2, '%') OR " + -// "e.examGroups LIKE CONCAT('%', :g3, '%') ") -// Set findExamsByGroupsIdentifier( -// @Param("g1") String group1, -// @Param("g2") String group2, -// @Param("g3") String group3 -// ); -// -// /** -// * fetch all data using one query -// * @param group1 group identifier -// * @param group2 group identifier -// * @return set of Exams for specific groups -// */ -// @Query("SELECT e FROM Exam e JOIN FETCH e.examType WHERE " + -// "e.examGroups LIKE CONCAT('%', :g1, '%') OR " + -// "e.examGroups LIKE CONCAT('%', :g2, '%')" ) -// Set findExamsByGroupsIdentifier( -// @Param("g1") String group1, -// @Param("g2") String group2 -// ); -// -// /** -// * fetch all data using one query -// * @param group group identifier -// * @return set of Exams for specific group -// */ -// @Query("SELECT e FROM Exam e JOIN FETCH e.examType WHERE " + -// "e.examGroups LIKE CONCAT('%', :gg, '%')") -// Set findExamsByGroupsIdentifier( -// @Param("gg") String group -// ); } \ No newline at end of file diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java index 0617441..1e8a986 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java @@ -11,6 +11,7 @@ import org.pkwmtt.examCalendar.entity.StudentGroup; import org.pkwmtt.examCalendar.mapper.ExamDtoMapper; import org.pkwmtt.examCalendar.repository.ExamRepository; +import org.pkwmtt.timetable.TimetableService; import java.lang.reflect.Field; import java.time.LocalDateTime; @@ -26,7 +27,7 @@ class ExamServiceTest { private ExamRepository examRepository; @Mock - private ExamDtoMapper examDtoMapper; + private TimetableService timetableService; @InjectMocks private ExamService examService; @@ -35,9 +36,6 @@ class ExamServiceTest { void addExam() { // given int examId = 1; - Set examGroups = new HashSet<>(); - examGroups.add(StudentGroup.builder().name("12K2").build()); - examGroups.add(StudentGroup.builder().name("13L1").build()); ExamDto examDto = new ExamDto( "Math exam", "desc", @@ -67,7 +65,6 @@ void addExam() { // then assertEquals(examId, result); verify(examRepository).save(exam); - verify(examDtoMapper).mapToNewExam(examDto); } /************************************************************************************/ diff --git a/src/test/java/org/pkwmtt/examCalendar/dto/ExamDtoTest.java b/src/test/java/org/pkwmtt/examCalendar/dto/ExamDtoTest.java index 0972256..23108d2 100644 --- a/src/test/java/org/pkwmtt/examCalendar/dto/ExamDtoTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/dto/ExamDtoTest.java @@ -111,26 +111,8 @@ void toLongDescription() { assertTrue(violations.stream().anyMatch(v -> v.getPropertyPath().toString().equals("description"))); } - // TODO: change to too large set -// TODO: check if groups exists -// @Test -// void toLongExamGroups() { -// // given -// ExamDto examDto = new ExamDto( -// "Math exam", -// "First exam", -// LocalDateTime.now().plusDays(1), -// "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", -// "exam" -// ); -//// when -// Set> violations = validator.validate(examDto); -//// then -// assertFalse(validator.validate(examDto).isEmpty()); -// assertTrue(violations.stream().anyMatch(v -> v.getPropertyPath().toString().equals("examGroups"))); -// } - -// date not in future + // TODO: change to too large set is Exam builder tests + // TODO: check if groups exists @Test void dateNotInFuture() { diff --git a/src/test/java/org/pkwmtt/examCalendar/mapper/ExamDtoMapperTest.java b/src/test/java/org/pkwmtt/examCalendar/mapper/ExamDtoMapperTest.java deleted file mode 100644 index 2224e5c..0000000 --- a/src/test/java/org/pkwmtt/examCalendar/mapper/ExamDtoMapperTest.java +++ /dev/null @@ -1,187 +0,0 @@ -package org.pkwmtt.examCalendar.mapper; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.pkwmtt.examCalendar.dto.ExamDto; -import org.pkwmtt.examCalendar.entity.Exam; -import org.pkwmtt.examCalendar.entity.ExamType; -import org.pkwmtt.examCalendar.entity.StudentGroup; -import org.pkwmtt.examCalendar.repository.ExamTypeRepository; -import org.pkwmtt.examCalendar.repository.GroupRepository; -import org.pkwmtt.exceptions.InvalidGroupIdentifierException; - -import java.time.LocalDateTime; -import java.util.HashSet; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class ExamDtoMapperTest { - - @Mock - private ExamTypeRepository examTypeRepository; - - @Mock - private GroupRepository groupRepository; - - @InjectMocks - private ExamDtoMapper examDtoMapper; - - private ExamDto examDto; - private String examTypeName; - - private Set groups; - - @BeforeEach - void setup(){ - StudentGroup group = StudentGroup.builder() - .name("12K2") - .build(); - groups = new HashSet<>(); - groups.add(group); - } - - /**********************************************************************************/ -// mapToNewExam - @Test - void isFieldsMappedProperlyToNewExam() { -// given - String examTypeName = "exam"; - String groupIdentifier = "12K2"; - ExamDto examDto = new ExamDto( - "Math exam", - "Linear algebra", - LocalDateTime.now().plusDays(1), - examTypeName, - Set.of("12K2") - ); - when(examTypeRepository.findByName(examTypeName)).thenReturn( - Optional.of(ExamType.builder() - .name(examTypeName) - .build()) - ); - - when(groupRepository.findAllByNameIn(Set.of(groupIdentifier))).thenReturn( - new HashSet<>(groups) - ); -// when - Exam exam = examDtoMapper.mapToNewExam(examDto); -// then -// test fields - assertEquals(examDto.getTitle(), exam.getTitle()); - assertEquals(examDto.getDescription(), exam.getDescription()); - assertEquals(examDto.getDate(), exam.getExamDate()); - assertEquals(examDto.getExamGroups(), exam.getGroups().stream().map(StudentGroup::getName).collect(Collectors.toSet())); - assertEquals(examTypeName, exam.getExamType().getName()); -// test null id - assertNull(exam.getExamId()); - } - -// TODO: change to checking if exam group exists - @Test - @Disabled("new version required") - void ShouldThrowExceptionWhenGroupIdentifierIsLongerThanSixCharactersForNewExam() { - // given -// StudentGroup group = StudentGroup.builder() -// .name("Not_Valid_Identifier") -// .build(); -// groups.add(group); - String examTypeName = "exam"; - ExamDto examDto = new ExamDto( - "Math exam", - "Linear algebra", - LocalDateTime.now().plusDays(1), - examTypeName, - Set.of("Not_Valid_Identifier") - ); - when(examTypeRepository.findByName(examTypeName)).thenReturn( - Optional.of(ExamType.builder() - .name(examTypeName) - .build()) - ); -// then - RuntimeException exception = assertThrows( - InvalidGroupIdentifierException.class, - () -> examDtoMapper.mapToNewExam(examDto) - ); - assertEquals("Invalid group identifier: Not_Valid_Identifier", exception.getMessage()); - } - - - /**********************************************************************************/ -// mapToExistingExam - @Test - void isFieldsMappedProperlyToExistingExam() { - // given - int examId = 1; - examTypeName = "exam"; - String groupIdentifier = "12K2"; - examDto = new ExamDto( - "Math exam", - "Linear algebra", - LocalDateTime.now().plusDays(1), - examTypeName, - Set.of(groupIdentifier) - ); - when(examTypeRepository.findByName(examTypeName)).thenReturn( - Optional.of(ExamType.builder() - .name(examTypeName) - .build()) - ); - - when(groupRepository.findAllByNameIn(Set.of(groupIdentifier))).thenReturn( - new HashSet<>(groups) - ); -// when - Exam exam = examDtoMapper.mapToExistingExam(examDto, examId); -// then -// test fields - assertEquals(examId, exam.getExamId()); - assertEquals(examDto.getTitle(), exam.getTitle()); - assertEquals(examDto.getDescription(), exam.getDescription()); - assertEquals(examDto.getDate(), exam.getExamDate()); - assertEquals(examDto.getExamGroups(), exam.getGroups().stream().map(StudentGroup::getName).collect(Collectors.toSet())); - assertEquals(examTypeName, exam.getExamType().getName()); -// test not null id - assertNotNull(exam.getExamId()); - } - - @Test - @Disabled("new version required") - void ShouldThrowExceptionWhenGroupIdentifierIsLongerThanSixCharactersForExistingExam() { - // given - int examId = 1; - StudentGroup group = StudentGroup.builder() - .name("Not_Valid_Identifier") - .build(); - groups.add(group); - String examTypeName = "exam"; - ExamDto examDto = new ExamDto( - "Math exam", - "Linear algebra", - LocalDateTime.now().plusDays(1), - examTypeName, - Set.of("12K2") - ); - when(examTypeRepository.findByName(examTypeName)).thenReturn( - Optional.of(ExamType.builder() - .name(examTypeName) - .build()) - ); -// then - RuntimeException exception = assertThrows( - InvalidGroupIdentifierException.class, - () -> examDtoMapper.mapToExistingExam(examDto, examId) - ); - assertEquals("Invalid group identifier: Not_Valid_Identifier", exception.getMessage()); - } -} diff --git a/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java b/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java index 192c8b4..b376e9e 100644 --- a/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java @@ -1,15 +1,16 @@ package org.pkwmtt.examCalendar.repository; import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; import org.pkwmtt.examCalendar.entity.Exam; import org.pkwmtt.examCalendar.entity.ExamType; +import org.pkwmtt.examCalendar.entity.StudentGroup; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import java.time.LocalDateTime; -import java.util.List; import java.util.Set; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -17,6 +18,7 @@ @Slf4j @DataJpaTest +@TestInstance(TestInstance.Lifecycle.PER_CLASS) class ExamRepositoryTest { @Autowired @@ -25,159 +27,131 @@ class ExamRepositoryTest { @Autowired private ExamTypeRepository examTypeRepository; -// TODO: change exam repository and test new version -// private ExamType examType; -// -// @BeforeEach -// void setup(){ -// examType = ExamType.builder() -// .name("exam") -// .build(); -// examTypeRepository.save(examType); -// } -// -// /** -// * test if method find specific count of exams when 1 or 0 group identifiers match -// */ -// @Test -// void testSingleIdentifierMatch() { -//// given -// Exam exam1 = Exam.builder() -// .title("Exam 1") -// .description("Exam 1") -// .examDate(LocalDateTime.now().plusDays(1)) -// .examGroups("12K2, K03") -// .examType(examType) -// .build(); -// examRepository.save(exam1); -// Exam exam2 = Exam.builder() -// .title("Exam 2") -// .description("Exam 2") -// .examDate(LocalDateTime.now().plusDays(1)) -// .examGroups("12K3, K03, S02") -// .examType(examType) -// .build(); -// examRepository.save(exam2); -// Exam exam3 = Exam.builder() -// .title("Exam 3") -// .description("Exam 3") -// .examDate(LocalDateTime.now().plusDays(1)) -// .examGroups("13K1, K05, L05") -// .examType(examType) -// .build(); -// examRepository.save(exam3); -// Exam exam4 = Exam.builder() -// .title("Exam 4") -// .description("Exam 4") -// .examDate(LocalDateTime.now().plusDays(1)) -// .examGroups("41K1, L04, P03, I01") -// .examType(examType) -// .build(); -// examRepository.save(exam4); -// Exam exam5 = Exam.builder() -// .title("Exam 5") -// .description("Exam 5") -// .examDate(LocalDateTime.now().plusDays(1)) -// .examGroups("11A1, G03, H01, P02") -// .examType(examType) -// .build(); -// examRepository.save(exam5); -// -// String generalGroup = "12K2"; -// String kGroup = "K05"; -// String lGroup = "L04"; -// String pGroup = "P02"; -// -//// when -// Set exams = examRepository.findExamsByGroupsIdentifier(generalGroup, kGroup, lGroup, pGroup); -// List examsTitles = exams.stream().map(Exam::getTitle).toList(); -//// then -// assertEquals(4, exams.size()); -// assertTrue(examsTitles.contains("Exam 1")); -// assertTrue(examsTitles.contains("Exam 3")); -// assertTrue(examsTitles.contains("Exam 4")); -// assertTrue(examsTitles.contains("Exam 5")); -// } -// -// /** -// * test if method don't duplicate exams when more than 1 identifier match -// */ -// @Test -// void testMultipleIdentifierMatch() { -//// given -// Exam exam1 = Exam.builder() -// .title("Exam 1") -// .description("Exam 1") -// .date(LocalDateTime.now().plusDays(1)) -// .examGroups("12K2, K01, L04, P03, I01") -// .examType(examType) -// .build(); -// examRepository.save(exam1); -// Exam exam2 = Exam.builder() -// .title("Exam 2") -// .description("Exam 2") -// .date(LocalDateTime.now().plusDays(1)) -// .examGroups("12K2, K05, L04, P02") -// .examType(examType) -// .build(); -// examRepository.save(exam2); -// Exam exam3 = Exam.builder() -// .title("Exam 3") -// .description("Exam 3") -// .date(LocalDateTime.now().plusDays(1)) -// .examGroups("12K2, K05, L04, P02, I05") -// .examType(examType) -// .build(); -// examRepository.save(exam3); -// -// String generalGroup = "12K2"; -// String kGroup = "K05"; -// String lGroup = "L04"; -// String pGroup = "P02"; -// -//// when -// Set exams = examRepository.findExamsByGroupsIdentifier(generalGroup, kGroup, lGroup, pGroup); -// List examsTitles = exams.stream().map(Exam::getTitle).toList(); -// -//// then -// assertEquals(3, exams.size()); -// assertTrue(examsTitles.contains("Exam 1")); -// assertTrue(examsTitles.contains("Exam 2")); -// assertTrue(examsTitles.contains("Exam 3")); -// } -// -// /** -// * test if method return empty set identifiers don't match -// */ -// @Test -// void testNothingMatch() { -//// given -// Exam exam1 = Exam.builder() -// .title("Exam 1") -// .description("Exam 1") -// .date(LocalDateTime.now().plusDays(1)) -// .examGroups("12K2, K01,") -// .examType(examType) -// .build(); -// examRepository.save(exam1); -// Exam exam2 = Exam.builder() -// .title("Exam 2") -// .description("Exam 2") -// .date(LocalDateTime.now().plusDays(1)) -// .examGroups("12K3, L05") -// .examType(examType) -// .build(); -// examRepository.save(exam2); -// -// String generalGroup = "14K3"; -// String kGroup = "K05"; -// String lGroup = "L02"; -// String pGroup = "P02"; -// -//// when -// Set exams = examRepository.findExamsByGroupsIdentifier(generalGroup, kGroup, lGroup, pGroup); -// -//// then -// assertTrue(exams.isEmpty()); -// } + @Autowired + private GroupRepository groupRepository; + + private StudentGroup g12K1; + private StudentGroup g12K2; + private StudentGroup g12K3; + private StudentGroup gL04; + private StudentGroup gL05; + + private Exam exam1; + private Exam exam2; + private Exam exam3; + private Exam exam4; + + @BeforeAll + void setUp() { + ExamType examType = ExamType.builder() + .name("exam").build(); + examTypeRepository.save(examType); + + g12K1 = StudentGroup.builder() + .name("12K1").build(); + g12K2 = StudentGroup.builder() + .name("12K2").build(); + g12K3 = StudentGroup.builder() + .name("12K3").build(); + gL04 = StudentGroup.builder() + .name("L04").build(); + gL05 = StudentGroup.builder() + .name("L05").build(); + + groupRepository.save(g12K1); + groupRepository.save(g12K2); + groupRepository.save(g12K3); + groupRepository.save(gL04); + groupRepository.save(gL05); + + exam1 = Exam.builder() + .title("math exam") + .description("Linear Algebra") + .examDate(LocalDateTime.now().plusDays(1)) + .examType(examType) + .groups(Set.of(gL04)) + .build(); + + exam2 = Exam.builder() + .title("math exam") + .description("Linear Algebra") + .examDate(LocalDateTime.now().plusDays(1)) + .examType(examType) + .groups(Set.of(g12K1, g12K2 ,gL04, gL05)) + .build(); + + exam3 = Exam.builder() + .title("math exam") + .description("Linear Algebra") + .examDate(LocalDateTime.now().plusDays(1)) + .examType(examType) + .groups(Set.of(g12K1)) + .build(); + + exam4 = Exam.builder() + .title("math exam") + .description("Linear Algebra") + .examDate(LocalDateTime.now().plusDays(1)) + .examType(examType) + .groups(Set.of(gL04, g12K1)) + .build(); + + examRepository.save(exam1); + examRepository.save(exam2); + examRepository.save(exam3); + examRepository.save(exam4); + } + + + @Test + void shouldReturnEmptySet() { +// when + Set exams = examRepository.findByGroupsIn(Set.of(g12K3)); +// then + assertTrue(exams.isEmpty()); + } + + @Test + void shouldReturnOneElementOutOfFour() { +// when + Set exams = examRepository.findByGroupsIn(Set.of(g12K2, gL05)); +// then + assertEquals(1, exams.size()); + assertTrue(exams.contains(exam2)); + + } + + @Test + void shouldReturnFourElementsOutOfFour() { +// when + Set exams = examRepository.findByGroupsIn(Set.of(g12K1, gL04)); +// then + assertEquals(4, exams.size()); + assertTrue(exams.contains(exam1)); + assertTrue(exams.contains(exam2)); + assertTrue(exams.contains(exam3)); + assertTrue(exams.contains(exam4)); + } + + @Test + void ShouldReturnEmptySetWhenNoGroups() { +// when + Set exams = examRepository.findByGroupsIn(Set.of()); +// then + assertTrue(exams.isEmpty()); + } + +// FIXME: + @Test + void ShouldReturnEmptySetWhenGroupNotExistsInDatabase() { +// when + Set exams = examRepository.findByGroupsIn(Set.of( + StudentGroup.builder() + .name("NotValid").build()) + ); +// then + assertTrue(exams.isEmpty()); + } + + } \ No newline at end of file From 86c9bb469df8949a2f161ff0762eb60565344b87 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Thu, 21 Aug 2025 13:26:21 +0200 Subject: [PATCH 040/116] fix tables and columns names mapping --- logs/app.log | 85 +++++++++++++++++++ .../org/pkwmtt/examCalendar/entity/Exam.java | 2 +- .../examCalendar/entity/GeneralGroup.java | 2 +- .../pkwmtt/examCalendar/entity/OTPCode.java | 2 +- .../examCalendar/entity/StudentGroup.java | 2 +- .../pkwmtt/examCalendar/ExamServiceTest.java | 54 +----------- .../repository/ExamRepositoryTest.java | 46 +++++----- src/test/resources/schema.sql | 71 ++++++++-------- 8 files changed, 153 insertions(+), 111 deletions(-) diff --git a/logs/app.log b/logs/app.log index 2e8f428..f7aa703 100644 --- a/logs/app.log +++ b/logs/app.log @@ -791,3 +791,88 @@ select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.tit Column "E1_0.exam_date" not found; SQL statement: select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] 2025-08-20 14:22:19 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-21 12:46:30 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-21 12:46:32 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Table "groups" not found (candidates are: "GROUPS"); SQL statement: +insert into "groups" (name,group_id) values (?,default) [42103-232] +2025-08-21 12:46:34 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-21 12:46:34 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-21 12:46:34 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-21 12:46:34 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-21 12:46:34 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-21 12:46:34 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-21 12:46:34 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-21 12:46:34 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-21 12:46:34 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-21 12:46:34 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-21 12:46:34 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-21 12:46:34 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-21 12:46:34 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-21 12:46:34 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-21 12:46:34 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-21 12:46:34 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-21 12:46:34 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-21 12:46:34 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-21 12:46:34 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-21 12:46:34 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-21 12:46:34 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-21 12:46:34 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-21 12:46:34 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,e1_0.title from exams e1_0 [42122-232] +2025-08-21 12:46:35 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-21 12:47:28 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Table "groups" not found (candidates are: "GROUPS"); SQL statement: +insert into "groups" (name,group_id) values (?,default) [42103-232] +2025-08-21 12:48:35 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Pole nie może być NULL"LETTER" +NULL not allowed for column "LETTER"; SQL statement: +insert into groups (name,group_id) values (?,default) [23502-232] +2025-08-21 12:57:43 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "exam_date" nie istnieje +Column "exam_date" not found; SQL statement: +insert into exams (description,"exam_date",exam_type_id,title,exam_id) values (?,?,?,?,default) [42122-232] +2025-08-21 12:59:32 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "E1_0.exam_date" nie istnieje +Column "E1_0.exam_date" not found; SQL statement: +select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,et1_0.exam_type_id,et1_0.name,g1_0.exam_id,g1_1.group_id,g1_1.name,e1_0.title from exams e1_0 join exam_type et1_0 on et1_0.exam_type_id=e1_0.exam_type_id join exams_groups g1_0 on e1_0.exam_id=g1_0.exam_id join groups g1_1 on g1_1.group_id=g1_0.group_id where 1=0 [42122-232] +2025-08-21 13:01:14 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "exam_date" nie istnieje +Column "exam_date" not found; SQL statement: +insert into exams (description,"exam_date",exam_type_id,title,exam_id) values (?,?,?,?,default) [42122-232] diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java b/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java index 0853041..9859105 100644 --- a/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java +++ b/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java @@ -29,7 +29,7 @@ public class Exam { private String description; - @Column(name = "`exam_date`", nullable = false) + @Column(name = "exam_date", nullable = false) private LocalDateTime examDate; @ManyToOne diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/GeneralGroup.java b/src/main/java/org/pkwmtt/examCalendar/entity/GeneralGroup.java index e368290..12e328f 100644 --- a/src/main/java/org/pkwmtt/examCalendar/entity/GeneralGroup.java +++ b/src/main/java/org/pkwmtt/examCalendar/entity/GeneralGroup.java @@ -13,7 +13,7 @@ @Builder @AllArgsConstructor @NoArgsConstructor -@Table(name = "`general_group`") +@Table(name = "general_group") public class GeneralGroup { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/OTPCode.java b/src/main/java/org/pkwmtt/examCalendar/entity/OTPCode.java index 7298d94..da9997c 100644 --- a/src/main/java/org/pkwmtt/examCalendar/entity/OTPCode.java +++ b/src/main/java/org/pkwmtt/examCalendar/entity/OTPCode.java @@ -27,6 +27,6 @@ public class OTPCode { private LocalDateTime expire; @OneToOne - @JoinColumn(name = "`general_group_id`", nullable = false) + @JoinColumn(name = "general_group_id", nullable = false) private GeneralGroup generalGroup; } diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/StudentGroup.java b/src/main/java/org/pkwmtt/examCalendar/entity/StudentGroup.java index c8568c1..72f016e 100644 --- a/src/main/java/org/pkwmtt/examCalendar/entity/StudentGroup.java +++ b/src/main/java/org/pkwmtt/examCalendar/entity/StudentGroup.java @@ -12,7 +12,7 @@ @RequiredArgsConstructor @AllArgsConstructor @Builder -@Table(name = "`groups`") +@Table(name = "groups") public class StudentGroup { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java index 1e8a986..0227ff8 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java @@ -34,72 +34,20 @@ class ExamServiceTest { @Test void addExam() { -// given - int examId = 1; - ExamDto examDto = new ExamDto( - "Math exam", - "desc", - LocalDateTime.now().plusDays(1), - "Exam", - Set.of("12k2", "13L1") - ); - Exam exam = Exam.builder() - .title("Math exam") - .description("desc") - .examDate(LocalDateTime.now().plusDays(1)) - .groups(examGroups) - .examType(new ExamType(1, "Exam")) - .build(); - when(examDtoMapper.mapToNewExam(examDto)).thenReturn(exam); -// assign exam id in repository - when(examRepository.save(exam)).thenAnswer(invocation -> { - Exam newExam = invocation.getArgument(0, Exam.class); - Field field = Exam.class.getDeclaredField("examId"); - field.setAccessible(true); - field.set(newExam, examId); - return newExam; - }); -// when - int result = examService.addExam(examDto); -// then - assertEquals(examId, result); - verify(examRepository).save(exam); } /************************************************************************************/ //modify exam @Test void shouldModifyExamWhenIdExists() { - // given - int examId = 1; - ExamDto examDto = mock(ExamDto.class); - Exam exam = mock(Exam.class); - when(examDtoMapper.mapToExistingExam(examDto, examId)).thenReturn(exam); - when(examRepository.findById(examId)).thenReturn(Optional.of(exam)); -// when - examService.modifyExam(examDto, examId); -// then - verify(examDtoMapper).mapToExistingExam(examDto, examId); - verify(examRepository).save(exam); } @Test void shouldThrowWhenExamIdNotExists() { // given - int examId = 5; - ExamDto examDto = mock(ExamDto.class); - when(examRepository.findById(examId)).thenThrow(new NoSuchElementException("Exam not found")); -// when - RuntimeException exception = assertThrows( - NoSuchElementException.class, - () -> examService.modifyExam(examDto, examId) - ); -// then - verify(examDtoMapper, never()).mapToExistingExam(examDto, examId); - verify(examRepository, never()).save(any()); - assertEquals("Exam not found", exception.getMessage()); + } /************************************************************************************/ diff --git a/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java b/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java index b376e9e..c9c5e04 100644 --- a/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java @@ -11,7 +11,9 @@ import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import java.time.LocalDateTime; +import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -36,10 +38,10 @@ class ExamRepositoryTest { private StudentGroup gL04; private StudentGroup gL05; - private Exam exam1; - private Exam exam2; - private Exam exam3; - private Exam exam4; + private Integer exam1Id; + private Integer exam2Id; + private Integer exam3Id; + private Integer exam4Id; @BeforeAll void setUp() { @@ -64,7 +66,7 @@ void setUp() { groupRepository.save(gL04); groupRepository.save(gL05); - exam1 = Exam.builder() + Exam exam1 = Exam.builder() .title("math exam") .description("Linear Algebra") .examDate(LocalDateTime.now().plusDays(1)) @@ -72,15 +74,15 @@ void setUp() { .groups(Set.of(gL04)) .build(); - exam2 = Exam.builder() + Exam exam2 = Exam.builder() .title("math exam") .description("Linear Algebra") .examDate(LocalDateTime.now().plusDays(1)) .examType(examType) - .groups(Set.of(g12K1, g12K2 ,gL04, gL05)) + .groups(Set.of(g12K1, g12K2, gL04, gL05)) .build(); - exam3 = Exam.builder() + Exam exam3 = Exam.builder() .title("math exam") .description("Linear Algebra") .examDate(LocalDateTime.now().plusDays(1)) @@ -88,7 +90,7 @@ void setUp() { .groups(Set.of(g12K1)) .build(); - exam4 = Exam.builder() + Exam exam4 = Exam.builder() .title("math exam") .description("Linear Algebra") .examDate(LocalDateTime.now().plusDays(1)) @@ -96,10 +98,10 @@ void setUp() { .groups(Set.of(gL04, g12K1)) .build(); - examRepository.save(exam1); - examRepository.save(exam2); - examRepository.save(exam3); - examRepository.save(exam4); + exam1Id = examRepository.save(exam1).getExamId(); + exam2Id = examRepository.save(exam2).getExamId(); + exam3Id = examRepository.save(exam3).getExamId(); + exam4Id = examRepository.save(exam4).getExamId(); } @@ -115,9 +117,12 @@ void shouldReturnEmptySet() { void shouldReturnOneElementOutOfFour() { // when Set exams = examRepository.findByGroupsIn(Set.of(g12K2, gL05)); + Set examsId = exams.stream().map(Exam::getExamId).collect(Collectors.toSet()); + // then + assertEquals(1, exams.size()); - assertTrue(exams.contains(exam2)); + assertTrue(examsId.contains(exam2Id)); } @@ -125,12 +130,13 @@ void shouldReturnOneElementOutOfFour() { void shouldReturnFourElementsOutOfFour() { // when Set exams = examRepository.findByGroupsIn(Set.of(g12K1, gL04)); + Set examsId = exams.stream().map(Exam::getExamId).collect(Collectors.toSet()); // then assertEquals(4, exams.size()); - assertTrue(exams.contains(exam1)); - assertTrue(exams.contains(exam2)); - assertTrue(exams.contains(exam3)); - assertTrue(exams.contains(exam4)); + assertTrue(examsId.contains(exam1Id)); + assertTrue(examsId.contains(exam2Id)); + assertTrue(examsId.contains(exam3Id)); + assertTrue(examsId.contains(exam4Id)); } @Test @@ -141,13 +147,13 @@ void ShouldReturnEmptySetWhenNoGroups() { assertTrue(exams.isEmpty()); } -// FIXME: @Test void ShouldReturnEmptySetWhenGroupNotExistsInDatabase() { // when Set exams = examRepository.findByGroupsIn(Set.of( StudentGroup.builder() - .name("NotValid").build()) + .groupId(Integer.MAX_VALUE) + .name("NotValid").build()) ); // then assertTrue(exams.isEmpty()); diff --git a/src/test/resources/schema.sql b/src/test/resources/schema.sql index 760aaf7..e868d5b 100644 --- a/src/test/resources/schema.sql +++ b/src/test/resources/schema.sql @@ -1,60 +1,63 @@ +DROP TABLE IF EXISTS exams_groups; DROP TABLE IF EXISTS exams; DROP TABLE IF EXISTS exam_type; -DROP TABLE IF EXISTS general_group; DROP TABLE IF EXISTS groups; +DROP TABLE IF EXISTS general_group; DROP TABLE IF EXISTS otp_codes; DROP TABLE IF EXISTS users; CREATE TABLE exam_type ( - exam_type_id INT PRIMARY KEY AUTO_INCREMENT, - name VARCHAR(255) + exam_type_id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL ); -CREATE TABLE general_group +CREATE TABLE exams ( - general_group_id INT PRIMARY KEY AUTO_INCREMENT, - name VARCHAR(255) + exam_id INT AUTO_INCREMENT PRIMARY KEY, + title VARCHAR(255) NOT NULL, + description VARCHAR(255), + exam_date TIMESTAMP NOT NULL, + exam_type_id INT NOT NULL, + CONSTRAINT fk_exam_type FOREIGN KEY (exam_type_id) REFERENCES exam_type (exam_type_id) ON DELETE CASCADE ); -CREATE TABLE exams +CREATE TABLE general_group ( - exam_id INT PRIMARY KEY AUTO_INCREMENT, - title VARCHAR(255), - description VARCHAR(255), - date TIMESTAMP(6), - "groups" VARCHAR(255), - exam_type_id INT NOT NULL, - FOREIGN KEY (exam_type_id) REFERENCES exam_type (exam_type_id) + general_group_id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL ); CREATE TABLE groups ( - group_id INT PRIMARY KEY AUTO_INCREMENT, - letter CHAR(1) NOT NULL, - group_count INT NOT NULL, - general_group_id INT NOT NULL, - name VARCHAR(255), - FOREIGN KEY (general_group_id) REFERENCES general_group (general_group_id) + group_id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL ); -CREATE TABLE users +CREATE TABLE exams_groups ( - user_id INT PRIMARY KEY AUTO_INCREMENT, - general_group_id INT NOT NULL, - email VARCHAR(254) NOT NULL, - is_active BOOLEAN NOT NULL, - role VARCHAR(20) NOT NULL, -- enum zamieniony na VARCHAR - FOREIGN KEY (general_group_id) REFERENCES general_group (general_group_id) + exam_group_id INT AUTO_INCREMENT PRIMARY KEY, + exam_id INT NOT NULL, + group_id INT NOT NULL, + CONSTRAINT fk_exam FOREIGN KEY (exam_id) REFERENCES exams (exam_id) ON DELETE CASCADE, + CONSTRAINT fk_group FOREIGN KEY (group_id) REFERENCES groups (group_id) ON DELETE CASCADE ); CREATE TABLE otp_codes ( - otp_code_id INT PRIMARY KEY AUTO_INCREMENT, - code VARCHAR(255), - expire TIMESTAMP NOT NULL, - used BOOLEAN NOT NULL, - user_id INT NOT NULL, - timestamp TIMESTAMP(6), - FOREIGN KEY (user_id) REFERENCES users (user_id) + otp_code_id INT AUTO_INCREMENT PRIMARY KEY, + code VARCHAR(255) NOT NULL, + expire TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + general_group_id INT NOT NULL, + CONSTRAINT fk_otp_group FOREIGN KEY (general_group_id) REFERENCES general_group (general_group_id) ON DELETE CASCADE +); + +CREATE TABLE users +( + user_id INT AUTO_INCREMENT PRIMARY KEY, + general_group_id INT NOT NULL, + email VARCHAR(255) NOT NULL, + is_active BOOLEAN NOT NULL DEFAULT TRUE, + role VARCHAR(50) NOT NULL DEFAULT 'REPRESENTATIVE', + CONSTRAINT fk_user_group FOREIGN KEY (general_group_id) REFERENCES general_group (general_group_id) ON DELETE CASCADE ); \ No newline at end of file From e69770de836c64422a69aab0bd5bf5bb548ec5b3 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Fri, 22 Aug 2025 23:15:50 +0200 Subject: [PATCH 041/116] fix addExam test --- logs/app.log | 21 ++++ .../org/pkwmtt/examCalendar/ExamService.java | 2 +- .../pkwmtt/examCalendar/ExamServiceTest.java | 101 +++++++++++++++++- 3 files changed, 119 insertions(+), 5 deletions(-) diff --git a/logs/app.log b/logs/app.log index f7aa703..42253e5 100644 --- a/logs/app.log +++ b/logs/app.log @@ -876,3 +876,24 @@ select e1_0.exam_id,e1_0.description,e1_0."exam_date",e1_0.exam_type_id,et1_0.ex 2025-08-21 13:01:14 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Kolumna "exam_date" nie istnieje Column "exam_date" not found; SQL statement: insert into exams (description,"exam_date",exam_type_id,title,exam_id) values (?,?,?,?,default) [42122-232] +2025-08-21 13:26:56 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-21 13:26:59 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-21 13:27:04 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-21 14:13:30 ERROR o.s.b.d.LoggingFailureAnalysisReporter - + +*************************** +APPLICATION FAILED TO START +*************************** + +Description: + +Parameter 0 of constructor in org.pkwmtt.examCalendar.ExamController required a bean of type 'org.pkwmtt.examCalendar.ExamService' that could not be found. + + +Action: + +Consider defining a bean of type 'org.pkwmtt.examCalendar.ExamService' in your configuration. + +2025-08-22 23:16:11 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-22 23:16:13 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-22 23:16:16 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamService.java b/src/main/java/org/pkwmtt/examCalendar/ExamService.java index e1db9ab..9828919 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamService.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamService.java @@ -123,7 +123,7 @@ private void verifyAndUpdateExamGroups(ExamDto examDto) { .map(g -> StudentGroup.builder() .name(g) .build()) - .collect(Collectors.toList()) + .collect(Collectors.toSet()) ); } catch (JsonProcessingException e) { allGroups = groupRepository.findAllByNameIn(examDto.getExamGroups()) diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java index 0227ff8..0f89640 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java @@ -1,9 +1,9 @@ package org.pkwmtt.examCalendar; +import com.fasterxml.jackson.core.JsonProcessingException; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; +import org.mockito.*; import org.mockito.junit.jupiter.MockitoExtension; import org.pkwmtt.examCalendar.dto.ExamDto; import org.pkwmtt.examCalendar.entity.Exam; @@ -11,21 +11,32 @@ import org.pkwmtt.examCalendar.entity.StudentGroup; import org.pkwmtt.examCalendar.mapper.ExamDtoMapper; import org.pkwmtt.examCalendar.repository.ExamRepository; +import org.pkwmtt.examCalendar.repository.ExamTypeRepository; +import org.pkwmtt.examCalendar.repository.GroupRepository; import org.pkwmtt.timetable.TimetableService; import java.lang.reflect.Field; import java.time.LocalDateTime; import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; + @ExtendWith(MockitoExtension.class) class ExamServiceTest { @Mock private ExamRepository examRepository; + @Mock + private GroupRepository groupRepository; + + @Mock + private ExamTypeRepository examTypeRepository; + @Mock private TimetableService timetableService; @@ -33,8 +44,89 @@ class ExamServiceTest { private ExamService examService; @Test - void addExam() { + void addExam() throws JsonProcessingException { +// given + String examTypeName = "exam"; + ExamType examType = ExamType.builder().name(examTypeName).build(); + examTypeRepository.save(examType); + + ExamDto examDto = ExamDto.builder() + .title("title") + .description("desc") + .date(LocalDateTime.now().plusDays(1)) + .examType(examTypeName) + .examGroups(Set.of("12K1", "P05", "L02")) + .build(); + + when(timetableService.getGeneralGroupList()).thenReturn(List.of("12K1", "12K2", "12K3")); + when(timetableService.getAvailableSubGroups("12K1")).thenReturn(List.of( + "K01", "K04", "L01", "L02", "L04", "P01", "P04" + )); + when(timetableService.getAvailableSubGroups("12K2")).thenReturn(List.of( + "K02", "K04", "K05", "L02", "L03", "L04", "L05", "P02", "P04", "P05" + )); + when(timetableService.getAvailableSubGroups("12K3")).thenReturn(List.of( + "K03", "K05", "L03", "L05", "L06", "P03", "P05" + )); + + when(groupRepository.findAllByNameIn(Set.of("12K1", "P05", "L02"))).thenReturn( + Stream.of("12K1", "P05", "L02").map(s -> + StudentGroup.builder() + .name(s) + .build() + ).collect(Collectors.toSet()) + ); + + when(examTypeRepository.findByName("exam")).thenReturn(Optional.of(ExamType.builder() + .examTypeId(1) + .name("exam") + .build())); + + Set studentGroups = Stream.of("12K1", "P05", "L02").map(s -> + StudentGroup.builder() + .name(s) + .build() + ).collect(Collectors.toSet()); + when(examRepository.save(any(Exam.class))).thenReturn(Exam.builder() + .examId(1) + .title("title") + .description("desc") + .examDate(LocalDateTime.now().plusDays(1)) + .examType(ExamType.builder() + .examTypeId(1) + .name("exam") + .build()) + .groups(studentGroups) + .build() + ); +// when + int returnedExamID = examService.addExam(examDto); +// then + verify(timetableService, times(1)).getGeneralGroupList(); + verify(timetableService, times(1)).getAvailableSubGroups("12K1"); + verify(timetableService, times(1)).getAvailableSubGroups("12K2"); + verify(timetableService, times(1)).getAvailableSubGroups("12K3"); + + ArgumentCaptor> studentGroupCaptor = ArgumentCaptor.forClass(Set.class); + verify(groupRepository, times(1)).saveAll(studentGroupCaptor.capture()); + + Set expectedGroups = studentGroups.stream() + .map(StudentGroup::getName) + .collect(Collectors.toSet()); + + Set providedGroups = studentGroupCaptor.getValue().stream() + .map(StudentGroup::getName) + .collect(Collectors.toSet()); + + assertEquals(expectedGroups, providedGroups); + + verify(examTypeRepository, times(1)).findByName(examTypeName); + + ArgumentCaptor examCaptor = ArgumentCaptor.forClass(Exam.class); + verify(examRepository, times(1)).save(examCaptor.capture()); + assertNull(examCaptor.getValue().getExamId()); + assertEquals(1, returnedExamID); } /************************************************************************************/ @@ -257,7 +349,8 @@ void shouldThrowExceptionWhenExamNotFound() { // when(examRepository.findExamsByGroupsIdentifier(any(), any(), any(), any())).thenReturn(exams); //// when // Set result = examService.getExamByGroup(groups); -//// then + + /// / then // List> cap = new ArrayList<>(); // for (int i = 0; i < 4; ++i) // cap.add(ArgumentCaptor.forClass(String.class)); From 5d5d516463a9dcffd3441ae9bf5ae6b5a7c0f5ad Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Sat, 23 Aug 2025 12:40:31 +0200 Subject: [PATCH 042/116] fix add exceptions in ExamService --- logs/app.log | 9 +++++++++ .../java/org/pkwmtt/examCalendar/ExamController.java | 5 +++-- src/main/java/org/pkwmtt/examCalendar/ExamService.java | 10 +++++----- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/logs/app.log b/logs/app.log index 42253e5..3085940 100644 --- a/logs/app.log +++ b/logs/app.log @@ -897,3 +897,12 @@ Consider defining a bean of type 'org.pkwmtt.examCalendar.ExamService' in your c 2025-08-22 23:16:11 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties 2025-08-22 23:16:13 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties 2025-08-22 23:16:16 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-22 23:17:17 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-22 23:17:19 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-22 23:17:22 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-22 23:18:22 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-22 23:18:24 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-22 23:18:28 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-22 23:19:00 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-22 23:19:02 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-22 23:19:06 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamController.java b/src/main/java/org/pkwmtt/examCalendar/ExamController.java index 5b59274..fd8c751 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamController.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamController.java @@ -1,5 +1,6 @@ package org.pkwmtt.examCalendar; +import com.fasterxml.jackson.core.JsonProcessingException; import jakarta.validation.Valid; import jakarta.validation.constraints.Positive; import lombok.RequiredArgsConstructor; @@ -29,7 +30,7 @@ public class ExamController { * @return 201 created with URI to GET method which returns created resource */ @PostMapping("") - public ResponseEntity addExam(@RequestBody @Valid ExamDto examDto) { + public ResponseEntity addExam(@RequestBody @Valid ExamDto examDto) throws JsonProcessingException { int id = examService.addExam(examDto); URI uri = ServletUriComponentsBuilder .fromCurrentRequest() @@ -45,7 +46,7 @@ public ResponseEntity addExam(@RequestBody @Valid ExamDto examDto) { * @return 204 no content */ @PutMapping("/{id}") - public ResponseEntity modifyExam(@PathVariable @Positive int id, @RequestBody @Valid ExamDto examDto) { + public ResponseEntity modifyExam(@PathVariable @Positive int id, @RequestBody @Valid ExamDto examDto) throws JsonProcessingException { examService.modifyExam(examDto, id); return ResponseEntity.noContent().build(); } diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamService.java b/src/main/java/org/pkwmtt/examCalendar/ExamService.java index 9828919..dc7b49e 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamService.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamService.java @@ -32,7 +32,7 @@ public class ExamService { * @param examDto details of exam * @return id of exam added to database */ - public int addExam(ExamDto examDto) { + public int addExam(ExamDto examDto) throws JsonProcessingException { verifyAndUpdateExamGroups(examDto); Set groups = groupRepository.findAllByNameIn(examDto.getExamGroups()); @@ -49,7 +49,7 @@ public int addExam(ExamDto examDto) { * @param examDto new details of exam that overwrite old ones * @param id of exam that need to be modified */ - public void modifyExam(ExamDto examDto, int id) { + public void modifyExam(ExamDto examDto, int id) throws JsonProcessingException { // check if exam which would be modified exists examRepository.findById(id).orElseThrow(() -> new NoSuchElementWithProvidedIdException(id)); @@ -111,7 +111,7 @@ private Set getGroupsFromTimetableService() throws JsonProcessingExcepti * when timetable service is unavailable check groups of existing exams for verification * @param examDto */ - private void verifyAndUpdateExamGroups(ExamDto examDto) { + private void verifyAndUpdateExamGroups(ExamDto examDto) throws JsonProcessingException { Set allGroups; try { allGroups = getGroupsFromTimetableService(); @@ -125,13 +125,13 @@ private void verifyAndUpdateExamGroups(ExamDto examDto) { .build()) .collect(Collectors.toSet()) ); - } catch (JsonProcessingException e) { + } catch (JsonProcessingException | SpecifiedGeneralGroupDoesntExistsException | WebPageContentNotAvailableException e) { allGroups = groupRepository.findAllByNameIn(examDto.getExamGroups()) .stream() .map(StudentGroup::getName) .collect(Collectors.toSet()); if (!allGroups.containsAll(examDto.getExamGroups())) - throw new WebPageContentNotAvailableException(); + throw e; } } } From cf52d2b9c5bace97a3442cfc7bb4cacd4527eafb Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Sun, 24 Aug 2025 14:59:00 +0200 Subject: [PATCH 043/116] expose bug in ExamService --- .../examCalendar/ExamControllerAdvice.java | 3 +- .../pkwmtt/examCalendar/ExamServiceTest.java | 206 ++++++++++++------ 2 files changed, 137 insertions(+), 72 deletions(-) diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java b/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java index 3c7cf78..5e3237f 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java @@ -5,6 +5,7 @@ import org.pkwmtt.exceptions.dto.ErrorResponseDTO; import org.pkwmtt.exceptions.ExamTypeNotExistsException; import org.pkwmtt.exceptions.NoSuchElementWithProvidedIdException; +import org.pkwmtt.timetable.TimetableController; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; @@ -13,7 +14,7 @@ import java.util.stream.Collectors; -@RestControllerAdvice +@RestControllerAdvice(assignableTypes = {ExamController.class}) public class ExamControllerAdvice { // TODO: handle or remove UnsupportedCountOfArgumentsException diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java index 0f89640..ed13578 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java @@ -44,89 +44,73 @@ class ExamServiceTest { private ExamService examService; @Test - void addExam() throws JsonProcessingException { + void addExamWithCorrectData() throws JsonProcessingException { // given String examTypeName = "exam"; - ExamType examType = ExamType.builder().name(examTypeName).build(); - examTypeRepository.save(examType); - - ExamDto examDto = ExamDto.builder() - .title("title") - .description("desc") - .date(LocalDateTime.now().plusDays(1)) - .examType(examTypeName) - .examGroups(Set.of("12K1", "P05", "L02")) - .build(); - - when(timetableService.getGeneralGroupList()).thenReturn(List.of("12K1", "12K2", "12K3")); - when(timetableService.getAvailableSubGroups("12K1")).thenReturn(List.of( - "K01", "K04", "L01", "L02", "L04", "P01", "P04" - )); - when(timetableService.getAvailableSubGroups("12K2")).thenReturn(List.of( - "K02", "K04", "K05", "L02", "L03", "L04", "L05", "P02", "P04", "P05" - )); - when(timetableService.getAvailableSubGroups("12K3")).thenReturn(List.of( - "K03", "K05", "L03", "L05", "L06", "P03", "P05" - )); - - when(groupRepository.findAllByNameIn(Set.of("12K1", "P05", "L02"))).thenReturn( - Stream.of("12K1", "P05", "L02").map(s -> - StudentGroup.builder() - .name(s) - .build() - ).collect(Collectors.toSet()) - ); + ExamType examType = mock(ExamType.class); + ExamDto examDto = mock(ExamDto.class); - when(examTypeRepository.findByName("exam")).thenReturn(Optional.of(ExamType.builder() - .examTypeId(1) - .name("exam") - .build())); - - Set studentGroups = Stream.of("12K1", "P05", "L02").map(s -> - StudentGroup.builder() - .name(s) - .build() - ).collect(Collectors.toSet()); - - when(examRepository.save(any(Exam.class))).thenReturn(Exam.builder() - .examId(1) - .title("title") - .description("desc") - .examDate(LocalDateTime.now().plusDays(1)) - .examType(ExamType.builder() - .examTypeId(1) - .name("exam") - .build()) - .groups(studentGroups) - .build() - ); +// List generalGroups = mockGetGeneralGroupList(); +// List subGroups = mockGetSubGroupsList(); + when(groupRepository.findAllByNameIn(anySet())).thenReturn(mock(Set.class)); + when(examTypeRepository.findByName(any(String.class))).thenReturn(Optional.of(mock(ExamType.class))); +// mockExamRepositoryFindByName(); + +// Set studentGroups = getExampleStudentGroupsSet(); + Set studentGroups = mock(Set.class); + +// when(examTypeRepository.findByName(examTypeName)).thenReturn(Optional.of(ExamType.builder().examTypeId(1).name(examTypeName).build())); + + try (MockedStatic mockedMapper = mockStatic(ExamDtoMapper.class)) { + mockedMapper.when(() -> ExamDtoMapper.mapToNewExam(any(ExamDto.class), any(Set.class), any(ExamType.class))).thenReturn(mock(Exam.class)); + + mockedMapper.verify(() -> ExamDtoMapper.mapToNewExam(any(ExamDto.class), any(Set.class), any(ExamType.class)), times(1)); + } + +// mockExamRepositorySaveExam(studentGroups); + when(examRepository.save(any(Exam.class))).thenReturn(mock(Exam.class)); // when - int returnedExamID = examService.addExam(examDto); +// int returnedExamID = examService.addExam(examDto); + examService.addExam(examDto); // then +// verify(timetableService, times(1)).getGeneralGroupList(); +// verify(timetableService, times(1)).getAvailableSubGroups("12K1"); +// verify(timetableService, times(1)).getAvailableSubGroups("12K2"); +// verify(timetableService, times(1)).getAvailableSubGroups("12K3"); + verify(timetableService, times(1)).getGeneralGroupList(); - verify(timetableService, times(1)).getAvailableSubGroups("12K1"); - verify(timetableService, times(1)).getAvailableSubGroups("12K2"); - verify(timetableService, times(1)).getAvailableSubGroups("12K3"); + verify(timetableService, times(1)).getAvailableSubGroups(any(String.class)); + verify(timetableService, times(1)).getAvailableSubGroups(any(String.class)); + verify(timetableService, times(1)).getAvailableSubGroups(any(String.class)); - ArgumentCaptor> studentGroupCaptor = ArgumentCaptor.forClass(Set.class); - verify(groupRepository, times(1)).saveAll(studentGroupCaptor.capture()); + verify(groupRepository, times(1)).saveAll(anySet()); - Set expectedGroups = studentGroups.stream() - .map(StudentGroup::getName) - .collect(Collectors.toSet()); +// ArgumentCaptor> studentGroupCaptor = ArgumentCaptor.forClass(Set.class); +// verify(groupRepository, times(1)).saveAll(studentGroupCaptor.capture()); +// +// Set expectedGroups = studentGroups.stream() +// .map(StudentGroup::getName) +// .collect(Collectors.toSet()); +// +// Set providedGroups = studentGroupCaptor.getValue().stream() +// .map(StudentGroup::getName) +// .collect(Collectors.toSet()); +// +// assertEquals(expectedGroups, providedGroups); - Set providedGroups = studentGroupCaptor.getValue().stream() - .map(StudentGroup::getName) - .collect(Collectors.toSet()); +// verify(examTypeRepository, times(1)).findByName(examTypeName); + verify(examTypeRepository, times(1)).findByName(any(String.class)); - assertEquals(expectedGroups, providedGroups); +// ArgumentCaptor examCaptor = ArgumentCaptor.forClass(Exam.class); +// verify(examRepository, times(1)).save(examCaptor.capture()); + verify(examRepository, times(1)).save(any(Exam.class)); - verify(examTypeRepository, times(1)).findByName(examTypeName); +// assertNull(examCaptor.getValue().getExamId()); +// assertEquals(1, returnedExamID); + } - ArgumentCaptor examCaptor = ArgumentCaptor.forClass(Exam.class); - verify(examRepository, times(1)).save(examCaptor.capture()); - assertNull(examCaptor.getValue().getExamId()); - assertEquals(1, returnedExamID); + @Test + void addExamWithWrongExamType() throws JsonProcessingException { } /************************************************************************************/ @@ -368,4 +352,84 @@ void shouldThrowExceptionWhenExamNotFound() { // assertEquals(4, passedGroups.size()); // } + +// helper methods +// private ExamDto getExampleExamDto(String examTypeName) { +// return ExamDto.builder() +// .title("title") +// .description("desc") +// .date(LocalDateTime.now().plusDays(1)) +// .examType(examTypeName) +// .examGroups(Set.of("12K1", "P05", "L02")) +// .build(); +// } +// +// private ExamType saveExampleExamType(String examTypeName) { +// return ExamType.builder().examTypeId(1).name(examTypeName).build(); +// } +// +// private List mockGetGeneralGroupList(){ +// List groups = List.of("12K1", "12K2", "12K3"); +// when(timetableService.getGeneralGroupList()).thenReturn(groups); +// return groups; +// } +// +// private void mockGetSubGroupsList() throws JsonProcessingException { +// List groups1 = List.of( +// "K01", "K04", "L01", "L02", "L04", "P01", "P04" +// ); +// List groups2 = List.of( +// "K02", "K04", "K05", "L02", "L03", "L04", "L05", "P02", "P04", "P05" +// ); +// List groups3 = List.of( +// "K03", "K05", "L03", "L05", "L06", "P03", "P05" +// ); +// +// when(timetableService.getAvailableSubGroups("12K1")).thenReturn(groups1); +// when(timetableService.getAvailableSubGroups("12K2")).thenReturn(groups2); +// when(timetableService.getAvailableSubGroups("12K3")).thenReturn(groups3); +// return new ArrayList(groups1, groups2, groups3); +// } +// +// private void mockGroupRepositoryFindByName() { +// when(groupRepository.findAllByNameIn(Set.of("12K1", "P05", "L02"))).thenReturn( +// Stream.of("12K1", "P05", "L02").map(s -> +// StudentGroup.builder() +// .name(s) +// .build() +// ).collect(Collectors.toSet()) +// ); +// } +// +// private void mockExamRepositoryFindByName() { +// when(examTypeRepository.findByName("exam")).thenReturn(Optional.of(ExamType.builder() +// .examTypeId(1) +// .name("exam") +// .build())); +// } +// +// private Set getExampleStudentGroupsSet() { +// return Stream.of("12K1", "P05", "L02").map(s -> +// StudentGroup.builder() +// .name(s) +// .build() +// ).collect(Collectors.toSet()); +// } +// +// private void mockExamRepositorySaveExam(Set studentGroups) { +// when(examRepository.save(any(Exam.class))).thenReturn(Exam.builder() +// .examId(1) +// .title("title") +// .description("desc") +// .examDate(LocalDateTime.now().plusDays(1)) +// .examType(ExamType.builder() +// .examTypeId(1) +// .name("exam") +// .build()) +// .groups(studentGroups) +// .build() +// ); +// } + + } \ No newline at end of file From a2558b02becef315ae5e7cce7b8c0768dedab0bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Piotrkowski?= Date: Sun, 24 Aug 2025 15:06:40 +0200 Subject: [PATCH 044/116] feat(auth): add JWT token generation --- pom.xml | 22 +++++ .../org/pkwmtt/repository/UserRepository.java | 2 + .../pkwmtt/security/auth/AuthController.java | 24 ++++++ .../org/pkwmtt/security/auth/AuthService.java | 8 ++ .../pkwmtt/security/auth/AuthServiceImpl.java | 30 +++++++ .../security/auth/dto/UserRequestDTO.java | 9 +++ .../provider/OTPAuthenticationProvider.java | 69 ++++++++++++++++ .../security/config/SpringSecurity.java | 16 ++++ .../org/pkwmtt/security/token/JwtService.java | 9 +++ .../pkwmtt/security/token/JwtServiceImpl.java | 81 +++++++++++++++++++ .../pkwmtt/security/token/dto/UserDTO.java | 18 +++++ .../pkwmtt/security/token/utils/JwtUtils.java | 20 +++++ .../org/pkwmtt/jwt/JwtSecretMakerTest.java | 21 +++++ 13 files changed, 329 insertions(+) create mode 100644 src/main/java/org/pkwmtt/security/auth/AuthController.java create mode 100644 src/main/java/org/pkwmtt/security/auth/AuthService.java create mode 100644 src/main/java/org/pkwmtt/security/auth/AuthServiceImpl.java create mode 100644 src/main/java/org/pkwmtt/security/auth/dto/UserRequestDTO.java create mode 100644 src/main/java/org/pkwmtt/security/auth/provider/OTPAuthenticationProvider.java create mode 100644 src/main/java/org/pkwmtt/security/token/JwtService.java create mode 100644 src/main/java/org/pkwmtt/security/token/JwtServiceImpl.java create mode 100644 src/main/java/org/pkwmtt/security/token/dto/UserDTO.java create mode 100644 src/main/java/org/pkwmtt/security/token/utils/JwtUtils.java create mode 100644 src/test/java/org/pkwmtt/jwt/JwtSecretMakerTest.java diff --git a/pom.xml b/pom.xml index 1ab2d6a..91cc172 100644 --- a/pom.xml +++ b/pom.xml @@ -87,6 +87,25 @@ spring-security-test test + + io.jsonwebtoken + jjwt-api + 0.12.7 + + + io.jsonwebtoken + jjwt-impl + 0.12.7 + runtime + + + io.jsonwebtoken + jjwt-jackson + 0.12.7 + runtime + + + junit @@ -138,6 +157,7 @@ org.springframework.boot spring-boot-starter-actuator + ch.qos.logback @@ -149,11 +169,13 @@ wiremock-spring-boot 3.10.0 + org.springframework.boot spring-boot-starter-mail + io.github.cdimascio diff --git a/src/main/java/org/pkwmtt/repository/UserRepository.java b/src/main/java/org/pkwmtt/repository/UserRepository.java index 71ccd75..1e53f12 100644 --- a/src/main/java/org/pkwmtt/repository/UserRepository.java +++ b/src/main/java/org/pkwmtt/repository/UserRepository.java @@ -2,6 +2,8 @@ import org.pkwmtt.entity.User; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; public interface UserRepository extends JpaRepository { + Optional findByEmail(String email); } \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/security/auth/AuthController.java b/src/main/java/org/pkwmtt/security/auth/AuthController.java new file mode 100644 index 0000000..8855825 --- /dev/null +++ b/src/main/java/org/pkwmtt/security/auth/AuthController.java @@ -0,0 +1,24 @@ +package org.pkwmtt.security.auth; + +import lombok.RequiredArgsConstructor; +import org.pkwmtt.security.auth.dto.UserRequestDTO; +import org.pkwmtt.security.token.JwtServiceImpl; +import org.pkwmtt.security.token.dto.UserDTO; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/pkwmtt/auth") +@RequiredArgsConstructor +public class AuthController { + private final AuthService authService; + private final JwtServiceImpl jwtServiceImpl; + + @PostMapping("/authenticate") + public String authenticate(@RequestBody UserRequestDTO requestUser) { + UserDTO user = authService.authenticateUser(requestUser); + return jwtServiceImpl.generateToken(user); + } +} diff --git a/src/main/java/org/pkwmtt/security/auth/AuthService.java b/src/main/java/org/pkwmtt/security/auth/AuthService.java new file mode 100644 index 0000000..7b60b77 --- /dev/null +++ b/src/main/java/org/pkwmtt/security/auth/AuthService.java @@ -0,0 +1,8 @@ +package org.pkwmtt.security.auth; + +import org.pkwmtt.security.auth.dto.UserRequestDTO; +import org.pkwmtt.security.token.dto.UserDTO; + +public interface AuthService { + UserDTO authenticateUser(UserRequestDTO requestUser); +} diff --git a/src/main/java/org/pkwmtt/security/auth/AuthServiceImpl.java b/src/main/java/org/pkwmtt/security/auth/AuthServiceImpl.java new file mode 100644 index 0000000..382c83f --- /dev/null +++ b/src/main/java/org/pkwmtt/security/auth/AuthServiceImpl.java @@ -0,0 +1,30 @@ +package org.pkwmtt.security.auth; + +import lombok.RequiredArgsConstructor; +import org.pkwmtt.security.auth.dto.UserRequestDTO; +import org.pkwmtt.security.token.dto.UserDTO; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class AuthServiceImpl implements AuthService { + private final AuthenticationManager authenticationManager; + + @Override + public UserDTO authenticateUser(UserRequestDTO requestUser) { + Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken( + requestUser.getEmail(), + requestUser.getOtp_code() + )); + + if (!authentication.isAuthenticated()) { + throw new UsernameNotFoundException("Invalid credentials"); + } + + return (UserDTO) authentication.getPrincipal(); + } +} diff --git a/src/main/java/org/pkwmtt/security/auth/dto/UserRequestDTO.java b/src/main/java/org/pkwmtt/security/auth/dto/UserRequestDTO.java new file mode 100644 index 0000000..f30ed0e --- /dev/null +++ b/src/main/java/org/pkwmtt/security/auth/dto/UserRequestDTO.java @@ -0,0 +1,9 @@ +package org.pkwmtt.security.auth.dto; + +import lombok.Data; + +@Data +public class UserRequestDTO { + private String otp_code; + private String email; +} diff --git a/src/main/java/org/pkwmtt/security/auth/provider/OTPAuthenticationProvider.java b/src/main/java/org/pkwmtt/security/auth/provider/OTPAuthenticationProvider.java new file mode 100644 index 0000000..d2b6894 --- /dev/null +++ b/src/main/java/org/pkwmtt/security/auth/provider/OTPAuthenticationProvider.java @@ -0,0 +1,69 @@ +package org.pkwmtt.security.auth.provider; +import lombok.RequiredArgsConstructor; +import org.pkwmtt.entity.User; +import org.pkwmtt.repository.UserRepository; +import org.pkwmtt.security.token.dto.UserDTO; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.stream.Stream; + +@Component +@RequiredArgsConstructor +public class OTPAuthenticationProvider implements AuthenticationProvider { + private final UserRepository userRepository; + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + String email = authentication.getName(); + String otpCode = authentication.getCredentials().toString(); + + // Fetch user from DB + User user = userRepository.findByEmail(email) + .orElseThrow(() -> new BadCredentialsException("User not found")); + + // Wrap role in a list to support multiple roles in the future + List authorities = Stream.of(user.getRole()) + .map(role -> new SimpleGrantedAuthority("ROLE_" + role.name())) + .toList(); + + // Validate critical user fields before OTP check + if(!isValidForAuthentication(user)){ + throw new BadCredentialsException( + "Invalid User Credentials. Please contact the administrator." + ); + } + + // TODO: integrate with real OTP service + boolean OTP_SERVICE_AUTH_RESULT = true; + + if(OTP_SERVICE_AUTH_RESULT){ + UserDTO userMapped = new UserDTO(user); + return new UsernamePasswordAuthenticationToken(userMapped, otpCode, authorities); + } else { + throw new BadCredentialsException("Invalid OTP"); + } + } + + @Override + public boolean supports(Class authentication) { + return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication); + } + + /** + * Validates user data before authentication. + * Returns true if user has email, role, group, and is active. + */ + private boolean isValidForAuthentication(User user) { + return user.getEmail() != null && + user.getRole() != null && + user.getGeneralGroup() != null && + user.isActive(); + } +} diff --git a/src/main/java/org/pkwmtt/security/config/SpringSecurity.java b/src/main/java/org/pkwmtt/security/config/SpringSecurity.java index c4dc591..7cf7a82 100644 --- a/src/main/java/org/pkwmtt/security/config/SpringSecurity.java +++ b/src/main/java/org/pkwmtt/security/config/SpringSecurity.java @@ -1,21 +1,32 @@ package org.pkwmtt.security.config; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.pkwmtt.security.auth.provider.OTPAuthenticationProvider; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.ProviderManager; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.web.SecurityFilterChain; +import java.util.List; + import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS; //@EnableWebSecurity @Slf4j @Configuration +@RequiredArgsConstructor public class SpringSecurity { + private final OTPAuthenticationProvider otpAuthenticationProvider; + @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { log.info("Configuring Security Filter Chain..."); @@ -30,4 +41,9 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { log.info("Configuring Success..."); return http.build(); } + + @Bean + public AuthenticationManager authenticationManager() { + return new ProviderManager(List.of(otpAuthenticationProvider)); + } } diff --git a/src/main/java/org/pkwmtt/security/token/JwtService.java b/src/main/java/org/pkwmtt/security/token/JwtService.java new file mode 100644 index 0000000..e248f86 --- /dev/null +++ b/src/main/java/org/pkwmtt/security/token/JwtService.java @@ -0,0 +1,9 @@ +package org.pkwmtt.security.token; + +import org.pkwmtt.security.token.dto.UserDTO; + +public interface JwtService { + String generateToken(UserDTO user); + Boolean validateToken(String token); + String getUserIdFromToken(String token); +} diff --git a/src/main/java/org/pkwmtt/security/token/JwtServiceImpl.java b/src/main/java/org/pkwmtt/security/token/JwtServiceImpl.java new file mode 100644 index 0000000..7453328 --- /dev/null +++ b/src/main/java/org/pkwmtt/security/token/JwtServiceImpl.java @@ -0,0 +1,81 @@ +package org.pkwmtt.security.token; + +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import lombok.RequiredArgsConstructor; +import org.pkwmtt.security.token.dto.UserDTO; +import org.pkwmtt.security.token.utils.JwtUtils; +import org.springframework.stereotype.Service; + +import javax.crypto.SecretKey; +import java.util.Base64; +import java.util.Date; + +@Service +@RequiredArgsConstructor +public class JwtServiceImpl implements JwtService { + private final JwtUtils jwtUtils; + + + /** + * Generates a JWT token for a given user. + * The token contains user's email, group, and role as claims, + * and is signed with a secret key. + * + * @param user - required user data to include in token claims + * @return signed JWT token as a String + */ + @Override + public String generateToken(UserDTO user) { + return Jwts.builder() + .subject(user.getEmail()) + .claim("group", user.getGroup()) + .claim("role", user.getRole()) + .issuedAt(new Date()) + .expiration((new Date(System.currentTimeMillis() + jwtUtils.getEXPIRATION_MS()))) + .signWith(decodeSecretKey()) + .compact(); + } + + + /** + * Decode a secret key for signing JWT. + * The key is decoded from Base64 stored in JwtUtils configuration. + * + * @return secret key for JWT signing + */ + private SecretKey decodeSecretKey(){ + byte[] decodedKey = Base64.getDecoder().decode(jwtUtils.getSECRET()); + return Keys.hmacShaKeyFor(decodedKey); + } + + /** + * Validate a JWT token. + * Attempts to parse the token; if parsing fails, the token is considered invalid. + * + * @param token JWT token string to validate + * @return true if the token is valid, false otherwise + */ + @Override + public Boolean validateToken(String token) { + try { + // TODO: add logic to validate the token + return true; + } catch (JwtException | IllegalArgumentException e) { + return false; + } + } + + /** + * Extracts the user identifier (email) from a JWT token. + * + * @param token JWT token to extract user from + * @return user email from token + */ + @Override + public String getUserIdFromToken(String token) { + // TODO: implement token parsing to extract subject/email + return ""; + } +} diff --git a/src/main/java/org/pkwmtt/security/token/dto/UserDTO.java b/src/main/java/org/pkwmtt/security/token/dto/UserDTO.java new file mode 100644 index 0000000..8736f61 --- /dev/null +++ b/src/main/java/org/pkwmtt/security/token/dto/UserDTO.java @@ -0,0 +1,18 @@ +package org.pkwmtt.security.token.dto; + +import lombok.Data; +import org.pkwmtt.entity.User; +import org.pkwmtt.enums.Role; + +@Data +public class UserDTO { + private String email; + private String group; + private Role role; + + public UserDTO(User user){ + this.email = user.getEmail(); + this.group = user.getGeneralGroup() != null ? user.getGeneralGroup().getName() : null; + this.role = user.getRole(); + } +} diff --git a/src/main/java/org/pkwmtt/security/token/utils/JwtUtils.java b/src/main/java/org/pkwmtt/security/token/utils/JwtUtils.java new file mode 100644 index 0000000..a9d18a7 --- /dev/null +++ b/src/main/java/org/pkwmtt/security/token/utils/JwtUtils.java @@ -0,0 +1,20 @@ +package org.pkwmtt.security.token.utils; + +import lombok.Getter; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +@Getter +@Component +public class JwtUtils { + private Environment environment; + private final String SECRET; + private final long EXPIRATION_MS = 1000L * 60 * 60 * 24 * 30 * 6; + + public JwtUtils(Environment environment) { + this.SECRET = environment.getProperty("JWT_SECRET_KEY"); + if(this.SECRET == null) { + throw new RuntimeException("JWT_SECRET_KEY not found in environment variables"); + } + } +} diff --git a/src/test/java/org/pkwmtt/jwt/JwtSecretMakerTest.java b/src/test/java/org/pkwmtt/jwt/JwtSecretMakerTest.java new file mode 100644 index 0000000..ed43d5d --- /dev/null +++ b/src/test/java/org/pkwmtt/jwt/JwtSecretMakerTest.java @@ -0,0 +1,21 @@ +package org.pkwmtt.jwt; + +import io.jsonwebtoken.Jwts; +import jakarta.xml.bind.DatatypeConverter; +import org.junit.jupiter.api.Test; + +import javax.crypto.SecretKey; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +public class JwtSecretMakerTest { + + @Test + public void generateSecretKey(){ + SecretKey key = Jwts.SIG.HS512.key().build(); + String encodedKey = DatatypeConverter.printHexBinary(key.getEncoded()); + System.out.printf("\nKey = [%s]\n", encodedKey); + String base64Secret = Base64.getEncoder().encodeToString(encodedKey.getBytes(StandardCharsets.UTF_8)); + System.out.println(base64Secret); + } +} From 2cd2aa3aa3a13a0391f7a62b70276ea08b86c084 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Piotrkowski?= Date: Sun, 24 Aug 2025 15:12:12 +0200 Subject: [PATCH 045/116] fix: remove unused imports and improve JwtUtils class --- .../org/pkwmtt/security/config/SpringSecurity.java | 3 --- .../org/pkwmtt/security/token/utils/JwtUtils.java | 11 +++++------ 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/pkwmtt/security/config/SpringSecurity.java b/src/main/java/org/pkwmtt/security/config/SpringSecurity.java index 7cf7a82..37aea49 100644 --- a/src/main/java/org/pkwmtt/security/config/SpringSecurity.java +++ b/src/main/java/org/pkwmtt/security/config/SpringSecurity.java @@ -6,11 +6,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.ProviderManager; -import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.web.SecurityFilterChain; diff --git a/src/main/java/org/pkwmtt/security/token/utils/JwtUtils.java b/src/main/java/org/pkwmtt/security/token/utils/JwtUtils.java index a9d18a7..0d07a92 100644 --- a/src/main/java/org/pkwmtt/security/token/utils/JwtUtils.java +++ b/src/main/java/org/pkwmtt/security/token/utils/JwtUtils.java @@ -7,14 +7,13 @@ @Getter @Component public class JwtUtils { - private Environment environment; - private final String SECRET; - private final long EXPIRATION_MS = 1000L * 60 * 60 * 24 * 30 * 6; + private final String secret; + private final long expirationMs = 1000L * 60 * 60 * 24 * 30 * 6; public JwtUtils(Environment environment) { - this.SECRET = environment.getProperty("JWT_SECRET_KEY"); - if(this.SECRET == null) { - throw new RuntimeException("JWT_SECRET_KEY not found in environment variables"); + this.secret = environment.getProperty("JWT_SECRET_KEY"); + if(this.secret == null) { + throw new IllegalStateException("JWT_SECRET_KEY not found in environment variables"); } } } From 84bddb004b0a268251cbfb31eb4841140b8d34c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Piotrkowski?= Date: Sun, 24 Aug 2025 15:13:21 +0200 Subject: [PATCH 046/116] fix: fields names --- src/main/java/org/pkwmtt/security/token/JwtServiceImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/pkwmtt/security/token/JwtServiceImpl.java b/src/main/java/org/pkwmtt/security/token/JwtServiceImpl.java index 7453328..f94079e 100644 --- a/src/main/java/org/pkwmtt/security/token/JwtServiceImpl.java +++ b/src/main/java/org/pkwmtt/security/token/JwtServiceImpl.java @@ -33,7 +33,7 @@ public String generateToken(UserDTO user) { .claim("group", user.getGroup()) .claim("role", user.getRole()) .issuedAt(new Date()) - .expiration((new Date(System.currentTimeMillis() + jwtUtils.getEXPIRATION_MS()))) + .expiration((new Date(System.currentTimeMillis() + jwtUtils.getExpirationMs()))) .signWith(decodeSecretKey()) .compact(); } @@ -46,7 +46,7 @@ public String generateToken(UserDTO user) { * @return secret key for JWT signing */ private SecretKey decodeSecretKey(){ - byte[] decodedKey = Base64.getDecoder().decode(jwtUtils.getSECRET()); + byte[] decodedKey = Base64.getDecoder().decode(jwtUtils.getSecret()); return Keys.hmacShaKeyFor(decodedKey); } From 0702118c6488f13c624a89d60abab5bd91b8c6c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Piotrkowski?= Date: Sun, 24 Aug 2025 15:29:10 +0200 Subject: [PATCH 047/116] Fallback to default JWT secret if env var missing --- .../java/org/pkwmtt/security/token/utils/JwtUtils.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/pkwmtt/security/token/utils/JwtUtils.java b/src/main/java/org/pkwmtt/security/token/utils/JwtUtils.java index 0d07a92..21ee1bb 100644 --- a/src/main/java/org/pkwmtt/security/token/utils/JwtUtils.java +++ b/src/main/java/org/pkwmtt/security/token/utils/JwtUtils.java @@ -7,13 +7,14 @@ @Getter @Component public class JwtUtils { + // Secret key used for signing JWTs. If the environment variable JWT_SECRET_KEY + // is not set, a default value "TEST_SECRET" is used. This allows the application + // to start without a real secret, e.g., for local development or tests. private final String secret; private final long expirationMs = 1000L * 60 * 60 * 24 * 30 * 6; public JwtUtils(Environment environment) { - this.secret = environment.getProperty("JWT_SECRET_KEY"); - if(this.secret == null) { - throw new IllegalStateException("JWT_SECRET_KEY not found in environment variables"); - } + // Get the secret key from environment variables, or fallback to "TEST_SECRET" + this.secret = environment.getProperty("JWT_SECRET_KEY", "TEST_SECRET"); } } From 4ff3ccb3fc864d77468b04463361ff0ff2ce9f96 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Mon, 25 Aug 2025 13:22:02 +0200 Subject: [PATCH 048/116] fix: critical bug in examService --- logs/app.log | 2193 +++++++++++++++++ .../pkwmtt/examCalendar/ExamController.java | 4 +- .../org/pkwmtt/examCalendar/ExamService.java | 71 +- .../org/pkwmtt/examCalendar/dto/ExamDto.java | 4 +- .../org/pkwmtt/examCalendar/entity/Exam.java | 1 + .../examCalendar/entity/StudentGroup.java | 2 +- .../examCalendar/mapper/ExamDtoMapper.java | 24 +- 7 files changed, 2260 insertions(+), 39 deletions(-) diff --git a/logs/app.log b/logs/app.log index 3085940..67b0c72 100644 --- a/logs/app.log +++ b/logs/app.log @@ -906,3 +906,2196 @@ Consider defining a bean of type 'org.pkwmtt.examCalendar.ExamService' in your c 2025-08-22 23:19:00 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties 2025-08-22 23:19:02 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties 2025-08-22 23:19:06 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-24 14:56:01 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'groups sg1_0 where sg1_0.name in ('12K2')' at line 1 +2025-08-24 14:56:01 ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.dao.InvalidDataAccessResourceUsageException: JDBC exception executing SQL [select sg1_0.group_id,sg1_0.name from groups sg1_0 where sg1_0.name in (?)] [You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'groups sg1_0 where sg1_0.name in ('12K2')' at line 1] [n/a]; SQL [n/a]] with root cause +java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'groups sg1_0 where sg1_0.name in ('12K2')' at line 1 + at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:112) + at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:114) + at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:990) + at com.mysql.cj.jdbc.ClientPreparedStatement.executeQuery(ClientPreparedStatement.java:1058) + at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeQuery(ProxyPreparedStatement.java:52) + at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeQuery(HikariProxyPreparedStatement.java) + at org.hibernate.sql.results.jdbc.internal.DeferredResultSetAccess.executeQuery(DeferredResultSetAccess.java:251) + at org.hibernate.sql.results.jdbc.internal.DeferredResultSetAccess.getResultSet(DeferredResultSetAccess.java:172) + at org.hibernate.sql.results.jdbc.internal.JdbcValuesResultSetImpl.(JdbcValuesResultSetImpl.java:74) + at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.resolveJdbcValuesSource(JdbcSelectExecutorStandardImpl.java:355) + at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.doExecuteQuery(JdbcSelectExecutorStandardImpl.java:137) + at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.executeQuery(JdbcSelectExecutorStandardImpl.java:102) + at org.hibernate.sql.exec.spi.JdbcSelectExecutor.executeQuery(JdbcSelectExecutor.java:91) + at org.hibernate.sql.exec.spi.JdbcSelectExecutor.list(JdbcSelectExecutor.java:165) + at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.lambda$new$1(ConcreteSqmSelectQueryPlan.java:152) + at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.withCacheableSqmInterpretation(ConcreteSqmSelectQueryPlan.java:442) + at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.performList(ConcreteSqmSelectQueryPlan.java:362) + at org.hibernate.query.sqm.internal.QuerySqmImpl.doList(QuerySqmImpl.java:380) + at org.hibernate.query.spi.AbstractSelectionQuery.list(AbstractSelectionQuery.java:143) + at org.hibernate.query.Query.getResultList(Query.java:120) + at org.springframework.data.jpa.repository.query.JpaQueryExecution$CollectionExecution.doExecute(JpaQueryExecution.java:130) + at org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:93) + at org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:160) + at org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:148) + at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:170) + at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:158) + at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:170) + at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:149) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:69) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) + at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:138) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:136) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223) + at jdk.proxy3/jdk.proxy3.$Proxy169.findAllByNameIn(Unknown Source) + at org.pkwmtt.examCalendar.ExamService.verifyAndUpdateExamGroups(ExamService.java:129) + at org.pkwmtt.examCalendar.ExamService.addExam(ExamService.java:37) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) + at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamService$$SpringCGLIB$$0.addExam() + at org.pkwmtt.examCalendar.ExamController.addExam(ExamController.java:34) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:174) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamController$$SpringCGLIB$$0.addExam() + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:258) + at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:191) + at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:986) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:891) + at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) + at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) + at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) + at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) + at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590) + at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.security.web.FilterChainProxy.lambda$doFilterInternal$3(FilterChainProxy.java:231) + at org.springframework.security.web.ObservationFilterChainDecorator$FilterObservation$SimpleFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:479) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:340) + at org.springframework.security.web.ObservationFilterChainDecorator.lambda$wrapSecured$0(ObservationFilterChainDecorator.java:82) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:128) + at org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:101) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:125) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:131) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:85) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) + at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$0(ObservationFilterChainDecorator.java:323) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:224) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233) + at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.ServletRequestPathFilter.doFilter(ServletRequestPathFilter.java:52) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebSecurityConfiguration.java:319) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.servlet.handler.HandlerMappingIntrospector.lambda$createCacheFilter$4(HandlerMappingIntrospector.java:267) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebMvcSecurityConfiguration.java:240) + at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:362) + at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:278) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:114) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) + at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) + at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) + at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:116) + at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) + at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) + at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) + at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:398) + at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) + at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:903) + at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1769) + at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1189) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:658) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) + at java.base/java.lang.Thread.run(Thread.java:1575) +2025-08-24 15:01:16 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'groups sg1_0 where sg1_0.name in ('12K2')' at line 1 +2025-08-24 15:01:16 ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.dao.InvalidDataAccessResourceUsageException: JDBC exception executing SQL [select sg1_0.group_id,sg1_0.name from groups sg1_0 where sg1_0.name in (?)] [You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'groups sg1_0 where sg1_0.name in ('12K2')' at line 1] [n/a]; SQL [n/a]] with root cause +java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'groups sg1_0 where sg1_0.name in ('12K2')' at line 1 + at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:112) + at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:114) + at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:990) + at com.mysql.cj.jdbc.ClientPreparedStatement.executeQuery(ClientPreparedStatement.java:1058) + at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeQuery(ProxyPreparedStatement.java:52) + at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeQuery(HikariProxyPreparedStatement.java) + at org.hibernate.sql.results.jdbc.internal.DeferredResultSetAccess.executeQuery(DeferredResultSetAccess.java:251) + at org.hibernate.sql.results.jdbc.internal.DeferredResultSetAccess.getResultSet(DeferredResultSetAccess.java:172) + at org.hibernate.sql.results.jdbc.internal.JdbcValuesResultSetImpl.(JdbcValuesResultSetImpl.java:74) + at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.resolveJdbcValuesSource(JdbcSelectExecutorStandardImpl.java:355) + at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.doExecuteQuery(JdbcSelectExecutorStandardImpl.java:137) + at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.executeQuery(JdbcSelectExecutorStandardImpl.java:102) + at org.hibernate.sql.exec.spi.JdbcSelectExecutor.executeQuery(JdbcSelectExecutor.java:91) + at org.hibernate.sql.exec.spi.JdbcSelectExecutor.list(JdbcSelectExecutor.java:165) + at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.lambda$new$1(ConcreteSqmSelectQueryPlan.java:152) + at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.withCacheableSqmInterpretation(ConcreteSqmSelectQueryPlan.java:442) + at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.performList(ConcreteSqmSelectQueryPlan.java:362) + at org.hibernate.query.sqm.internal.QuerySqmImpl.doList(QuerySqmImpl.java:380) + at org.hibernate.query.spi.AbstractSelectionQuery.list(AbstractSelectionQuery.java:143) + at org.hibernate.query.Query.getResultList(Query.java:120) + at org.springframework.data.jpa.repository.query.JpaQueryExecution$CollectionExecution.doExecute(JpaQueryExecution.java:130) + at org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:93) + at org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:160) + at org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:148) + at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:170) + at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:158) + at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:170) + at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:149) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:69) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) + at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:138) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:136) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223) + at jdk.proxy3/jdk.proxy3.$Proxy169.findAllByNameIn(Unknown Source) + at org.pkwmtt.examCalendar.ExamService.verifyAndUpdateExamGroups(ExamService.java:129) + at org.pkwmtt.examCalendar.ExamService.addExam(ExamService.java:37) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) + at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamService$$SpringCGLIB$$0.addExam() + at org.pkwmtt.examCalendar.ExamController.addExam(ExamController.java:34) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:174) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamController$$SpringCGLIB$$0.addExam() + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:258) + at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:191) + at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:986) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:891) + at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) + at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) + at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) + at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) + at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590) + at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.security.web.FilterChainProxy.lambda$doFilterInternal$3(FilterChainProxy.java:231) + at org.springframework.security.web.ObservationFilterChainDecorator$FilterObservation$SimpleFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:479) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:340) + at org.springframework.security.web.ObservationFilterChainDecorator.lambda$wrapSecured$0(ObservationFilterChainDecorator.java:82) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:128) + at org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:101) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:125) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:131) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:85) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) + at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$0(ObservationFilterChainDecorator.java:323) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:224) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233) + at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.ServletRequestPathFilter.doFilter(ServletRequestPathFilter.java:52) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebSecurityConfiguration.java:319) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.servlet.handler.HandlerMappingIntrospector.lambda$createCacheFilter$4(HandlerMappingIntrospector.java:267) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebMvcSecurityConfiguration.java:240) + at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:362) + at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:278) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:114) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) + at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) + at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) + at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:116) + at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) + at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) + at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) + at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:398) + at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) + at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:903) + at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1769) + at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1189) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:658) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) + at java.base/java.lang.Thread.run(Thread.java:1575) +2025-08-24 15:01:59 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'groups sg1_0 where sg1_0.name in ('12K1','12K3','12K2')' at line 1 +2025-08-24 15:01:59 ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.dao.InvalidDataAccessResourceUsageException: JDBC exception executing SQL [select sg1_0.group_id,sg1_0.name from groups sg1_0 where sg1_0.name in (?,?,?)] [You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'groups sg1_0 where sg1_0.name in ('12K1','12K3','12K2')' at line 1] [n/a]; SQL [n/a]] with root cause +java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'groups sg1_0 where sg1_0.name in ('12K1','12K3','12K2')' at line 1 + at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:112) + at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:114) + at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:990) + at com.mysql.cj.jdbc.ClientPreparedStatement.executeQuery(ClientPreparedStatement.java:1058) + at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeQuery(ProxyPreparedStatement.java:52) + at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeQuery(HikariProxyPreparedStatement.java) + at org.hibernate.sql.results.jdbc.internal.DeferredResultSetAccess.executeQuery(DeferredResultSetAccess.java:251) + at org.hibernate.sql.results.jdbc.internal.DeferredResultSetAccess.getResultSet(DeferredResultSetAccess.java:172) + at org.hibernate.sql.results.jdbc.internal.JdbcValuesResultSetImpl.(JdbcValuesResultSetImpl.java:74) + at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.resolveJdbcValuesSource(JdbcSelectExecutorStandardImpl.java:355) + at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.doExecuteQuery(JdbcSelectExecutorStandardImpl.java:137) + at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.executeQuery(JdbcSelectExecutorStandardImpl.java:102) + at org.hibernate.sql.exec.spi.JdbcSelectExecutor.executeQuery(JdbcSelectExecutor.java:91) + at org.hibernate.sql.exec.spi.JdbcSelectExecutor.list(JdbcSelectExecutor.java:165) + at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.lambda$new$1(ConcreteSqmSelectQueryPlan.java:152) + at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.withCacheableSqmInterpretation(ConcreteSqmSelectQueryPlan.java:442) + at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.performList(ConcreteSqmSelectQueryPlan.java:362) + at org.hibernate.query.sqm.internal.QuerySqmImpl.doList(QuerySqmImpl.java:380) + at org.hibernate.query.spi.AbstractSelectionQuery.list(AbstractSelectionQuery.java:143) + at org.hibernate.query.Query.getResultList(Query.java:120) + at org.springframework.data.jpa.repository.query.JpaQueryExecution$CollectionExecution.doExecute(JpaQueryExecution.java:130) + at org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:93) + at org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:160) + at org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:148) + at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:170) + at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:158) + at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:170) + at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:149) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:69) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) + at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:138) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:136) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223) + at jdk.proxy3/jdk.proxy3.$Proxy169.findAllByNameIn(Unknown Source) + at org.pkwmtt.examCalendar.ExamService.verifyAndUpdateExamGroups(ExamService.java:129) + at org.pkwmtt.examCalendar.ExamService.addExam(ExamService.java:37) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) + at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamService$$SpringCGLIB$$0.addExam() + at org.pkwmtt.examCalendar.ExamController.addExam(ExamController.java:34) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:174) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamController$$SpringCGLIB$$0.addExam() + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:258) + at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:191) + at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:986) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:891) + at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) + at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) + at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) + at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) + at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590) + at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.security.web.FilterChainProxy.lambda$doFilterInternal$3(FilterChainProxy.java:231) + at org.springframework.security.web.ObservationFilterChainDecorator$FilterObservation$SimpleFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:479) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:340) + at org.springframework.security.web.ObservationFilterChainDecorator.lambda$wrapSecured$0(ObservationFilterChainDecorator.java:82) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:128) + at org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:101) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:125) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:131) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:85) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) + at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$0(ObservationFilterChainDecorator.java:323) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:224) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233) + at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.ServletRequestPathFilter.doFilter(ServletRequestPathFilter.java:52) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebSecurityConfiguration.java:319) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.servlet.handler.HandlerMappingIntrospector.lambda$createCacheFilter$4(HandlerMappingIntrospector.java:267) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebMvcSecurityConfiguration.java:240) + at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:362) + at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:278) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:114) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) + at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) + at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) + at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:116) + at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) + at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) + at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) + at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:398) + at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) + at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:903) + at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1769) + at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1189) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:658) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) + at java.base/java.lang.Thread.run(Thread.java:1575) +2025-08-24 15:04:04 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'groups sg1_0 where sg1_0.name in ('12K1','12K3','12K2')' at line 1 +2025-08-24 15:04:04 ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.dao.InvalidDataAccessResourceUsageException: JDBC exception executing SQL [select sg1_0.group_id,sg1_0.name from groups sg1_0 where sg1_0.name in (?,?,?)] [You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'groups sg1_0 where sg1_0.name in ('12K1','12K3','12K2')' at line 1] [n/a]; SQL [n/a]] with root cause +java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'groups sg1_0 where sg1_0.name in ('12K1','12K3','12K2')' at line 1 + at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:112) + at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:114) + at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:990) + at com.mysql.cj.jdbc.ClientPreparedStatement.executeQuery(ClientPreparedStatement.java:1058) + at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeQuery(ProxyPreparedStatement.java:52) + at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeQuery(HikariProxyPreparedStatement.java) + at org.hibernate.sql.results.jdbc.internal.DeferredResultSetAccess.executeQuery(DeferredResultSetAccess.java:251) + at org.hibernate.sql.results.jdbc.internal.DeferredResultSetAccess.getResultSet(DeferredResultSetAccess.java:172) + at org.hibernate.sql.results.jdbc.internal.JdbcValuesResultSetImpl.(JdbcValuesResultSetImpl.java:74) + at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.resolveJdbcValuesSource(JdbcSelectExecutorStandardImpl.java:355) + at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.doExecuteQuery(JdbcSelectExecutorStandardImpl.java:137) + at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.executeQuery(JdbcSelectExecutorStandardImpl.java:102) + at org.hibernate.sql.exec.spi.JdbcSelectExecutor.executeQuery(JdbcSelectExecutor.java:91) + at org.hibernate.sql.exec.spi.JdbcSelectExecutor.list(JdbcSelectExecutor.java:165) + at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.lambda$new$1(ConcreteSqmSelectQueryPlan.java:152) + at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.withCacheableSqmInterpretation(ConcreteSqmSelectQueryPlan.java:442) + at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.performList(ConcreteSqmSelectQueryPlan.java:362) + at org.hibernate.query.sqm.internal.QuerySqmImpl.doList(QuerySqmImpl.java:380) + at org.hibernate.query.spi.AbstractSelectionQuery.list(AbstractSelectionQuery.java:143) + at org.hibernate.query.Query.getResultList(Query.java:120) + at org.springframework.data.jpa.repository.query.JpaQueryExecution$CollectionExecution.doExecute(JpaQueryExecution.java:130) + at org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:93) + at org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:160) + at org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:148) + at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:170) + at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:158) + at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:170) + at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:149) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:69) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) + at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:138) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:136) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223) + at jdk.proxy3/jdk.proxy3.$Proxy169.findAllByNameIn(Unknown Source) + at org.pkwmtt.examCalendar.ExamService.verifyAndUpdateExamGroups(ExamService.java:129) + at org.pkwmtt.examCalendar.ExamService.addExam(ExamService.java:37) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) + at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamService$$SpringCGLIB$$0.addExam() + at org.pkwmtt.examCalendar.ExamController.addExam(ExamController.java:34) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:174) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamController$$SpringCGLIB$$0.addExam() + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:258) + at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:191) + at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:986) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:891) + at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) + at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) + at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) + at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) + at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590) + at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.security.web.FilterChainProxy.lambda$doFilterInternal$3(FilterChainProxy.java:231) + at org.springframework.security.web.ObservationFilterChainDecorator$FilterObservation$SimpleFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:479) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:340) + at org.springframework.security.web.ObservationFilterChainDecorator.lambda$wrapSecured$0(ObservationFilterChainDecorator.java:82) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:128) + at org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:101) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:125) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:131) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:85) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) + at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$0(ObservationFilterChainDecorator.java:323) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:224) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233) + at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.ServletRequestPathFilter.doFilter(ServletRequestPathFilter.java:52) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebSecurityConfiguration.java:319) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.servlet.handler.HandlerMappingIntrospector.lambda$createCacheFilter$4(HandlerMappingIntrospector.java:267) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebMvcSecurityConfiguration.java:240) + at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:362) + at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:278) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:114) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) + at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) + at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) + at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:116) + at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) + at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) + at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) + at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:398) + at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) + at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:903) + at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1769) + at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1189) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:658) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) + at java.base/java.lang.Thread.run(Thread.java:1575) +2025-08-24 15:04:12 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'groups sg1_0 where sg1_0.name in ('12K1','12K3','12K2')' at line 1 +2025-08-24 15:04:12 ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.dao.InvalidDataAccessResourceUsageException: JDBC exception executing SQL [select sg1_0.group_id,sg1_0.name from groups sg1_0 where sg1_0.name in (?,?,?)] [You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'groups sg1_0 where sg1_0.name in ('12K1','12K3','12K2')' at line 1] [n/a]; SQL [n/a]] with root cause +java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'groups sg1_0 where sg1_0.name in ('12K1','12K3','12K2')' at line 1 + at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:112) + at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:114) + at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:990) + at com.mysql.cj.jdbc.ClientPreparedStatement.executeQuery(ClientPreparedStatement.java:1058) + at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeQuery(ProxyPreparedStatement.java:52) + at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeQuery(HikariProxyPreparedStatement.java) + at org.hibernate.sql.results.jdbc.internal.DeferredResultSetAccess.executeQuery(DeferredResultSetAccess.java:251) + at org.hibernate.sql.results.jdbc.internal.DeferredResultSetAccess.getResultSet(DeferredResultSetAccess.java:172) + at org.hibernate.sql.results.jdbc.internal.JdbcValuesResultSetImpl.(JdbcValuesResultSetImpl.java:74) + at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.resolveJdbcValuesSource(JdbcSelectExecutorStandardImpl.java:355) + at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.doExecuteQuery(JdbcSelectExecutorStandardImpl.java:137) + at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.executeQuery(JdbcSelectExecutorStandardImpl.java:102) + at org.hibernate.sql.exec.spi.JdbcSelectExecutor.executeQuery(JdbcSelectExecutor.java:91) + at org.hibernate.sql.exec.spi.JdbcSelectExecutor.list(JdbcSelectExecutor.java:165) + at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.lambda$new$1(ConcreteSqmSelectQueryPlan.java:152) + at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.withCacheableSqmInterpretation(ConcreteSqmSelectQueryPlan.java:442) + at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.performList(ConcreteSqmSelectQueryPlan.java:362) + at org.hibernate.query.sqm.internal.QuerySqmImpl.doList(QuerySqmImpl.java:380) + at org.hibernate.query.spi.AbstractSelectionQuery.list(AbstractSelectionQuery.java:143) + at org.hibernate.query.Query.getResultList(Query.java:120) + at org.springframework.data.jpa.repository.query.JpaQueryExecution$CollectionExecution.doExecute(JpaQueryExecution.java:130) + at org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:93) + at org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:160) + at org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:148) + at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:170) + at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:158) + at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:170) + at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:149) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:69) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) + at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:138) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:136) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223) + at jdk.proxy3/jdk.proxy3.$Proxy169.findAllByNameIn(Unknown Source) + at org.pkwmtt.examCalendar.ExamService.verifyAndUpdateExamGroups(ExamService.java:129) + at org.pkwmtt.examCalendar.ExamService.addExam(ExamService.java:37) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) + at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamService$$SpringCGLIB$$0.addExam() + at org.pkwmtt.examCalendar.ExamController.addExam(ExamController.java:34) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:174) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamController$$SpringCGLIB$$0.addExam() + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:258) + at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:191) + at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:986) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:891) + at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) + at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) + at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) + at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) + at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590) + at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.security.web.FilterChainProxy.lambda$doFilterInternal$3(FilterChainProxy.java:231) + at org.springframework.security.web.ObservationFilterChainDecorator$FilterObservation$SimpleFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:479) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:340) + at org.springframework.security.web.ObservationFilterChainDecorator.lambda$wrapSecured$0(ObservationFilterChainDecorator.java:82) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:128) + at org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:101) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:125) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:131) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:85) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) + at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$0(ObservationFilterChainDecorator.java:323) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:224) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233) + at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.ServletRequestPathFilter.doFilter(ServletRequestPathFilter.java:52) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebSecurityConfiguration.java:319) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.servlet.handler.HandlerMappingIntrospector.lambda$createCacheFilter$4(HandlerMappingIntrospector.java:267) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebMvcSecurityConfiguration.java:240) + at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:362) + at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:278) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:114) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) + at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) + at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) + at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:116) + at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) + at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) + at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) + at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:398) + at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) + at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:903) + at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1769) + at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1189) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:658) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) + at java.base/java.lang.Thread.run(Thread.java:1575) +2025-08-24 15:10:02 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'groups sg1_0 where sg1_0.name in ('12K1','12K3','12K2')' at line 1 +2025-08-25 12:41:51 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'groups (name) values ('12K3')' at line 1 +2025-08-25 12:41:51 ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.dao.InvalidDataAccessResourceUsageException: could not execute statement [You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'groups (name) values ('12K3')' at line 1] [insert into groups (name) values (?)]; SQL [insert into groups (name) values (?)]] with root cause +java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'groups (name) values ('12K3')' at line 1 + at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:112) + at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:114) + at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:990) + at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1168) + at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1103) + at com.mysql.cj.jdbc.ClientPreparedStatement.executeLargeUpdate(ClientPreparedStatement.java:1450) + at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdate(ClientPreparedStatement.java:1086) + at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeUpdate(ProxyPreparedStatement.java:61) + at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeUpdate(HikariProxyPreparedStatement.java) + at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:194) + at org.hibernate.id.insert.GetGeneratedKeysDelegate.performMutation(GetGeneratedKeysDelegate.java:116) + at org.hibernate.engine.jdbc.mutation.internal.MutationExecutorSingleNonBatched.performNonBatchedOperations(MutationExecutorSingleNonBatched.java:47) + at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.execute(AbstractMutationExecutor.java:55) + at org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.doStaticInserts(InsertCoordinatorStandard.java:194) + at org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.coordinateInsert(InsertCoordinatorStandard.java:132) + at org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.insert(InsertCoordinatorStandard.java:95) + at org.hibernate.action.internal.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:85) + at org.hibernate.engine.spi.ActionQueue.execute(ActionQueue.java:682) + at org.hibernate.engine.spi.ActionQueue.addResolvedEntityInsertAction(ActionQueue.java:293) + at org.hibernate.engine.spi.ActionQueue.addInsertAction(ActionQueue.java:274) + at org.hibernate.engine.spi.ActionQueue.addAction(ActionQueue.java:324) + at org.hibernate.event.internal.AbstractSaveEventListener.addInsertAction(AbstractSaveEventListener.java:394) + at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:308) + at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:224) + at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:136) + at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:177) + at org.hibernate.event.internal.DefaultPersistEventListener.persist(DefaultPersistEventListener.java:95) + at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:79) + at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:55) + at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:127) + at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:761) + at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:745) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:364) + at jdk.proxy3/jdk.proxy3.$Proxy159.persist(Unknown Source) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:320) + at jdk.proxy3/jdk.proxy3.$Proxy159.persist(Unknown Source) + at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:653) + at org.springframework.data.jpa.repository.support.SimpleJpaRepository.saveAll(SimpleJpaRepository.java:679) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.data.repository.core.support.RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0(RepositoryMethodInvoker.java:277) + at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:170) + at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:158) + at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:515) + at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:284) + at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:734) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:174) + at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:149) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:69) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) + at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:138) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:165) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223) + at jdk.proxy3/jdk.proxy3.$Proxy169.saveAll(Unknown Source) + at org.pkwmtt.examCalendar.ExamService.verifyAndUpdateExamGroups(ExamService.java:129) + at org.pkwmtt.examCalendar.ExamService.addExam(ExamService.java:37) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) + at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamService$$SpringCGLIB$$0.addExam() + at org.pkwmtt.examCalendar.ExamController.addExam(ExamController.java:34) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:174) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamController$$SpringCGLIB$$0.addExam() + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:258) + at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:191) + at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:986) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:891) + at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) + at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) + at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) + at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) + at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590) + at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.security.web.FilterChainProxy.lambda$doFilterInternal$3(FilterChainProxy.java:231) + at org.springframework.security.web.ObservationFilterChainDecorator$FilterObservation$SimpleFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:479) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:340) + at org.springframework.security.web.ObservationFilterChainDecorator.lambda$wrapSecured$0(ObservationFilterChainDecorator.java:82) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:128) + at org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:101) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:125) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:131) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:85) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) + at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$0(ObservationFilterChainDecorator.java:323) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:224) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233) + at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.ServletRequestPathFilter.doFilter(ServletRequestPathFilter.java:52) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebSecurityConfiguration.java:319) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.servlet.handler.HandlerMappingIntrospector.lambda$createCacheFilter$4(HandlerMappingIntrospector.java:267) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebMvcSecurityConfiguration.java:240) + at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:362) + at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:278) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:114) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) + at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) + at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) + at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:116) + at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) + at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) + at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) + at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:398) + at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) + at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:903) + at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1769) + at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1189) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:658) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) + at java.base/java.lang.Thread.run(Thread.java:1575) +2025-08-25 12:42:06 ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.pkwmtt.exceptions.UnsupportedCountOfArgumentsException: Invalid count of arguments provided: 0 expected more than: 1 less than: 100] with root cause +org.pkwmtt.exceptions.UnsupportedCountOfArgumentsException: Invalid count of arguments provided: 0 expected more than: 1 less than: 100 + at org.pkwmtt.examCalendar.entity.Exam$Builder.build(Exam.java:52) + at org.pkwmtt.examCalendar.mapper.ExamDtoMapper.mapToNewExam(ExamDtoMapper.java:33) + at org.pkwmtt.examCalendar.ExamService.addExam(ExamService.java:44) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) + at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamService$$SpringCGLIB$$0.addExam() + at org.pkwmtt.examCalendar.ExamController.addExam(ExamController.java:34) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:174) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamController$$SpringCGLIB$$0.addExam() + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:258) + at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:191) + at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:986) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:891) + at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) + at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) + at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) + at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) + at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590) + at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.security.web.FilterChainProxy.lambda$doFilterInternal$3(FilterChainProxy.java:231) + at org.springframework.security.web.ObservationFilterChainDecorator$FilterObservation$SimpleFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:479) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:340) + at org.springframework.security.web.ObservationFilterChainDecorator.lambda$wrapSecured$0(ObservationFilterChainDecorator.java:82) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:128) + at org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:101) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:125) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:131) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:85) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) + at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$0(ObservationFilterChainDecorator.java:323) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:224) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233) + at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.ServletRequestPathFilter.doFilter(ServletRequestPathFilter.java:52) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebSecurityConfiguration.java:319) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.servlet.handler.HandlerMappingIntrospector.lambda$createCacheFilter$4(HandlerMappingIntrospector.java:267) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebMvcSecurityConfiguration.java:240) + at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:362) + at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:278) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:114) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) + at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) + at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) + at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:116) + at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) + at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) + at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) + at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:398) + at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) + at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:903) + at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1769) + at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1189) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:658) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) + at java.base/java.lang.Thread.run(Thread.java:1575) +2025-08-25 12:49:10 ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.pkwmtt.exceptions.UnsupportedCountOfArgumentsException: Invalid count of arguments provided: 0 expected more than: 1 less than: 100] with root cause +org.pkwmtt.exceptions.UnsupportedCountOfArgumentsException: Invalid count of arguments provided: 0 expected more than: 1 less than: 100 + at org.pkwmtt.examCalendar.entity.Exam$Builder.build(Exam.java:52) + at org.pkwmtt.examCalendar.mapper.ExamDtoMapper.mapToNewExam(ExamDtoMapper.java:33) + at org.pkwmtt.examCalendar.ExamService.addExam(ExamService.java:44) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) + at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamService$$SpringCGLIB$$0.addExam() + at org.pkwmtt.examCalendar.ExamController.addExam(ExamController.java:34) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:174) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamController$$SpringCGLIB$$0.addExam() + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:258) + at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:191) + at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:986) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:891) + at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) + at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) + at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) + at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) + at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590) + at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.security.web.FilterChainProxy.lambda$doFilterInternal$3(FilterChainProxy.java:231) + at org.springframework.security.web.ObservationFilterChainDecorator$FilterObservation$SimpleFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:479) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:340) + at org.springframework.security.web.ObservationFilterChainDecorator.lambda$wrapSecured$0(ObservationFilterChainDecorator.java:82) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:128) + at org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:101) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:125) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:131) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:85) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) + at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$0(ObservationFilterChainDecorator.java:323) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:224) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233) + at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.ServletRequestPathFilter.doFilter(ServletRequestPathFilter.java:52) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebSecurityConfiguration.java:319) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.servlet.handler.HandlerMappingIntrospector.lambda$createCacheFilter$4(HandlerMappingIntrospector.java:267) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebMvcSecurityConfiguration.java:240) + at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:362) + at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:278) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:114) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) + at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) + at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) + at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:116) + at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) + at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) + at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) + at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:398) + at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) + at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:903) + at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1769) + at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1189) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:658) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) + at java.base/java.lang.Thread.run(Thread.java:1575) +2025-08-25 12:54:18 ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.pkwmtt.exceptions.UnsupportedCountOfArgumentsException: Invalid count of arguments provided: 0 expected more than: 1 less than: 100] with root cause +org.pkwmtt.exceptions.UnsupportedCountOfArgumentsException: Invalid count of arguments provided: 0 expected more than: 1 less than: 100 + at org.pkwmtt.examCalendar.entity.Exam$Builder.build(Exam.java:52) + at org.pkwmtt.examCalendar.mapper.ExamDtoMapper.mapToNewExam(ExamDtoMapper.java:33) + at org.pkwmtt.examCalendar.ExamService.addExam(ExamService.java:44) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) + at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamService$$SpringCGLIB$$0.addExam() + at org.pkwmtt.examCalendar.ExamController.addExam(ExamController.java:34) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:174) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamController$$SpringCGLIB$$0.addExam() + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:258) + at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:191) + at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:986) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:891) + at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) + at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) + at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) + at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) + at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590) + at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.security.web.FilterChainProxy.lambda$doFilterInternal$3(FilterChainProxy.java:231) + at org.springframework.security.web.ObservationFilterChainDecorator$FilterObservation$SimpleFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:479) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:340) + at org.springframework.security.web.ObservationFilterChainDecorator.lambda$wrapSecured$0(ObservationFilterChainDecorator.java:82) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:128) + at org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:101) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:125) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:131) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:85) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) + at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$0(ObservationFilterChainDecorator.java:323) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:224) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233) + at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.ServletRequestPathFilter.doFilter(ServletRequestPathFilter.java:52) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebSecurityConfiguration.java:319) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.servlet.handler.HandlerMappingIntrospector.lambda$createCacheFilter$4(HandlerMappingIntrospector.java:267) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebMvcSecurityConfiguration.java:240) + at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:362) + at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:278) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:114) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) + at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) + at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) + at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:116) + at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) + at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) + at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) + at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:398) + at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) + at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:903) + at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1769) + at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1189) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:658) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) + at java.base/java.lang.Thread.run(Thread.java:1575) +2025-08-25 13:00:07 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'groups (name) values ('12K1')' at line 1 +2025-08-25 13:00:07 ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.dao.InvalidDataAccessResourceUsageException: could not execute statement [You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'groups (name) values ('12K1')' at line 1] [insert into groups (name) values (?)]; SQL [insert into groups (name) values (?)]] with root cause +java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'groups (name) values ('12K1')' at line 1 + at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:112) + at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:114) + at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:990) + at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1168) + at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1103) + at com.mysql.cj.jdbc.ClientPreparedStatement.executeLargeUpdate(ClientPreparedStatement.java:1450) + at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdate(ClientPreparedStatement.java:1086) + at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeUpdate(ProxyPreparedStatement.java:61) + at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeUpdate(HikariProxyPreparedStatement.java) + at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:194) + at org.hibernate.id.insert.GetGeneratedKeysDelegate.performMutation(GetGeneratedKeysDelegate.java:116) + at org.hibernate.engine.jdbc.mutation.internal.MutationExecutorSingleNonBatched.performNonBatchedOperations(MutationExecutorSingleNonBatched.java:47) + at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.execute(AbstractMutationExecutor.java:55) + at org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.doStaticInserts(InsertCoordinatorStandard.java:194) + at org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.coordinateInsert(InsertCoordinatorStandard.java:132) + at org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.insert(InsertCoordinatorStandard.java:95) + at org.hibernate.action.internal.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:85) + at org.hibernate.engine.spi.ActionQueue.execute(ActionQueue.java:682) + at org.hibernate.engine.spi.ActionQueue.addResolvedEntityInsertAction(ActionQueue.java:293) + at org.hibernate.engine.spi.ActionQueue.addInsertAction(ActionQueue.java:274) + at org.hibernate.engine.spi.ActionQueue.addAction(ActionQueue.java:324) + at org.hibernate.event.internal.AbstractSaveEventListener.addInsertAction(AbstractSaveEventListener.java:394) + at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:308) + at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:224) + at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:136) + at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:177) + at org.hibernate.event.internal.DefaultPersistEventListener.persist(DefaultPersistEventListener.java:95) + at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:79) + at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:55) + at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:127) + at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:761) + at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:745) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:364) + at jdk.proxy3/jdk.proxy3.$Proxy159.persist(Unknown Source) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:320) + at jdk.proxy3/jdk.proxy3.$Proxy159.persist(Unknown Source) + at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:653) + at org.springframework.data.jpa.repository.support.SimpleJpaRepository.saveAll(SimpleJpaRepository.java:679) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.data.repository.core.support.RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0(RepositoryMethodInvoker.java:277) + at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:170) + at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:158) + at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:515) + at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:284) + at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:734) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:174) + at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:149) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:69) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) + at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:138) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:165) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223) + at jdk.proxy3/jdk.proxy3.$Proxy169.saveAll(Unknown Source) + at org.pkwmtt.examCalendar.ExamService.verifyAndUpdateExamGroups(ExamService.java:129) + at org.pkwmtt.examCalendar.ExamService.addExam(ExamService.java:37) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) + at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamService$$SpringCGLIB$$0.addExam() + at org.pkwmtt.examCalendar.ExamController.addExam(ExamController.java:34) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:174) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamController$$SpringCGLIB$$0.addExam() + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:258) + at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:191) + at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:986) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:891) + at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) + at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) + at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) + at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) + at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590) + at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.security.web.FilterChainProxy.lambda$doFilterInternal$3(FilterChainProxy.java:231) + at org.springframework.security.web.ObservationFilterChainDecorator$FilterObservation$SimpleFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:479) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:340) + at org.springframework.security.web.ObservationFilterChainDecorator.lambda$wrapSecured$0(ObservationFilterChainDecorator.java:82) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:128) + at org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:101) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:125) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:131) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:85) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) + at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$0(ObservationFilterChainDecorator.java:323) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:224) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233) + at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.ServletRequestPathFilter.doFilter(ServletRequestPathFilter.java:52) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebSecurityConfiguration.java:319) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.servlet.handler.HandlerMappingIntrospector.lambda$createCacheFilter$4(HandlerMappingIntrospector.java:267) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebMvcSecurityConfiguration.java:240) + at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:362) + at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:278) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:114) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) + at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) + at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) + at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:116) + at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) + at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) + at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) + at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:398) + at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) + at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:903) + at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1769) + at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1189) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:658) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) + at java.base/java.lang.Thread.run(Thread.java:1575) +2025-08-25 13:06:48 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'groups (name) values ('12K1')' at line 1 +2025-08-25 13:06:48 ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.dao.InvalidDataAccessResourceUsageException: could not execute statement [You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'groups (name) values ('12K1')' at line 1] [insert into groups (name) values (?)]; SQL [insert into groups (name) values (?)]] with root cause +java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'groups (name) values ('12K1')' at line 1 + at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:112) + at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:114) + at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:990) + at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1168) + at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1103) + at com.mysql.cj.jdbc.ClientPreparedStatement.executeLargeUpdate(ClientPreparedStatement.java:1450) + at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdate(ClientPreparedStatement.java:1086) + at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeUpdate(ProxyPreparedStatement.java:61) + at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeUpdate(HikariProxyPreparedStatement.java) + at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:194) + at org.hibernate.id.insert.GetGeneratedKeysDelegate.performMutation(GetGeneratedKeysDelegate.java:116) + at org.hibernate.engine.jdbc.mutation.internal.MutationExecutorSingleNonBatched.performNonBatchedOperations(MutationExecutorSingleNonBatched.java:47) + at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.execute(AbstractMutationExecutor.java:55) + at org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.doStaticInserts(InsertCoordinatorStandard.java:194) + at org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.coordinateInsert(InsertCoordinatorStandard.java:132) + at org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.insert(InsertCoordinatorStandard.java:95) + at org.hibernate.action.internal.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:85) + at org.hibernate.engine.spi.ActionQueue.execute(ActionQueue.java:682) + at org.hibernate.engine.spi.ActionQueue.addResolvedEntityInsertAction(ActionQueue.java:293) + at org.hibernate.engine.spi.ActionQueue.addInsertAction(ActionQueue.java:274) + at org.hibernate.engine.spi.ActionQueue.addAction(ActionQueue.java:324) + at org.hibernate.event.internal.AbstractSaveEventListener.addInsertAction(AbstractSaveEventListener.java:394) + at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:308) + at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:224) + at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:136) + at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:177) + at org.hibernate.event.internal.DefaultPersistEventListener.persist(DefaultPersistEventListener.java:95) + at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:79) + at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:55) + at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:127) + at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:761) + at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:745) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:364) + at jdk.proxy3/jdk.proxy3.$Proxy159.persist(Unknown Source) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:320) + at jdk.proxy3/jdk.proxy3.$Proxy159.persist(Unknown Source) + at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:653) + at org.springframework.data.jpa.repository.support.SimpleJpaRepository.saveAll(SimpleJpaRepository.java:679) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.data.repository.core.support.RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0(RepositoryMethodInvoker.java:277) + at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:170) + at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:158) + at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:515) + at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:284) + at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:734) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:174) + at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:149) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:69) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) + at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:138) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:165) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223) + at jdk.proxy3/jdk.proxy3.$Proxy169.saveAll(Unknown Source) + at org.pkwmtt.examCalendar.ExamService.verifyAndUpdateExamGroups(ExamService.java:129) + at org.pkwmtt.examCalendar.ExamService.addExam(ExamService.java:37) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) + at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamService$$SpringCGLIB$$0.addExam() + at org.pkwmtt.examCalendar.ExamController.addExam(ExamController.java:34) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:174) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamController$$SpringCGLIB$$0.addExam() + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:258) + at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:191) + at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:986) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:891) + at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) + at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) + at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) + at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) + at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590) + at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.security.web.FilterChainProxy.lambda$doFilterInternal$3(FilterChainProxy.java:231) + at org.springframework.security.web.ObservationFilterChainDecorator$FilterObservation$SimpleFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:479) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:340) + at org.springframework.security.web.ObservationFilterChainDecorator.lambda$wrapSecured$0(ObservationFilterChainDecorator.java:82) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:128) + at org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:101) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:125) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:131) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:85) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) + at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$0(ObservationFilterChainDecorator.java:323) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:224) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233) + at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.ServletRequestPathFilter.doFilter(ServletRequestPathFilter.java:52) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebSecurityConfiguration.java:319) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.servlet.handler.HandlerMappingIntrospector.lambda$createCacheFilter$4(HandlerMappingIntrospector.java:267) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebMvcSecurityConfiguration.java:240) + at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:362) + at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:278) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:114) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) + at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) + at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) + at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:116) + at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) + at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) + at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) + at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:398) + at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) + at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:903) + at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1769) + at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1189) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:658) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) + at java.base/java.lang.Thread.run(Thread.java:1575) +2025-08-25 13:12:29 ERROR o.s.boot.SpringApplication - Application run failed +java.lang.TypeNotPresentException: Type org.pkwmtt.examCalendar.entity.Exam not present + at java.base/sun.reflect.generics.factory.CoreReflectionFactory.makeNamedType(CoreReflectionFactory.java:116) + at java.base/sun.reflect.generics.visitor.Reifier.visitClassTypeSignature(Reifier.java:125) + at java.base/sun.reflect.generics.tree.ClassTypeSignature.accept(ClassTypeSignature.java:49) + at java.base/sun.reflect.generics.visitor.Reifier.reifyTypeArguments(Reifier.java:68) + at java.base/sun.reflect.generics.visitor.Reifier.visitClassTypeSignature(Reifier.java:138) + at java.base/sun.reflect.generics.tree.ClassTypeSignature.accept(ClassTypeSignature.java:49) + at java.base/sun.reflect.generics.repository.ClassRepository.computeSuperInterfaces(ClassRepository.java:117) + at java.base/sun.reflect.generics.repository.ClassRepository.getSuperInterfaces(ClassRepository.java:95) + at java.base/java.lang.Class.getGenericInterfaces(Class.java:1385) + at org.springframework.core.ResolvableType.getInterfaces(ResolvableType.java:553) + at org.springframework.core.ResolvableType.as(ResolvableType.java:501) + at org.springframework.data.util.TypeDiscoverer.getSuperTypeInformation(TypeDiscoverer.java:263) + at org.springframework.data.util.ClassTypeInformation.getSuperTypeInformation(ClassTypeInformation.java:37) + at org.springframework.data.util.TypeInformation.getRequiredSuperTypeInformation(TypeInformation.java:327) + at org.springframework.data.repository.core.support.DefaultRepositoryMetadata.(DefaultRepositoryMetadata.java:55) + at org.springframework.data.repository.core.support.AbstractRepositoryMetadata.getMetadata(AbstractRepositoryMetadata.java:79) + at org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport.getRepositoryConfigurations(RepositoryConfigurationExtensionSupport.java:93) + at org.springframework.data.repository.config.RepositoryConfigurationDelegate.registerRepositoriesIn(RepositoryConfigurationDelegate.java:170) + at org.springframework.boot.autoconfigure.data.AbstractRepositoryConfigurationSourceSupport.registerBeanDefinitions(AbstractRepositoryConfigurationSourceSupport.java:62) + at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.lambda$loadBeanDefinitionsFromRegistrars$1(ConfigurationClassBeanDefinitionReader.java:398) + at java.base/java.util.LinkedHashMap.forEach(LinkedHashMap.java:987) + at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsFromRegistrars(ConfigurationClassBeanDefinitionReader.java:397) + at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass(ConfigurationClassBeanDefinitionReader.java:151) + at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitions(ConfigurationClassBeanDefinitionReader.java:123) + at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:430) + at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:290) + at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:349) + at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:118) + at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:791) + at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:609) + at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) + at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752) + at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439) + at org.springframework.boot.SpringApplication.run(SpringApplication.java:318) + at org.springframework.boot.SpringApplication.run(SpringApplication.java:1361) + at org.springframework.boot.SpringApplication.run(SpringApplication.java:1350) + at org.pkwmtt.PkwmttBackendApplication.main(PkwmttBackendApplication.java:12) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:50) +Caused by: java.lang.ClassNotFoundException: org.pkwmtt.examCalendar.entity.Exam + at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641) + at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188) + at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:528) + at java.base/java.lang.Class.forName0(Native Method) + at java.base/java.lang.Class.forName(Class.java:578) + at java.base/java.lang.Class.forName(Class.java:557) + at org.springframework.boot.devtools.restart.classloader.RestartClassLoader.loadClass(RestartClassLoader.java:121) + at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:528) + at java.base/java.lang.Class.forName0(Native Method) + at java.base/java.lang.Class.forName(Class.java:578) + at java.base/java.lang.Class.forName(Class.java:557) + at java.base/sun.reflect.generics.factory.CoreReflectionFactory.makeNamedType(CoreReflectionFactory.java:113) + ... 39 common frames omitted diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamController.java b/src/main/java/org/pkwmtt/examCalendar/ExamController.java index fd8c751..d11cf27 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamController.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamController.java @@ -75,8 +75,8 @@ public ResponseEntity getExam(@PathVariable int id) { * @return 200 ok with list of exams for specific group */ @GetMapping("/by-groups") - public ResponseEntity> getExams(@RequestParam Set groups){ - return ResponseEntity.ok(ExamDtoMapper.mapToExamDto(examService.getExamByGroups(groups))); + public ResponseEntity> getExams(@RequestParam Set groups){ + return ResponseEntity.ok(examService.getExamByGroups(groups)); } /** diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamService.java b/src/main/java/org/pkwmtt/examCalendar/ExamService.java index dc7b49e..8eb417f 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamService.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamService.java @@ -34,8 +34,7 @@ public class ExamService { */ public int addExam(ExamDto examDto) throws JsonProcessingException { - verifyAndUpdateExamGroups(examDto); - Set groups = groupRepository.findAllByNameIn(examDto.getExamGroups()); + Set groups = verifyAndUpdateExamGroups(examDto); // check if exam type exists ExamType examType = examTypeRepository.findByName(examDto.getExamType()) @@ -53,8 +52,7 @@ public void modifyExam(ExamDto examDto, int id) throws JsonProcessingException { // check if exam which would be modified exists examRepository.findById(id).orElseThrow(() -> new NoSuchElementWithProvidedIdException(id)); - verifyAndUpdateExamGroups(examDto); - Set groups = groupRepository.findAllByNameIn(examDto.getExamGroups()); + Set groups = verifyAndUpdateExamGroups(examDto); // check if exam type exists ExamType examType = examTypeRepository.findByName(examDto.getExamType()) @@ -109,29 +107,56 @@ private Set getGroupsFromTimetableService() throws JsonProcessingExcepti /** * verify if groups exists in timetable if exist updates database. * when timetable service is unavailable check groups of existing exams for verification + * * @param examDto */ - private void verifyAndUpdateExamGroups(ExamDto examDto) throws JsonProcessingException { - Set allGroups; + private Set verifyAndUpdateExamGroups(ExamDto examDto) throws JsonProcessingException { + Set allGeneralGroups; try { - allGroups = getGroupsFromTimetableService(); - if (!allGroups.containsAll(examDto.getExamGroups())) { - examDto.getExamGroups().removeAll(allGroups); - throw new InvalidGroupIdentifierException(examDto.getExamGroups()); - } else - groupRepository.saveAll(examDto.getExamGroups().stream() - .map(g -> StudentGroup.builder() - .name(g) - .build()) - .collect(Collectors.toSet()) - ); - } catch (JsonProcessingException | SpecifiedGeneralGroupDoesntExistsException | WebPageContentNotAvailableException e) { - allGroups = groupRepository.findAllByNameIn(examDto.getExamGroups()) - .stream() - .map(StudentGroup::getName) - .collect(Collectors.toSet()); - if (!allGroups.containsAll(examDto.getExamGroups())) +// only one general group could be assigned to subgroups + if (examDto.getGeneralGroups().size() != 1 && !examDto.getSubgroups().isEmpty()) + throw new InvalidGroupIdentifierException("ambiguous general group for subgroups"); + allGeneralGroups = new HashSet<>(timetableService.getGeneralGroupList()); +// verify generalGroups using timetable service + if (!allGeneralGroups.containsAll(examDto.getGeneralGroups())) + throw new InvalidGroupIdentifierException("one of generalGroups identifier is incorrect"); +// if subgroups exists verify them using timetable service (get subgroups for generalGroup) + if (!examDto.getSubgroups().isEmpty() && !new HashSet<>(timetableService.getAvailableSubGroups(examDto.getGeneralGroups().iterator().next())).containsAll(examDto.getSubgroups())) + throw new InvalidGroupIdentifierException("one or more of subgroups identifier is incorrect"); + +// change subgroups format from "L04" to "12K-L04" and save them to database + Set groups = reformatGroups(examDto); + return new HashSet<>(groupRepository.saveAll(groups.stream() + .map(g -> StudentGroup.builder() + .name(g) + .build()) + .collect(Collectors.toList()) + )); + } catch (WebPageContentNotAvailableException | JsonProcessingException | + SpecifiedGeneralGroupDoesntExistsException e) { + Set groups = reformatGroups(examDto); +// verify groups using database + Set studentGroups = groupRepository.findAllByNameIn(groups); + if (studentGroups.stream().map(StudentGroup::getName).collect(Collectors.toSet()) == groups) + return studentGroups; + else throw e; } } + + private static Set reformatGroups(ExamDto examDto) { + Set groups = new HashSet<>(); + if (examDto.getGeneralGroups().size() == 1) { +// change 12K2 to 12K + String generalGroupIdentifier = examDto.getGeneralGroups().iterator().next(); + generalGroupIdentifier = generalGroupIdentifier.substring(0, generalGroupIdentifier.length() - 1); + for (String groupName : examDto.getSubgroups()) { + groups.add(generalGroupIdentifier + "-" + groupName); + } + } + if (examDto.getSubgroups().isEmpty()) { + groups.addAll(examDto.getGeneralGroups()); + } + return groups; + } } diff --git a/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java b/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java index e1c9d84..765f078 100644 --- a/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java +++ b/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java @@ -29,5 +29,7 @@ public class ExamDto { private final String examType; @NotEmpty - private final Set examGroups; + private final Set generalGroups; + + private final Set subgroups; } diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java b/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java index 9859105..57c8d0e 100644 --- a/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java +++ b/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java @@ -29,6 +29,7 @@ public class Exam { private String description; +// TODO: set timezone for accurate hours of exam @Column(name = "exam_date", nullable = false) private LocalDateTime examDate; diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/StudentGroup.java b/src/main/java/org/pkwmtt/examCalendar/entity/StudentGroup.java index 72f016e..c8568c1 100644 --- a/src/main/java/org/pkwmtt/examCalendar/entity/StudentGroup.java +++ b/src/main/java/org/pkwmtt/examCalendar/entity/StudentGroup.java @@ -12,7 +12,7 @@ @RequiredArgsConstructor @AllArgsConstructor @Builder -@Table(name = "groups") +@Table(name = "`groups`") public class StudentGroup { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoMapper.java b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoMapper.java index 01dae15..5041c21 100644 --- a/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoMapper.java +++ b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoMapper.java @@ -50,17 +50,17 @@ public static Exam mapToExistingExam(ExamDto examDto, Set groups, .build(); } - public static Set mapToExamDto(Set exams) { - return exams.stream().map(ExamDtoMapper::mapToExamDto).collect(Collectors.toSet()); - } +// public static Set mapToExamDto(Set exams) { +// return exams.stream().map(ExamDtoMapper::mapToExamDto).collect(Collectors.toSet()); +// } - public static ExamDto mapToExamDto(Exam exam) { - return ExamDto.builder() - .title(exam.getTitle()) - .description(exam.getDescription()) - .date(exam.getExamDate()) - .examType(exam.getExamType().getName()) - .examGroups(exam.getGroups().stream().map(StudentGroup::getName).collect(Collectors.toSet())) - .build(); - } +// public static ExamDto mapToExamDto(Exam exam) { +// return ExamDto.builder() +// .title(exam.getTitle()) +// .description(exam.getDescription()) +// .date(exam.getExamDate()) +// .examType(exam.getExamType().getName()) +// .examGroups(exam.getGroups().stream().map(StudentGroup::getName).collect(Collectors.toSet())) +// .build(); +// } } From 29634a5cb9dd649abdb2dffb3c63c5f08f80bbef Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Tue, 26 Aug 2025 13:09:14 +0200 Subject: [PATCH 049/116] change group identifiers format --- logs/app.log | 381 ++++++++++++++++++ .../org/pkwmtt/examCalendar/ExamService.java | 13 +- 2 files changed, 388 insertions(+), 6 deletions(-) diff --git a/logs/app.log b/logs/app.log index 67b0c72..cd71c3e 100644 --- a/logs/app.log +++ b/logs/app.log @@ -3099,3 +3099,384 @@ Caused by: java.lang.ClassNotFoundException: org.pkwmtt.examCalendar.entity.Exam at java.base/java.lang.Class.forName(Class.java:557) at java.base/sun.reflect.generics.factory.CoreReflectionFactory.makeNamedType(CoreReflectionFactory.java:113) ... 39 common frames omitted +2025-08-25 19:58:10 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'groups (name) values ('12K-P04')' at line 1 +2025-08-25 19:58:10 ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.dao.InvalidDataAccessResourceUsageException: could not execute statement [You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'groups (name) values ('12K-P04')' at line 1] [insert into groups (name) values (?)]; SQL [insert into groups (name) values (?)]] with root cause +java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'groups (name) values ('12K-P04')' at line 1 + at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:112) + at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:114) + at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:990) + at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1168) + at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1103) + at com.mysql.cj.jdbc.ClientPreparedStatement.executeLargeUpdate(ClientPreparedStatement.java:1450) + at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdate(ClientPreparedStatement.java:1086) + at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeUpdate(ProxyPreparedStatement.java:61) + at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeUpdate(HikariProxyPreparedStatement.java) + at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:194) + at org.hibernate.id.insert.GetGeneratedKeysDelegate.performMutation(GetGeneratedKeysDelegate.java:116) + at org.hibernate.engine.jdbc.mutation.internal.MutationExecutorSingleNonBatched.performNonBatchedOperations(MutationExecutorSingleNonBatched.java:47) + at org.hibernate.engine.jdbc.mutation.internal.AbstractMutationExecutor.execute(AbstractMutationExecutor.java:55) + at org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.doStaticInserts(InsertCoordinatorStandard.java:194) + at org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.coordinateInsert(InsertCoordinatorStandard.java:132) + at org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.insert(InsertCoordinatorStandard.java:95) + at org.hibernate.action.internal.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:85) + at org.hibernate.engine.spi.ActionQueue.execute(ActionQueue.java:682) + at org.hibernate.engine.spi.ActionQueue.addResolvedEntityInsertAction(ActionQueue.java:293) + at org.hibernate.engine.spi.ActionQueue.addInsertAction(ActionQueue.java:274) + at org.hibernate.engine.spi.ActionQueue.addAction(ActionQueue.java:324) + at org.hibernate.event.internal.AbstractSaveEventListener.addInsertAction(AbstractSaveEventListener.java:394) + at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:308) + at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:224) + at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:136) + at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:177) + at org.hibernate.event.internal.DefaultPersistEventListener.persist(DefaultPersistEventListener.java:95) + at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:79) + at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:55) + at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:127) + at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:761) + at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:745) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:364) + at jdk.proxy3/jdk.proxy3.$Proxy159.persist(Unknown Source) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:320) + at jdk.proxy3/jdk.proxy3.$Proxy159.persist(Unknown Source) + at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:653) + at org.springframework.data.jpa.repository.support.SimpleJpaRepository.saveAll(SimpleJpaRepository.java:679) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.data.repository.core.support.RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0(RepositoryMethodInvoker.java:277) + at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:170) + at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:158) + at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:515) + at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:284) + at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:734) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:174) + at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:149) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:69) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) + at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:138) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:165) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223) + at jdk.proxy3/jdk.proxy3.$Proxy169.saveAll(Unknown Source) + at org.pkwmtt.examCalendar.ExamService.verifyAndUpdateExamGroups(ExamService.java:129) + at org.pkwmtt.examCalendar.ExamService.addExam(ExamService.java:37) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) + at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamService$$SpringCGLIB$$0.addExam() + at org.pkwmtt.examCalendar.ExamController.addExam(ExamController.java:34) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:174) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamController$$SpringCGLIB$$0.addExam() + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:258) + at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:191) + at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:986) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:891) + at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) + at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) + at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) + at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) + at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590) + at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.security.web.FilterChainProxy.lambda$doFilterInternal$3(FilterChainProxy.java:231) + at org.springframework.security.web.ObservationFilterChainDecorator$FilterObservation$SimpleFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:479) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:340) + at org.springframework.security.web.ObservationFilterChainDecorator.lambda$wrapSecured$0(ObservationFilterChainDecorator.java:82) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:128) + at org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:101) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:125) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:131) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:85) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) + at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$0(ObservationFilterChainDecorator.java:323) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:224) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233) + at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.ServletRequestPathFilter.doFilter(ServletRequestPathFilter.java:52) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebSecurityConfiguration.java:319) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.servlet.handler.HandlerMappingIntrospector.lambda$createCacheFilter$4(HandlerMappingIntrospector.java:267) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebMvcSecurityConfiguration.java:240) + at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:362) + at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:278) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:114) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) + at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) + at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) + at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:116) + at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) + at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) + at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) + at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:398) + at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) + at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:903) + at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1769) + at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1189) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:658) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) + at java.base/java.lang.Thread.run(Thread.java:1575) +2025-08-26 13:07:50 ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.pkwmtt.exceptions.InvalidGroupIdentifierException: Invalid group identifier: ambiguous general group for subgroups] with root cause +org.pkwmtt.exceptions.InvalidGroupIdentifierException: Invalid group identifier: ambiguous general group for subgroups + at org.pkwmtt.examCalendar.ExamService.verifyAndUpdateExamGroups(ExamService.java:118) + at org.pkwmtt.examCalendar.ExamService.addExam(ExamService.java:37) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) + at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamService$$SpringCGLIB$$0.addExam() + at org.pkwmtt.examCalendar.ExamController.addExam(ExamController.java:34) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:174) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamController$$SpringCGLIB$$0.addExam() + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:258) + at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:191) + at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:986) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:891) + at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) + at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) + at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) + at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) + at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590) + at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.security.web.FilterChainProxy.lambda$doFilterInternal$3(FilterChainProxy.java:231) + at org.springframework.security.web.ObservationFilterChainDecorator$FilterObservation$SimpleFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:479) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:340) + at org.springframework.security.web.ObservationFilterChainDecorator.lambda$wrapSecured$0(ObservationFilterChainDecorator.java:82) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:128) + at org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:101) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:125) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:131) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:85) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) + at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$0(ObservationFilterChainDecorator.java:323) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:224) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233) + at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.ServletRequestPathFilter.doFilter(ServletRequestPathFilter.java:52) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebSecurityConfiguration.java:319) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.servlet.handler.HandlerMappingIntrospector.lambda$createCacheFilter$4(HandlerMappingIntrospector.java:267) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebMvcSecurityConfiguration.java:240) + at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:362) + at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:278) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:114) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) + at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) + at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) + at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:116) + at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) + at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) + at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) + at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:398) + at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) + at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:903) + at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1769) + at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1189) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:658) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) + at java.base/java.lang.Thread.run(Thread.java:1575) diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamService.java b/src/main/java/org/pkwmtt/examCalendar/ExamService.java index 8eb417f..cbd7c84 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamService.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamService.java @@ -120,6 +120,7 @@ private Set verifyAndUpdateExamGroups(ExamDto examDto) throws Json // verify generalGroups using timetable service if (!allGeneralGroups.containsAll(examDto.getGeneralGroups())) throw new InvalidGroupIdentifierException("one of generalGroups identifier is incorrect"); +// TODO: find not valid identifier // if subgroups exists verify them using timetable service (get subgroups for generalGroup) if (!examDto.getSubgroups().isEmpty() && !new HashSet<>(timetableService.getAvailableSubGroups(examDto.getGeneralGroups().iterator().next())).containsAll(examDto.getSubgroups())) throw new InvalidGroupIdentifierException("one or more of subgroups identifier is incorrect"); @@ -146,13 +147,13 @@ private Set verifyAndUpdateExamGroups(ExamDto examDto) throws Json private static Set reformatGroups(ExamDto examDto) { Set groups = new HashSet<>(); - if (examDto.getGeneralGroups().size() == 1) { -// change 12K2 to 12K + if (examDto.getGeneralGroups().size() == 1 && !examDto.getSubgroups().isEmpty()) { +// change 12K2 to 12K and add to groups String generalGroupIdentifier = examDto.getGeneralGroups().iterator().next(); - generalGroupIdentifier = generalGroupIdentifier.substring(0, generalGroupIdentifier.length() - 1); - for (String groupName : examDto.getSubgroups()) { - groups.add(generalGroupIdentifier + "-" + groupName); - } + if(Character.isDigit(generalGroupIdentifier.charAt(generalGroupIdentifier.length() - 1))) + generalGroupIdentifier = generalGroupIdentifier.substring(0, generalGroupIdentifier.length() - 1); + groups.add(generalGroupIdentifier); + groups.addAll(examDto.getSubgroups()); } if (examDto.getSubgroups().isEmpty()) { groups.addAll(examDto.getGeneralGroups()); From cda66941fc4311645aff531045378b289617dac2 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Tue, 26 Aug 2025 16:27:33 +0200 Subject: [PATCH 050/116] refactor examService --- logs/app.log | 959 ++++++++++++++++++ .../pkwmtt/examCalendar/ExamController.java | 4 +- .../org/pkwmtt/examCalendar/ExamService.java | 136 ++- .../examCalendar/entity/StudentGroup.java | 2 +- .../ServiceNotAvailableException.java | 7 + 5 files changed, 1057 insertions(+), 51 deletions(-) create mode 100644 src/main/java/org/pkwmtt/exceptions/ServiceNotAvailableException.java diff --git a/logs/app.log b/logs/app.log index cd71c3e..4ea5a45 100644 --- a/logs/app.log +++ b/logs/app.log @@ -3480,3 +3480,962 @@ org.pkwmtt.exceptions.InvalidGroupIdentifierException: Invalid group identifier: at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:658) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) at java.base/java.lang.Thread.run(Thread.java:1575) +2025-08-26 15:05:56 ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.pkwmtt.exceptions.InvalidGroupIdentifierException: Invalid group identifiers: [not_valid_group]] with root cause +org.pkwmtt.exceptions.InvalidGroupIdentifierException: Invalid group identifiers: [not_valid_group] + at org.pkwmtt.examCalendar.ExamService.verifyAndUpdateExamGroups(ExamService.java:126) + at org.pkwmtt.examCalendar.ExamService.addExam(ExamService.java:37) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) + at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamService$$SpringCGLIB$$0.addExam() + at org.pkwmtt.examCalendar.ExamController.addExam(ExamController.java:34) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:174) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamController$$SpringCGLIB$$0.addExam() + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:258) + at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:191) + at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:986) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:891) + at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) + at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) + at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) + at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) + at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590) + at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.security.web.FilterChainProxy.lambda$doFilterInternal$3(FilterChainProxy.java:231) + at org.springframework.security.web.ObservationFilterChainDecorator$FilterObservation$SimpleFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:479) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:340) + at org.springframework.security.web.ObservationFilterChainDecorator.lambda$wrapSecured$0(ObservationFilterChainDecorator.java:82) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:128) + at org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:101) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:125) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:131) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:85) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) + at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$0(ObservationFilterChainDecorator.java:323) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:224) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233) + at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.ServletRequestPathFilter.doFilter(ServletRequestPathFilter.java:52) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebSecurityConfiguration.java:319) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.servlet.handler.HandlerMappingIntrospector.lambda$createCacheFilter$4(HandlerMappingIntrospector.java:267) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebMvcSecurityConfiguration.java:240) + at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:362) + at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:278) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:114) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) + at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) + at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) + at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:116) + at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) + at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) + at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) + at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:398) + at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) + at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:903) + at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1769) + at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1189) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:658) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) + at java.base/java.lang.Thread.run(Thread.java:1575) +2025-08-26 15:06:40 ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.pkwmtt.exceptions.InvalidGroupIdentifierException: Invalid group identifiers: []] with root cause +org.pkwmtt.exceptions.InvalidGroupIdentifierException: Invalid group identifiers: [] + at org.pkwmtt.examCalendar.ExamService.verifyAndUpdateExamGroups(ExamService.java:149) + at org.pkwmtt.examCalendar.ExamService.addExam(ExamService.java:37) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) + at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamService$$SpringCGLIB$$0.addExam() + at org.pkwmtt.examCalendar.ExamController.addExam(ExamController.java:34) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:174) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamController$$SpringCGLIB$$0.addExam() + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:258) + at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:191) + at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:986) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:891) + at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) + at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) + at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) + at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) + at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590) + at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.security.web.FilterChainProxy.lambda$doFilterInternal$3(FilterChainProxy.java:231) + at org.springframework.security.web.ObservationFilterChainDecorator$FilterObservation$SimpleFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:479) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:340) + at org.springframework.security.web.ObservationFilterChainDecorator.lambda$wrapSecured$0(ObservationFilterChainDecorator.java:82) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:128) + at org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:101) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:125) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:131) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:85) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) + at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$0(ObservationFilterChainDecorator.java:323) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:224) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233) + at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.ServletRequestPathFilter.doFilter(ServletRequestPathFilter.java:52) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebSecurityConfiguration.java:319) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.servlet.handler.HandlerMappingIntrospector.lambda$createCacheFilter$4(HandlerMappingIntrospector.java:267) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebMvcSecurityConfiguration.java:240) + at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:362) + at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:278) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:114) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) + at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) + at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) + at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:116) + at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) + at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) + at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) + at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:398) + at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) + at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:903) + at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1769) + at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1189) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:658) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) + at java.base/java.lang.Thread.run(Thread.java:1575) +2025-08-26 16:13:18 ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.pkwmtt.exceptions.InvalidGroupIdentifierException: Invalid group identifiers: []] with root cause +org.pkwmtt.exceptions.InvalidGroupIdentifierException: Invalid group identifiers: [] + at org.pkwmtt.examCalendar.ExamService.verifyAndUpdateExamGroups(ExamService.java:149) + at org.pkwmtt.examCalendar.ExamService.addExam(ExamService.java:37) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) + at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamService$$SpringCGLIB$$0.addExam() + at org.pkwmtt.examCalendar.ExamController.addExam(ExamController.java:34) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:174) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamController$$SpringCGLIB$$0.addExam() + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:258) + at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:191) + at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:986) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:891) + at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) + at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) + at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) + at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) + at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590) + at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.security.web.FilterChainProxy.lambda$doFilterInternal$3(FilterChainProxy.java:231) + at org.springframework.security.web.ObservationFilterChainDecorator$FilterObservation$SimpleFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:479) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:340) + at org.springframework.security.web.ObservationFilterChainDecorator.lambda$wrapSecured$0(ObservationFilterChainDecorator.java:82) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:128) + at org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:101) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:125) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:131) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:85) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) + at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$0(ObservationFilterChainDecorator.java:323) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:224) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233) + at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.ServletRequestPathFilter.doFilter(ServletRequestPathFilter.java:52) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebSecurityConfiguration.java:319) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.servlet.handler.HandlerMappingIntrospector.lambda$createCacheFilter$4(HandlerMappingIntrospector.java:267) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebMvcSecurityConfiguration.java:240) + at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:362) + at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:278) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:114) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) + at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) + at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) + at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:116) + at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) + at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) + at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) + at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:398) + at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) + at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:903) + at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1769) + at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1189) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:658) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) + at java.base/java.lang.Thread.run(Thread.java:1575) +2025-08-26 16:16:07 ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.pkwmtt.exceptions.UnsupportedCountOfArgumentsException: Invalid count of arguments provided: 0 expected more than: 1 less than: 100] with root cause +org.pkwmtt.exceptions.UnsupportedCountOfArgumentsException: Invalid count of arguments provided: 0 expected more than: 1 less than: 100 + at org.pkwmtt.examCalendar.entity.Exam$Builder.build(Exam.java:53) + at org.pkwmtt.examCalendar.mapper.ExamDtoMapper.mapToNewExam(ExamDtoMapper.java:33) + at org.pkwmtt.examCalendar.ExamService.addExam(ExamService.java:44) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) + at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamService$$SpringCGLIB$$0.addExam() + at org.pkwmtt.examCalendar.ExamController.addExam(ExamController.java:34) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:174) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamController$$SpringCGLIB$$0.addExam() + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:258) + at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:191) + at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:986) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:891) + at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) + at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) + at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) + at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) + at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590) + at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.security.web.FilterChainProxy.lambda$doFilterInternal$3(FilterChainProxy.java:231) + at org.springframework.security.web.ObservationFilterChainDecorator$FilterObservation$SimpleFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:479) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:340) + at org.springframework.security.web.ObservationFilterChainDecorator.lambda$wrapSecured$0(ObservationFilterChainDecorator.java:82) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:128) + at org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:101) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:125) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:131) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:85) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) + at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$0(ObservationFilterChainDecorator.java:323) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:224) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233) + at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.ServletRequestPathFilter.doFilter(ServletRequestPathFilter.java:52) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebSecurityConfiguration.java:319) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.servlet.handler.HandlerMappingIntrospector.lambda$createCacheFilter$4(HandlerMappingIntrospector.java:267) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebMvcSecurityConfiguration.java:240) + at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:362) + at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:278) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:114) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) + at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) + at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) + at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:116) + at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) + at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) + at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) + at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:398) + at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) + at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:903) + at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1769) + at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1189) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:658) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) + at java.base/java.lang.Thread.run(Thread.java:1575) +2025-08-26 16:20:35 ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.pkwmtt.exceptions.InvalidGroupIdentifierException: Invalid group identifier: ambiguous general group for subgroups] with root cause +org.pkwmtt.exceptions.InvalidGroupIdentifierException: Invalid group identifier: ambiguous general group for subgroups + at org.pkwmtt.examCalendar.ExamService.verifyAndUpdateExamGroups(ExamService.java:161) + at org.pkwmtt.examCalendar.ExamService.addExam(ExamService.java:37) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) + at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamService$$SpringCGLIB$$0.addExam() + at org.pkwmtt.examCalendar.ExamController.addExam(ExamController.java:34) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:174) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamController$$SpringCGLIB$$0.addExam() + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:258) + at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:191) + at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:986) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:891) + at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) + at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) + at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) + at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) + at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590) + at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.security.web.FilterChainProxy.lambda$doFilterInternal$3(FilterChainProxy.java:231) + at org.springframework.security.web.ObservationFilterChainDecorator$FilterObservation$SimpleFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:479) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:340) + at org.springframework.security.web.ObservationFilterChainDecorator.lambda$wrapSecured$0(ObservationFilterChainDecorator.java:82) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:128) + at org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:101) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:125) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:131) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:85) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) + at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$0(ObservationFilterChainDecorator.java:323) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:224) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233) + at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.ServletRequestPathFilter.doFilter(ServletRequestPathFilter.java:52) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebSecurityConfiguration.java:319) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.servlet.handler.HandlerMappingIntrospector.lambda$createCacheFilter$4(HandlerMappingIntrospector.java:267) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebMvcSecurityConfiguration.java:240) + at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:362) + at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:278) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:114) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) + at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) + at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) + at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:116) + at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) + at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) + at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) + at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:398) + at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) + at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:903) + at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1769) + at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1189) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:658) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) + at java.base/java.lang.Thread.run(Thread.java:1575) +2025-08-26 16:20:39 ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.pkwmtt.exceptions.UnsupportedCountOfArgumentsException: Invalid count of arguments provided: 0 expected more than: 1 less than: 100] with root cause +org.pkwmtt.exceptions.UnsupportedCountOfArgumentsException: Invalid count of arguments provided: 0 expected more than: 1 less than: 100 + at org.pkwmtt.examCalendar.entity.Exam$Builder.build(Exam.java:53) + at org.pkwmtt.examCalendar.mapper.ExamDtoMapper.mapToNewExam(ExamDtoMapper.java:33) + at org.pkwmtt.examCalendar.ExamService.addExam(ExamService.java:44) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) + at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamService$$SpringCGLIB$$0.addExam() + at org.pkwmtt.examCalendar.ExamController.addExam(ExamController.java:34) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:174) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamController$$SpringCGLIB$$0.addExam() + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:258) + at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:191) + at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:986) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:891) + at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) + at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) + at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) + at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) + at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590) + at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.security.web.FilterChainProxy.lambda$doFilterInternal$3(FilterChainProxy.java:231) + at org.springframework.security.web.ObservationFilterChainDecorator$FilterObservation$SimpleFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:479) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:340) + at org.springframework.security.web.ObservationFilterChainDecorator.lambda$wrapSecured$0(ObservationFilterChainDecorator.java:82) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:128) + at org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:101) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:125) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:131) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:85) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) + at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$0(ObservationFilterChainDecorator.java:323) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:224) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233) + at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.ServletRequestPathFilter.doFilter(ServletRequestPathFilter.java:52) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebSecurityConfiguration.java:319) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.servlet.handler.HandlerMappingIntrospector.lambda$createCacheFilter$4(HandlerMappingIntrospector.java:267) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebMvcSecurityConfiguration.java:240) + at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:362) + at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:278) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:114) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) + at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) + at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) + at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:116) + at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) + at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) + at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) + at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:398) + at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) + at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:903) + at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1769) + at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1189) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:658) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) + at java.base/java.lang.Thread.run(Thread.java:1575) +2025-08-26 16:26:41 ERROR o.s.b.d.LoggingFailureAnalysisReporter - + +*************************** +APPLICATION FAILED TO START +*************************** + +Description: + +Parameter 0 of constructor in org.pkwmtt.examCalendar.ExamController required a bean of type 'org.pkwmtt.examCalendar.ExamService' that could not be found. + + +Action: + +Consider defining a bean of type 'org.pkwmtt.examCalendar.ExamService' in your configuration. + diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamController.java b/src/main/java/org/pkwmtt/examCalendar/ExamController.java index d11cf27..9427dc1 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamController.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamController.java @@ -30,7 +30,7 @@ public class ExamController { * @return 201 created with URI to GET method which returns created resource */ @PostMapping("") - public ResponseEntity addExam(@RequestBody @Valid ExamDto examDto) throws JsonProcessingException { + public ResponseEntity addExam(@RequestBody @Valid ExamDto examDto){ int id = examService.addExam(examDto); URI uri = ServletUriComponentsBuilder .fromCurrentRequest() @@ -46,7 +46,7 @@ public ResponseEntity addExam(@RequestBody @Valid ExamDto examDto) throws * @return 204 no content */ @PutMapping("/{id}") - public ResponseEntity modifyExam(@PathVariable @Positive int id, @RequestBody @Valid ExamDto examDto) throws JsonProcessingException { + public ResponseEntity modifyExam(@PathVariable @Positive int id, @RequestBody @Valid ExamDto examDto) { examService.modifyExam(examDto, id); return ResponseEntity.noContent().build(); } diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamService.java b/src/main/java/org/pkwmtt/examCalendar/ExamService.java index cbd7c84..b08dc86 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamService.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamService.java @@ -32,7 +32,7 @@ public class ExamService { * @param examDto details of exam * @return id of exam added to database */ - public int addExam(ExamDto examDto) throws JsonProcessingException { + public int addExam(ExamDto examDto) { Set groups = verifyAndUpdateExamGroups(examDto); @@ -48,7 +48,7 @@ public int addExam(ExamDto examDto) throws JsonProcessingException { * @param examDto new details of exam that overwrite old ones * @param id of exam that need to be modified */ - public void modifyExam(ExamDto examDto, int id) throws JsonProcessingException { + public void modifyExam(ExamDto examDto, int id) { // check if exam which would be modified exists examRepository.findById(id).orElseThrow(() -> new NoSuchElementWithProvidedIdException(id)); @@ -106,58 +106,98 @@ private Set getGroupsFromTimetableService() throws JsonProcessingExcepti /** * verify if groups exists in timetable if exist updates database. - * when timetable service is unavailable check groups of existing exams for verification + * when timetable service is unavailable verifies groups using groupsRepository * - * @param examDto + * @param examDto containing groups for verification */ - private Set verifyAndUpdateExamGroups(ExamDto examDto) throws JsonProcessingException { - Set allGeneralGroups; + private Set verifyAndUpdateExamGroups(ExamDto examDto) { + Set generalGroupsGromRepository; + Set generalGroups = examDto.getGeneralGroups(); + Set subgroups = examDto.getSubgroups(); +// if timetable service is unavailable verify general groups using GroupRepository try { -// only one general group could be assigned to subgroups - if (examDto.getGeneralGroups().size() != 1 && !examDto.getSubgroups().isEmpty()) - throw new InvalidGroupIdentifierException("ambiguous general group for subgroups"); - allGeneralGroups = new HashSet<>(timetableService.getGeneralGroupList()); + generalGroupsGromRepository = new HashSet<>(timetableService.getGeneralGroupList()); + } catch (WebPageContentNotAvailableException e) { + generalGroupsGromRepository = verifyUsingRepository(generalGroups); + } // verify generalGroups using timetable service - if (!allGeneralGroups.containsAll(examDto.getGeneralGroups())) - throw new InvalidGroupIdentifierException("one of generalGroups identifier is incorrect"); -// TODO: find not valid identifier -// if subgroups exists verify them using timetable service (get subgroups for generalGroup) - if (!examDto.getSubgroups().isEmpty() && !new HashSet<>(timetableService.getAvailableSubGroups(examDto.getGeneralGroups().iterator().next())).containsAll(examDto.getSubgroups())) - throw new InvalidGroupIdentifierException("one or more of subgroups identifier is incorrect"); - -// change subgroups format from "L04" to "12K-L04" and save them to database - Set groups = reformatGroups(examDto); - return new HashSet<>(groupRepository.saveAll(groups.stream() - .map(g -> StudentGroup.builder() - .name(g) - .build()) - .collect(Collectors.toList()) - )); - } catch (WebPageContentNotAvailableException | JsonProcessingException | - SpecifiedGeneralGroupDoesntExistsException e) { - Set groups = reformatGroups(examDto); -// verify groups using database - Set studentGroups = groupRepository.findAllByNameIn(groups); - if (studentGroups.stream().map(StudentGroup::getName).collect(Collectors.toSet()) == groups) - return studentGroups; - else - throw e; + if (!generalGroupsGromRepository.containsAll(generalGroups)) { + generalGroups.removeAll(generalGroupsGromRepository); + throw new InvalidGroupIdentifierException(generalGroups); + } +// if there are no subgroups save exam for exercise groups or whole year e.g. +// 12K2 - exercise group exam +// 12K1, 12K2, 12K3 - whole year exam + if (subgroups.isEmpty()) { +// TODO: change groups.name in database to unique + return saveNewStudentGroups(generalGroups); +// exams for subgroups e.g. L04 must have only superior group to avoid ambiguity + } else if (generalGroups.size() == 1) { +// if there are only one group change it from Set to String + String superiorGroup = generalGroups.iterator().next(); + Set subGroupsFromTimetable; + try { + subGroupsFromTimetable = new HashSet<>(timetableService.getAvailableSubGroups(superiorGroup)); + } catch (JsonProcessingException | + SpecifiedGeneralGroupDoesntExistsException | + WebPageContentNotAvailableException e) { + throw new ServiceNotAvailableException("Couldn't verify groups using timetable service"); +// TODO: add verification with repository when timetable service is unavailable + } +// verify if subgroups for specific general group exists + if (!subGroupsFromTimetable.containsAll(subgroups)) { + subgroups.removeAll(subGroupsFromTimetable); + throw new InvalidGroupIdentifierException(subgroups); + } +// change superior group format e.g. 12K2 to 12K + if (Character.isDigit(superiorGroup.charAt(superiorGroup.length() - 1))) + superiorGroup = superiorGroup.substring(0, superiorGroup.length() - 1); +// save subgroups with superior group identifier + subgroups.add(superiorGroup); + return saveNewStudentGroups(subgroups); } +// only one general group could be assigned to subgroups (when there are more than 1 general group and +// more than 0 subgroups) + else + throw new InvalidGroupIdentifierException("ambiguous general group for subgroups"); } - private static Set reformatGroups(ExamDto examDto) { - Set groups = new HashSet<>(); - if (examDto.getGeneralGroups().size() == 1 && !examDto.getSubgroups().isEmpty()) { -// change 12K2 to 12K and add to groups - String generalGroupIdentifier = examDto.getGeneralGroups().iterator().next(); - if(Character.isDigit(generalGroupIdentifier.charAt(generalGroupIdentifier.length() - 1))) - generalGroupIdentifier = generalGroupIdentifier.substring(0, generalGroupIdentifier.length() - 1); - groups.add(generalGroupIdentifier); - groups.addAll(examDto.getSubgroups()); - } - if (examDto.getSubgroups().isEmpty()) { - groups.addAll(examDto.getGeneralGroups()); - } - return groups; + /** + * @param groups groups that would be verified using repository + * @return set of groups (String) when verification succeeded + * @throws WebPageContentNotAvailableException when verification not succeeded + */ + private Set verifyUsingRepository(Set groups) throws WebPageContentNotAvailableException { + Set groupsFromRepository = groupRepository.findAllByNameIn(groups).stream() + .map(StudentGroup::getName) + .collect(Collectors.toSet() + ); + if (groupsFromRepository.containsAll(groups)) + return groups; + else + throw new ServiceNotAvailableException("Couldn't verify groups using repository"); + } + + /** + * saves groups to groupRepository, existing group names are filtered out before saving + * + * @param groups groups that would be saved to repository + * @return set of StudentsGroup Entity with names from groups. + */ + private Set saveNewStudentGroups(Set groups) { +// remove duplicates before saving records + Set existingGroups = groupRepository.findAllByNameIn(groups); + groups.removeAll(existingGroups.stream() + .map(StudentGroup::getName) + .collect(Collectors.toSet()) + ); + List savedGroups = groupRepository.saveAll(groups.stream() + .map(g -> StudentGroup.builder() + .name(g) + .build()) + .collect(Collectors.toList()) + ); + existingGroups.addAll(savedGroups); + return existingGroups; } } diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/StudentGroup.java b/src/main/java/org/pkwmtt/examCalendar/entity/StudentGroup.java index c8568c1..9cb963d 100644 --- a/src/main/java/org/pkwmtt/examCalendar/entity/StudentGroup.java +++ b/src/main/java/org/pkwmtt/examCalendar/entity/StudentGroup.java @@ -19,7 +19,7 @@ public class StudentGroup { @Column(name = "group_id") private Integer groupId; - @Column(nullable = false) + @Column(nullable = false, unique = true) private String name; // FIXME: remove? diff --git a/src/main/java/org/pkwmtt/exceptions/ServiceNotAvailableException.java b/src/main/java/org/pkwmtt/exceptions/ServiceNotAvailableException.java new file mode 100644 index 0000000..2f8b1b2 --- /dev/null +++ b/src/main/java/org/pkwmtt/exceptions/ServiceNotAvailableException.java @@ -0,0 +1,7 @@ +package org.pkwmtt.exceptions; + +public class ServiceNotAvailableException extends RuntimeException { + public ServiceNotAvailableException(String message) { + super(message); + } +} From 1b512ed786a197d5fe5e48b80c5f8b866cc46726 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Wed, 27 Aug 2025 14:00:46 +0200 Subject: [PATCH 051/116] implement getExams by groups with unlimited count of arguments --- logs/app.log | 494 ++++++++++++++++++ .../pkwmtt/examCalendar/ExamController.java | 9 +- .../org/pkwmtt/examCalendar/ExamService.java | 24 +- .../repository/ExamRepository.java | 28 +- 4 files changed, 542 insertions(+), 13 deletions(-) diff --git a/logs/app.log b/logs/app.log index 4ea5a45..acf37c8 100644 --- a/logs/app.log +++ b/logs/app.log @@ -4439,3 +4439,497 @@ Action: Consider defining a bean of type 'org.pkwmtt.examCalendar.ExamService' in your configuration. +2025-08-27 13:25:45 ERROR o.s.boot.SpringApplication - Application run failed +org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'examController' defined in file [D:\Java\PKWMTT\target\classes\org\pkwmtt\examCalendar\ExamController.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'examService' defined in file [D:\Java\PKWMTT\target\classes\org\pkwmtt\examCalendar\ExamService.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'examRepository' defined in org.pkwmtt.examCalendar.repository.ExamRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: Could not create query for public abstract java.util.List org.pkwmtt.examCalendar.repository.ExamRepository.findAllBySubgroupsOfGeneralGroup(java.lang.String,java.util.Set); Reason: Validation failed for query for method public abstract java.util.List org.pkwmtt.examCalendar.repository.ExamRepository.findAllBySubgroupsOfGeneralGroup(java.lang.String,java.util.Set) + at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:804) + at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:240) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1395) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1232) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529) + at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) + at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373) + at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) + at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) + at org.springframework.beans.factory.support.DefaultListableBeanFactory.instantiateSingleton(DefaultListableBeanFactory.java:1222) + at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingleton(DefaultListableBeanFactory.java:1188) + at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:1123) + at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:987) + at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:627) + at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) + at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752) + at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439) + at org.springframework.boot.SpringApplication.run(SpringApplication.java:318) + at org.springframework.boot.SpringApplication.run(SpringApplication.java:1361) + at org.springframework.boot.SpringApplication.run(SpringApplication.java:1350) + at org.pkwmtt.PkwmttBackendApplication.main(PkwmttBackendApplication.java:12) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:50) +Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'examService' defined in file [D:\Java\PKWMTT\target\classes\org\pkwmtt\examCalendar\ExamService.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'examRepository' defined in org.pkwmtt.examCalendar.repository.ExamRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: Could not create query for public abstract java.util.List org.pkwmtt.examCalendar.repository.ExamRepository.findAllBySubgroupsOfGeneralGroup(java.lang.String,java.util.Set); Reason: Validation failed for query for method public abstract java.util.List org.pkwmtt.examCalendar.repository.ExamRepository.findAllBySubgroupsOfGeneralGroup(java.lang.String,java.util.Set) + at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:804) + at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:240) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1395) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1232) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529) + at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) + at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373) + at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) + at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) + at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1683) + at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1628) + at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:913) + at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791) + ... 24 common frames omitted +Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'examRepository' defined in org.pkwmtt.examCalendar.repository.ExamRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: Could not create query for public abstract java.util.List org.pkwmtt.examCalendar.repository.ExamRepository.findAllBySubgroupsOfGeneralGroup(java.lang.String,java.util.Set); Reason: Validation failed for query for method public abstract java.util.List org.pkwmtt.examCalendar.repository.ExamRepository.findAllBySubgroupsOfGeneralGroup(java.lang.String,java.util.Set) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1826) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:607) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529) + at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) + at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373) + at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) + at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) + at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1683) + at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1628) + at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:913) + at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791) + ... 37 common frames omitted +Caused by: org.springframework.data.repository.query.QueryCreationException: Could not create query for public abstract java.util.List org.pkwmtt.examCalendar.repository.ExamRepository.findAllBySubgroupsOfGeneralGroup(java.lang.String,java.util.Set); Reason: Validation failed for query for method public abstract java.util.List org.pkwmtt.examCalendar.repository.ExamRepository.findAllBySubgroupsOfGeneralGroup(java.lang.String,java.util.Set) + at org.springframework.data.repository.query.QueryCreationException.create(QueryCreationException.java:101) + at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.lookupQuery(QueryExecutorMethodInterceptor.java:120) + at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.mapMethodsToQuery(QueryExecutorMethodInterceptor.java:104) + at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.lambda$new$0(QueryExecutorMethodInterceptor.java:92) + at java.base/java.util.Optional.map(Optional.java:260) + at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.(QueryExecutorMethodInterceptor.java:92) + at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:434) + at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.lambda$afterPropertiesSet$4(RepositoryFactoryBeanSupport.java:350) + at org.springframework.data.util.Lazy.getNullable(Lazy.java:135) + at org.springframework.data.util.Lazy.get(Lazy.java:113) + at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.afterPropertiesSet(RepositoryFactoryBeanSupport.java:356) + at org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean.afterPropertiesSet(JpaRepositoryFactoryBean.java:132) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1873) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1822) + ... 47 common frames omitted +Caused by: java.lang.IllegalArgumentException: Validation failed for query for method public abstract java.util.List org.pkwmtt.examCalendar.repository.ExamRepository.findAllBySubgroupsOfGeneralGroup(java.lang.String,java.util.Set) + at org.springframework.data.jpa.repository.query.SimpleJpaQuery.validateQuery(SimpleJpaQuery.java:97) + at org.springframework.data.jpa.repository.query.SimpleJpaQuery.(SimpleJpaQuery.java:67) + at org.springframework.data.jpa.repository.query.JpaQueryFactory.fromMethodWithQueryString(JpaQueryFactory.java:49) + at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$DeclaredQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:174) + at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$CreateIfNotFoundQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:254) + at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$AbstractQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:99) + at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.lookupQuery(QueryExecutorMethodInterceptor.java:116) + ... 59 common frames omitted +Caused by: org.hibernate.query.sqm.InterpretationException: Error interpreting query [Cannot fetch the same association twice with a different alias] [ SELECT DISTINCT e FROM Exam e + JOIN FETCH e.groups g1 + JOIN FETCH e.groups g2 + WHERE g1.name = :general AND g2.name IN :sub +] + at org.hibernate.query.hql.internal.StandardHqlTranslator.translate(StandardHqlTranslator.java:95) + at org.hibernate.query.internal.QueryInterpretationCacheStandardImpl.createHqlInterpretation(QueryInterpretationCacheStandardImpl.java:145) + at org.hibernate.query.internal.QueryInterpretationCacheStandardImpl.resolveHqlInterpretation(QueryInterpretationCacheStandardImpl.java:132) + at org.hibernate.query.spi.QueryEngine.interpretHql(QueryEngine.java:54) + at org.hibernate.internal.AbstractSharedSessionContract.interpretHql(AbstractSharedSessionContract.java:832) + at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:878) + at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:796) + at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:143) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:364) + at jdk.proxy3/jdk.proxy3.$Proxy159.createQuery(Unknown Source) + at org.springframework.data.jpa.repository.query.SimpleJpaQuery.validateQuery(SimpleJpaQuery.java:91) + ... 65 common frames omitted +Caused by: java.lang.IllegalStateException: Cannot fetch the same association twice with a different alias + at org.hibernate.query.hql.internal.QualifiedJoinPathConsumer.createJoin(QualifiedJoinPathConsumer.java:209) + at org.hibernate.query.hql.internal.QualifiedJoinPathConsumer$AttributeJoinDelegate.consumeIdentifier(QualifiedJoinPathConsumer.java:271) + at org.hibernate.query.hql.internal.QualifiedJoinPathConsumer.consumeIdentifier(QualifiedJoinPathConsumer.java:107) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitSimplePath(SemanticQueryBuilder.java:5465) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitGeneralPathFragment(SemanticQueryBuilder.java:5298) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitGeneralPathFragment(SemanticQueryBuilder.java:277) + at org.hibernate.grammars.hql.HqlParser$GeneralPathFragmentContext.accept(HqlParser.java:4789) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitPath(SemanticQueryBuilder.java:5289) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitPath(SemanticQueryBuilder.java:277) + at org.hibernate.grammars.hql.HqlParser$PathContext.accept(HqlParser.java:4519) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.getJoin(SemanticQueryBuilder.java:2280) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.consumeJoin(SemanticQueryBuilder.java:2224) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitEntityWithJoins(SemanticQueryBuilder.java:2037) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitFromClause(SemanticQueryBuilder.java:2016) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitQuery(SemanticQueryBuilder.java:1248) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitQuerySpecExpression(SemanticQueryBuilder.java:1040) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitQuerySpecExpression(SemanticQueryBuilder.java:277) + at org.hibernate.grammars.hql.HqlParser$QuerySpecExpressionContext.accept(HqlParser.java:2134) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitSimpleQueryGroup(SemanticQueryBuilder.java:1025) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitSimpleQueryGroup(SemanticQueryBuilder.java:277) + at org.hibernate.grammars.hql.HqlParser$SimpleQueryGroupContext.accept(HqlParser.java:2005) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitSelectStatement(SemanticQueryBuilder.java:492) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitStatement(SemanticQueryBuilder.java:451) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.buildSemanticModel(SemanticQueryBuilder.java:324) + at org.hibernate.query.hql.internal.StandardHqlTranslator.translate(StandardHqlTranslator.java:71) + ... 77 common frames omitted +2025-08-27 13:27:06 ERROR o.s.boot.SpringApplication - Application run failed +org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'examController' defined in file [D:\Java\PKWMTT\target\classes\org\pkwmtt\examCalendar\ExamController.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'examService' defined in file [D:\Java\PKWMTT\target\classes\org\pkwmtt\examCalendar\ExamService.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'examRepository' defined in org.pkwmtt.examCalendar.repository.ExamRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: Could not create query for public abstract java.util.List org.pkwmtt.examCalendar.repository.ExamRepository.findAllBySubgroupsOfGeneralGroup(java.lang.String,java.util.Set); Reason: Validation failed for query for method public abstract java.util.List org.pkwmtt.examCalendar.repository.ExamRepository.findAllBySubgroupsOfGeneralGroup(java.lang.String,java.util.Set) + at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:804) + at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:240) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1395) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1232) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529) + at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) + at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373) + at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) + at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) + at org.springframework.beans.factory.support.DefaultListableBeanFactory.instantiateSingleton(DefaultListableBeanFactory.java:1222) + at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingleton(DefaultListableBeanFactory.java:1188) + at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:1123) + at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:987) + at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:627) + at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) + at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752) + at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439) + at org.springframework.boot.SpringApplication.run(SpringApplication.java:318) + at org.springframework.boot.SpringApplication.run(SpringApplication.java:1361) + at org.springframework.boot.SpringApplication.run(SpringApplication.java:1350) + at org.pkwmtt.PkwmttBackendApplication.main(PkwmttBackendApplication.java:12) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:50) +Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'examService' defined in file [D:\Java\PKWMTT\target\classes\org\pkwmtt\examCalendar\ExamService.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'examRepository' defined in org.pkwmtt.examCalendar.repository.ExamRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: Could not create query for public abstract java.util.List org.pkwmtt.examCalendar.repository.ExamRepository.findAllBySubgroupsOfGeneralGroup(java.lang.String,java.util.Set); Reason: Validation failed for query for method public abstract java.util.List org.pkwmtt.examCalendar.repository.ExamRepository.findAllBySubgroupsOfGeneralGroup(java.lang.String,java.util.Set) + at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:804) + at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:240) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1395) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1232) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529) + at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) + at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373) + at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) + at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) + at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1683) + at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1628) + at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:913) + at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791) + ... 24 common frames omitted +Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'examRepository' defined in org.pkwmtt.examCalendar.repository.ExamRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: Could not create query for public abstract java.util.List org.pkwmtt.examCalendar.repository.ExamRepository.findAllBySubgroupsOfGeneralGroup(java.lang.String,java.util.Set); Reason: Validation failed for query for method public abstract java.util.List org.pkwmtt.examCalendar.repository.ExamRepository.findAllBySubgroupsOfGeneralGroup(java.lang.String,java.util.Set) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1826) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:607) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529) + at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) + at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373) + at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) + at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) + at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1683) + at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1628) + at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:913) + at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791) + ... 37 common frames omitted +Caused by: org.springframework.data.repository.query.QueryCreationException: Could not create query for public abstract java.util.List org.pkwmtt.examCalendar.repository.ExamRepository.findAllBySubgroupsOfGeneralGroup(java.lang.String,java.util.Set); Reason: Validation failed for query for method public abstract java.util.List org.pkwmtt.examCalendar.repository.ExamRepository.findAllBySubgroupsOfGeneralGroup(java.lang.String,java.util.Set) + at org.springframework.data.repository.query.QueryCreationException.create(QueryCreationException.java:101) + at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.lookupQuery(QueryExecutorMethodInterceptor.java:120) + at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.mapMethodsToQuery(QueryExecutorMethodInterceptor.java:104) + at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.lambda$new$0(QueryExecutorMethodInterceptor.java:92) + at java.base/java.util.Optional.map(Optional.java:260) + at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.(QueryExecutorMethodInterceptor.java:92) + at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:434) + at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.lambda$afterPropertiesSet$4(RepositoryFactoryBeanSupport.java:350) + at org.springframework.data.util.Lazy.getNullable(Lazy.java:135) + at org.springframework.data.util.Lazy.get(Lazy.java:113) + at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.afterPropertiesSet(RepositoryFactoryBeanSupport.java:356) + at org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean.afterPropertiesSet(JpaRepositoryFactoryBean.java:132) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1873) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1822) + ... 47 common frames omitted +Caused by: java.lang.IllegalArgumentException: Validation failed for query for method public abstract java.util.List org.pkwmtt.examCalendar.repository.ExamRepository.findAllBySubgroupsOfGeneralGroup(java.lang.String,java.util.Set) + at org.springframework.data.jpa.repository.query.SimpleJpaQuery.validateQuery(SimpleJpaQuery.java:97) + at org.springframework.data.jpa.repository.query.SimpleJpaQuery.(SimpleJpaQuery.java:67) + at org.springframework.data.jpa.repository.query.JpaQueryFactory.fromMethodWithQueryString(JpaQueryFactory.java:49) + at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$DeclaredQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:174) + at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$CreateIfNotFoundQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:254) + at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$AbstractQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:99) + at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.lookupQuery(QueryExecutorMethodInterceptor.java:116) + ... 59 common frames omitted +Caused by: org.hibernate.query.sqm.InterpretationException: Error interpreting query [Cannot fetch the same association twice with a different alias] [ SELECT e FROM Exam e + JOIN FETCH e.groups g1 + JOIN FETCH e.groups g2 + WHERE g1.name = :general AND g2.name IN :sub +] + at org.hibernate.query.hql.internal.StandardHqlTranslator.translate(StandardHqlTranslator.java:95) + at org.hibernate.query.internal.QueryInterpretationCacheStandardImpl.createHqlInterpretation(QueryInterpretationCacheStandardImpl.java:145) + at org.hibernate.query.internal.QueryInterpretationCacheStandardImpl.resolveHqlInterpretation(QueryInterpretationCacheStandardImpl.java:132) + at org.hibernate.query.spi.QueryEngine.interpretHql(QueryEngine.java:54) + at org.hibernate.internal.AbstractSharedSessionContract.interpretHql(AbstractSharedSessionContract.java:832) + at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:878) + at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:796) + at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:143) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:364) + at jdk.proxy3/jdk.proxy3.$Proxy159.createQuery(Unknown Source) + at org.springframework.data.jpa.repository.query.SimpleJpaQuery.validateQuery(SimpleJpaQuery.java:91) + ... 65 common frames omitted +Caused by: java.lang.IllegalStateException: Cannot fetch the same association twice with a different alias + at org.hibernate.query.hql.internal.QualifiedJoinPathConsumer.createJoin(QualifiedJoinPathConsumer.java:209) + at org.hibernate.query.hql.internal.QualifiedJoinPathConsumer$AttributeJoinDelegate.consumeIdentifier(QualifiedJoinPathConsumer.java:271) + at org.hibernate.query.hql.internal.QualifiedJoinPathConsumer.consumeIdentifier(QualifiedJoinPathConsumer.java:107) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitSimplePath(SemanticQueryBuilder.java:5465) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitGeneralPathFragment(SemanticQueryBuilder.java:5298) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitGeneralPathFragment(SemanticQueryBuilder.java:277) + at org.hibernate.grammars.hql.HqlParser$GeneralPathFragmentContext.accept(HqlParser.java:4789) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitPath(SemanticQueryBuilder.java:5289) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitPath(SemanticQueryBuilder.java:277) + at org.hibernate.grammars.hql.HqlParser$PathContext.accept(HqlParser.java:4519) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.getJoin(SemanticQueryBuilder.java:2280) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.consumeJoin(SemanticQueryBuilder.java:2224) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitEntityWithJoins(SemanticQueryBuilder.java:2037) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitFromClause(SemanticQueryBuilder.java:2016) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitQuery(SemanticQueryBuilder.java:1248) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitQuerySpecExpression(SemanticQueryBuilder.java:1040) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitQuerySpecExpression(SemanticQueryBuilder.java:277) + at org.hibernate.grammars.hql.HqlParser$QuerySpecExpressionContext.accept(HqlParser.java:2134) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitSimpleQueryGroup(SemanticQueryBuilder.java:1025) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitSimpleQueryGroup(SemanticQueryBuilder.java:277) + at org.hibernate.grammars.hql.HqlParser$SimpleQueryGroupContext.accept(HqlParser.java:2005) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitSelectStatement(SemanticQueryBuilder.java:492) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.visitStatement(SemanticQueryBuilder.java:451) + at org.hibernate.query.hql.internal.SemanticQueryBuilder.buildSemanticModel(SemanticQueryBuilder.java:324) + at org.hibernate.query.hql.internal.StandardHqlTranslator.translate(StandardHqlTranslator.java:71) + ... 77 common frames omitted +2025-08-27 13:33:33 ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.NullPointerException: Cannot invoke "java.util.Set.isEmpty()" because "subgroups" is null] with root cause +java.lang.NullPointerException: Cannot invoke "java.util.Set.isEmpty()" because "subgroups" is null + at org.pkwmtt.examCalendar.ExamService.getExamByGroups(ExamService.java:88) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) + at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamService$$SpringCGLIB$$0.getExamByGroups() + at org.pkwmtt.examCalendar.ExamController.getExams(ExamController.java:82) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) + at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) + at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:174) + at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) + at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) + at org.pkwmtt.examCalendar.ExamController$$SpringCGLIB$$0.getExams() + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:258) + at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:191) + at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:986) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:891) + at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) + at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) + at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) + at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) + at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564) + at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.security.web.FilterChainProxy.lambda$doFilterInternal$3(FilterChainProxy.java:231) + at org.springframework.security.web.ObservationFilterChainDecorator$FilterObservation$SimpleFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:479) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:340) + at org.springframework.security.web.ObservationFilterChainDecorator.lambda$wrapSecured$0(ObservationFilterChainDecorator.java:82) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:128) + at org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:101) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:125) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:131) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:85) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) + at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$0(ObservationFilterChainDecorator.java:323) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:224) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233) + at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.ServletRequestPathFilter.doFilter(ServletRequestPathFilter.java:52) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebSecurityConfiguration.java:319) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.servlet.handler.HandlerMappingIntrospector.lambda$createCacheFilter$4(HandlerMappingIntrospector.java:267) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebMvcSecurityConfiguration.java:240) + at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:362) + at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:278) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:114) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) + at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) + at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) + at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:116) + at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) + at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) + at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) + at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:398) + at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) + at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:903) + at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1769) + at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1189) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:658) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) + at java.base/java.lang.Thread.run(Thread.java:1575) +2025-08-27 13:35:23 ERROR o.s.boot.SpringApplication - Application run failed +org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'examController': Resolution of declared constructors on bean Class [org.pkwmtt.examCalendar.ExamController] from ClassLoader [org.springframework.boot.devtools.restart.classloader.RestartClassLoader@7fcff7f7] failed + at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.determineCandidateConstructors(AutowiredAnnotationBeanPostProcessor.java:384) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.determineConstructorsFromBeanPostProcessors(AbstractAutowireCapableBeanFactory.java:1334) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1229) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529) + at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) + at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373) + at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) + at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) + at org.springframework.beans.factory.support.DefaultListableBeanFactory.instantiateSingleton(DefaultListableBeanFactory.java:1222) + at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingleton(DefaultListableBeanFactory.java:1188) + at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:1123) + at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:987) + at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:627) + at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) + at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752) + at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439) + at org.springframework.boot.SpringApplication.run(SpringApplication.java:318) + at org.springframework.boot.SpringApplication.run(SpringApplication.java:1361) + at org.springframework.boot.SpringApplication.run(SpringApplication.java:1350) + at org.pkwmtt.PkwmttBackendApplication.main(PkwmttBackendApplication.java:12) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:50) +Caused by: java.lang.NoClassDefFoundError: org/pkwmtt/examCalendar/ExamService + at java.base/java.lang.Class.getDeclaredConstructors0(Native Method) + at java.base/java.lang.Class.privateGetDeclaredConstructors(Class.java:3621) + at java.base/java.lang.Class.getDeclaredConstructors(Class.java:2786) + at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.determineCandidateConstructors(AutowiredAnnotationBeanPostProcessor.java:379) + ... 23 common frames omitted +Caused by: java.lang.ClassNotFoundException: org.pkwmtt.examCalendar.ExamService + at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641) + at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188) + at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:528) + at java.base/java.lang.Class.forName0(Native Method) + at java.base/java.lang.Class.forName(Class.java:578) + at java.base/java.lang.Class.forName(Class.java:557) + at org.springframework.boot.devtools.restart.classloader.RestartClassLoader.loadClass(RestartClassLoader.java:121) + at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:528) + ... 27 common frames omitted +2025-08-27 13:38:38 ERROR o.s.boot.SpringApplication - Application run failed +org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'examController': Resolution of declared constructors on bean Class [org.pkwmtt.examCalendar.ExamController] from ClassLoader [org.springframework.boot.devtools.restart.classloader.RestartClassLoader@1e690bbc] failed + at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.determineCandidateConstructors(AutowiredAnnotationBeanPostProcessor.java:384) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.determineConstructorsFromBeanPostProcessors(AbstractAutowireCapableBeanFactory.java:1334) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1229) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529) + at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) + at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373) + at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) + at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) + at org.springframework.beans.factory.support.DefaultListableBeanFactory.instantiateSingleton(DefaultListableBeanFactory.java:1222) + at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingleton(DefaultListableBeanFactory.java:1188) + at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:1123) + at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:987) + at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:627) + at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) + at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752) + at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439) + at org.springframework.boot.SpringApplication.run(SpringApplication.java:318) + at org.springframework.boot.SpringApplication.run(SpringApplication.java:1361) + at org.springframework.boot.SpringApplication.run(SpringApplication.java:1350) + at org.pkwmtt.PkwmttBackendApplication.main(PkwmttBackendApplication.java:12) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:50) +Caused by: java.lang.NoClassDefFoundError: org/pkwmtt/examCalendar/ExamService + at java.base/java.lang.Class.getDeclaredConstructors0(Native Method) + at java.base/java.lang.Class.privateGetDeclaredConstructors(Class.java:3621) + at java.base/java.lang.Class.getDeclaredConstructors(Class.java:2786) + at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.determineCandidateConstructors(AutowiredAnnotationBeanPostProcessor.java:379) + ... 23 common frames omitted +Caused by: java.lang.ClassNotFoundException: org.pkwmtt.examCalendar.ExamService + at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641) + at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188) + at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:528) + at java.base/java.lang.Class.forName0(Native Method) + at java.base/java.lang.Class.forName(Class.java:578) + at java.base/java.lang.Class.forName(Class.java:557) + at org.springframework.boot.devtools.restart.classloader.RestartClassLoader.loadClass(RestartClassLoader.java:121) + at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:528) + ... 27 common frames omitted diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamController.java b/src/main/java/org/pkwmtt/examCalendar/ExamController.java index 9427dc1..6edd1c0 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamController.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamController.java @@ -71,12 +71,15 @@ public ResponseEntity getExam(@PathVariable int id) { } /** - * @param groups set of groups +//TODO: update javadoc * @return 200 ok with list of exams for specific group */ @GetMapping("/by-groups") - public ResponseEntity> getExams(@RequestParam Set groups){ - return ResponseEntity.ok(examService.getExamByGroups(groups)); + public ResponseEntity> getExams( + @RequestParam Set generalGroups, + @RequestParam(required = false) Set subgroups + ){ + return ResponseEntity.ok(examService.getExamByGroups(generalGroups, subgroups)); } /** diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamService.java b/src/main/java/org/pkwmtt/examCalendar/ExamService.java index b08dc86..1c963a7 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamService.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamService.java @@ -77,15 +77,23 @@ public Exam getExamById(int id) { return examRepository.findById(id).orElseThrow(() -> new NoSuchElementWithProvidedIdException(id)); } - public Set getExamByGroups(Set groupNames) { -// validate provided groups - Set studentGroups = groupRepository.findAllByNameIn(groupNames); - Set groupNamesFromDatabase = studentGroups.stream().map(StudentGroup::getName).collect(Collectors.toSet()); - if (!groupNamesFromDatabase.equals(groupNames)) { - groupNames.removeAll(groupNamesFromDatabase); - throw new InvalidGroupIdentifierException(groupNames); + public List getExamByGroups(Set generalGroups, Set subgroups) { +// get exams for general groups + List exams = new ArrayList<>(examRepository.findAllByGroups_NameIn(generalGroups)); +// convert general group identifiers. e.g. 12K2 to 12K + Set superiorGroups = generalGroups.stream().map(g -> { + if (Character.isDigit(g.charAt(g.length() - 1))) + return g.substring(0, g.length() - 1); + return g; + }).collect(Collectors.toSet()); +// check if subgroups are provided + if(subgroups != null && !subgroups.isEmpty()){ +// check if superior group identifies the groups unambiguously + if(superiorGroups.size() != 1) + throw new InvalidGroupIdentifierException("ambiguous superior group identifier for subgroups"); + exams.addAll(examRepository.findAllBySubgroupsOfGeneralGroup(superiorGroups.iterator().next(), subgroups)); } - return examRepository.findByGroupsIn(studentGroups); + return exams; } /** diff --git a/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java index bcc1c9f..2b29276 100644 --- a/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java +++ b/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java @@ -5,13 +5,37 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import org.springframework.security.core.parameters.P; +import java.util.Collection; +import java.util.List; import java.util.Set; public interface ExamRepository extends JpaRepository { - @Query("SELECT e FROM Exam e JOIN FETCH e.examType JOIN FETCH e.groups g WHERE g IN :gr") - Set findByGroupsIn(@Param("gr") Set groups); +// @Query("SELECT e FROM Exam e JOIN FETCH e.examType JOIN FETCH e.groups g WHERE g IN :gr") +// Set findByGroupsIn(@Param("gr") Set groups); + + /** + * @param groups set of generalGroups + * @return list of exams for generalGroups + */ + List findAllByGroups_NameIn(Set groups); + + /** + * @param generalGroup superior group of subgroups e.g. 12K + * @param subgroup exam groups + * @return list of exams for subgroups + */ + @Query(""" + SELECT DISTINCT e FROM Exam e + JOIN e.groups g1 + JOIN FETCH e.groups g2 + WHERE g1.name = :general AND g2.name IN :sub + """) + List findAllBySubgroupsOfGeneralGroup(@Param("general") String generalGroup , @Param("sub") Set subgroup); + + } \ No newline at end of file From b4d55720b1aa8cb5e8a2f4b992d52a6221f4a1a7 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Wed, 27 Aug 2025 14:32:01 +0200 Subject: [PATCH 052/116] adapt ExamDtoMapper to new Exam Entity --- .../pkwmtt/examCalendar/ExamController.java | 10 +++--- .../examCalendar/mapper/ExamDtoMapper.java | 34 ++++++++++++------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamController.java b/src/main/java/org/pkwmtt/examCalendar/ExamController.java index 6edd1c0..028c08e 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamController.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamController.java @@ -71,15 +71,17 @@ public ResponseEntity getExam(@PathVariable int id) { } /** -//TODO: update javadoc - * @return 200 ok with list of exams for specific group + * when subgroups isn't null all generalGroups must be form the same year of study. e.g. 12K2, 12K1 is from 12K + * @param generalGroups set of general groups e.g. 12K2 + * @param subgroups set of subgroups of general group e.g. L04 + * @return List of ExamDto for specific groups */ @GetMapping("/by-groups") - public ResponseEntity> getExams( + public ResponseEntity> getExams( @RequestParam Set generalGroups, @RequestParam(required = false) Set subgroups ){ - return ResponseEntity.ok(examService.getExamByGroups(generalGroups, subgroups)); + return ResponseEntity.ok(ExamDtoMapper.mapToExamDto(examService.getExamByGroups(generalGroups, subgroups))); } /** diff --git a/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoMapper.java b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoMapper.java index 5041c21..33aeaee 100644 --- a/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoMapper.java +++ b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoMapper.java @@ -1,20 +1,24 @@ package org.pkwmtt.examCalendar.mapper; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.pkwmtt.examCalendar.dto.ExamDto; import org.pkwmtt.examCalendar.entity.Exam; import org.pkwmtt.examCalendar.entity.ExamType; import org.pkwmtt.examCalendar.entity.StudentGroup; import org.springframework.stereotype.Component; +import java.util.List; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; /** * maps ExamDto to Exam entity. Couldn't be utility class, because needs ExamTypeRepository to validate exam types */ @Component @RequiredArgsConstructor +@Slf4j public class ExamDtoMapper { private ExamDtoMapper examDtoMapper; @@ -50,17 +54,23 @@ public static Exam mapToExistingExam(ExamDto examDto, Set groups, .build(); } -// public static Set mapToExamDto(Set exams) { -// return exams.stream().map(ExamDtoMapper::mapToExamDto).collect(Collectors.toSet()); -// } + public static List mapToExamDto(List exams) { + return exams.stream().map(ExamDtoMapper::mapToExamDto).collect(Collectors.toList()); + } -// public static ExamDto mapToExamDto(Exam exam) { -// return ExamDto.builder() -// .title(exam.getTitle()) -// .description(exam.getDescription()) -// .date(exam.getExamDate()) -// .examType(exam.getExamType().getName()) -// .examGroups(exam.getGroups().stream().map(StudentGroup::getName).collect(Collectors.toSet())) -// .build(); -// } + public static ExamDto mapToExamDto(Exam exam) { + Set groups = exam.getGroups().stream().map(StudentGroup::getName).collect(Collectors.toSet()); + Set generalGroups = groups.stream().filter(group -> Character.isDigit(group.charAt(0))).collect(Collectors.toSet()); + Set subgroups = groups.stream().filter(group -> Character.isAlphabetic(group.charAt(0))).collect(Collectors.toSet()); + if(groups.size() != subgroups.size() + generalGroups.size()) + log.warn("Some groups of {} were not consumed in ExamDtoMapper.mapToExamDto()", groups); + return ExamDto.builder() + .title(exam.getTitle()) + .description(exam.getDescription()) + .date(exam.getExamDate()) + .examType(exam.getExamType().getName()) + .generalGroups(generalGroups) + .subgroups(subgroups) + .build(); + } } From 19e96ec8be69faea2563d3051cc7b2c3431cda4e Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Wed, 27 Aug 2025 19:48:48 +0200 Subject: [PATCH 053/116] tests for ExamDto with bug fixes --- .../org/pkwmtt/examCalendar/ExamService.java | 2 +- .../org/pkwmtt/examCalendar/dto/ExamDto.java | 1 + .../pkwmtt/examCalendar/dto/ExamDtoTest.java | 197 +++++++++++++----- 3 files changed, 150 insertions(+), 50 deletions(-) diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamService.java b/src/main/java/org/pkwmtt/examCalendar/ExamService.java index 1c963a7..f99c912 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamService.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamService.java @@ -136,7 +136,7 @@ private Set verifyAndUpdateExamGroups(ExamDto examDto) { // if there are no subgroups save exam for exercise groups or whole year e.g. // 12K2 - exercise group exam // 12K1, 12K2, 12K3 - whole year exam - if (subgroups.isEmpty()) { + if (subgroups == null || subgroups.isEmpty()) { // TODO: change groups.name in database to unique return saveNewStudentGroups(generalGroups); // exams for subgroups e.g. L04 must have only superior group to avoid ambiguity diff --git a/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java b/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java index 765f078..8b5e72a 100644 --- a/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java +++ b/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java @@ -29,6 +29,7 @@ public class ExamDto { private final String examType; @NotEmpty + @Size(min = 1) private final Set generalGroups; private final Set subgroups; diff --git a/src/test/java/org/pkwmtt/examCalendar/dto/ExamDtoTest.java b/src/test/java/org/pkwmtt/examCalendar/dto/ExamDtoTest.java index 23108d2..459d052 100644 --- a/src/test/java/org/pkwmtt/examCalendar/dto/ExamDtoTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/dto/ExamDtoTest.java @@ -24,15 +24,74 @@ public ExamDtoTest() { } @Test - void validData() { + void shouldSuccessWithCompleteData() { // given - ExamDto examDto = new ExamDto( - "Math exam", - "First exam", - LocalDateTime.now().plusDays(1), - "exam", - Set.of("12K2") - ); + ExamDto examDto = ExamDto.builder() + .title("Math exam") + .description("First exam") + .date(LocalDateTime.now().plusDays(1)) + .examType("exam") + .generalGroups(Set.of("12K2")) + .subgroups(Set.of("L04")) + .build(); +// when, then + assertTrue(validator.validate(examDto).isEmpty()); + } + + @Test + void shouldSuccessWithEmptyDescription() { +// given + ExamDto examDto = ExamDto.builder() + .title("Math exam") + .description("") + .date(LocalDateTime.now().plusDays(1)) + .examType("exam") + .generalGroups(Set.of("12K2")) + .subgroups(Set.of("L04")) + .build(); +// when, then + assertTrue(validator.validate(examDto).isEmpty()); + } + + @Test + void shouldSuccessWithBlankDescription() { +// given + ExamDto examDto = ExamDto.builder() + .title("Math exam") + .date(LocalDateTime.now().plusDays(1)) + .examType("exam") + .generalGroups(Set.of("12K2")) + .subgroups(Set.of("L04")) + .build(); +// when, then + assertTrue(validator.validate(examDto).isEmpty()); + } + + @Test + void shouldSuccessWithBlankSubgroups() { +// given + ExamDto examDto = ExamDto.builder() + .title("Math exam") + .description("First exam") + .date(LocalDateTime.now().plusDays(1)) + .examType("exam") + .generalGroups(Set.of("12K2")) + .build(); +// when, then + assertTrue(validator.validate(examDto).isEmpty()); + } + + @Test + void shouldSuccessWithEmptySubgroups() { +// given + ExamDto examDto = ExamDto.builder() + .title("Math exam") + .description("First exam") + .date(LocalDateTime.now().plusDays(1)) + .examType("exam") + .generalGroups(Set.of("12K2")) + .subgroups(Set.of("")) + .build(); // when, then assertTrue(validator.validate(examDto).isEmpty()); } @@ -40,15 +99,33 @@ void validData() { // empty Strings @Test - void emptyStringTitle() { + void shouldFailWithEmptyTitle() { + // given + ExamDto examDto = ExamDto.builder() + .title("") + .description("First exam") + .date(LocalDateTime.now().plusDays(1)) + .examType("exam") + .generalGroups(Set.of("12K2")) + .subgroups(Set.of("")) + .build(); +// when + Set> violations = validator.validate(examDto); +// then + assertFalse(validator.validate(examDto).isEmpty()); + assertTrue(violations.stream().anyMatch(v -> v.getPropertyPath().toString().equals("title"))); + } + + @Test + void shouldFailWithBlankTitle() { // given - ExamDto examDto = new ExamDto( - "", - "First exam", - LocalDateTime.now().plusDays(1), - "exam", - Set.of("12K2") - ); + ExamDto examDto = ExamDto.builder() + .description("First exam") + .date(LocalDateTime.now().plusDays(1)) + .examType("exam") + .generalGroups(Set.of("12K2")) + .subgroups(Set.of("")) + .build(); // when Set> violations = validator.validate(examDto); // then @@ -57,35 +134,54 @@ void emptyStringTitle() { } @Test - void emptyExamGroups() { + void shouldFailWithEmptyGeneralGroups() { // given - ExamDto examDto = new ExamDto( - "Math exam", - "First exam", - LocalDateTime.now().plusDays(1), - "exam", - Set.of() - ); + ExamDto examDto = ExamDto.builder() + .title("Math exam") + .description("First exam") + .date(LocalDateTime.now().plusDays(1)) + .examType("exam") + .generalGroups(Set.of()) + .subgroups(Set.of("L04")) + .build(); // when Set> violations = validator.validate(examDto); // then assertFalse(validator.validate(examDto).isEmpty()); - assertTrue(violations.stream().anyMatch(v -> v.getPropertyPath().toString().equals("examGroups"))); + assertTrue(violations.stream().anyMatch(v -> v.getPropertyPath().toString().equals("generalGroups"))); + } + + @Test + void shouldFailWithBlankGeneralGroups() { + // given + ExamDto examDto = ExamDto.builder() + .title("Math exam") + .description("First exam") + .date(LocalDateTime.now().plusDays(1)) + .examType("exam") + .subgroups(Set.of("L04")) + .build(); +// when + Set> violations = validator.validate(examDto); +// then + assertFalse(validator.validate(examDto).isEmpty()); + assertTrue(violations.stream().anyMatch(v -> v.getPropertyPath().toString().equals("generalGroups"))); } // to long Strings @Test - void toLongStringTitle() { + void ShouldFailWithTooLongTitle() { // given - ExamDto examDto = new ExamDto( -// 256 characters - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "First exam", - LocalDateTime.now().plusDays(1), - "exam", - Set.of("12K2") - ); + ExamDto examDto = ExamDto.builder() +// 256 characters + .title("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + .description("First exam") + .date(LocalDateTime.now().plusDays(1)) + .examType("exam") + .generalGroups(Set.of("12K2")) + .subgroups(Set.of("L04")) + .build(); // when Set> violations = validator.validate(examDto); // then @@ -96,14 +192,15 @@ void toLongStringTitle() { @Test void toLongDescription() { // given - ExamDto examDto = new ExamDto( - "Math exam", + ExamDto examDto = ExamDto.builder() // 256 characters - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - LocalDateTime.now().plusDays(1), - "exam", - Set.of("12K2") - ); + .title("Math exam") + .description("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + .date(LocalDateTime.now().plusDays(1)) + .examType("exam") + .generalGroups(Set.of("12K2")) + .subgroups(Set.of("L04")) + .build(); // when Set> violations = validator.validate(examDto); // then @@ -111,19 +208,21 @@ void toLongDescription() { assertTrue(violations.stream().anyMatch(v -> v.getPropertyPath().toString().equals("description"))); } - // TODO: change to too large set is Exam builder tests + // TODO: change to too large set in Exam builder tests // TODO: check if groups exists @Test void dateNotInFuture() { // given - ExamDto examDto = new ExamDto( - "Math exam", - "First exam", - LocalDateTime.now().minusHours(1), - "exam", - Set.of("12K2") - ); + ExamDto examDto = ExamDto.builder() +// 256 characters + .title("Math exam") + .description("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + .date(LocalDateTime.now().minusHours(1)) + .examType("exam") + .generalGroups(Set.of("12K2")) + .subgroups(Set.of("L04")) + .build(); // when Set> violations = validator.validate(examDto); // then From 38d2374c8dde3ccd586a7091dd07afa5aa6a2891 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Wed, 27 Aug 2025 20:21:41 +0200 Subject: [PATCH 054/116] test Exam builder + fix NullPointerException --- logs/app.log | 2 + .../org/pkwmtt/examCalendar/entity/Exam.java | 2 + .../UnsupportedCountOfArgumentsException.java | 2 +- .../pkwmtt/examCalendar/dto/ExamDtoTest.java | 3 - .../pkwmtt/examCalendar/entity/ExamTest.java | 87 +++++++++++++++++++ 5 files changed, 92 insertions(+), 4 deletions(-) create mode 100644 src/test/java/org/pkwmtt/examCalendar/entity/ExamTest.java diff --git a/logs/app.log b/logs/app.log index acf37c8..1bd1622 100644 --- a/logs/app.log +++ b/logs/app.log @@ -4933,3 +4933,5 @@ Caused by: java.lang.ClassNotFoundException: org.pkwmtt.examCalendar.ExamService at org.springframework.boot.devtools.restart.classloader.RestartClassLoader.loadClass(RestartClassLoader.java:121) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:528) ... 27 common frames omitted +2025-08-27 19:49:22 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-27 19:49:23 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java b/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java index 57c8d0e..d3f8602 100644 --- a/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java +++ b/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java @@ -49,6 +49,8 @@ public class Exam { public static class Builder { public Exam build() { // min 1 max 100 elements of set + if(groups == null) + throw new UnsupportedCountOfArgumentsException(1, 100, null); if(groups.isEmpty() || groups.size() > 100) throw new UnsupportedCountOfArgumentsException(1, 100, groups.size()); return new Exam(examId, title, description, examDate, examType, groups); diff --git a/src/main/java/org/pkwmtt/exceptions/UnsupportedCountOfArgumentsException.java b/src/main/java/org/pkwmtt/exceptions/UnsupportedCountOfArgumentsException.java index 709978a..fc3718c 100644 --- a/src/main/java/org/pkwmtt/exceptions/UnsupportedCountOfArgumentsException.java +++ b/src/main/java/org/pkwmtt/exceptions/UnsupportedCountOfArgumentsException.java @@ -1,7 +1,7 @@ package org.pkwmtt.exceptions; public class UnsupportedCountOfArgumentsException extends RuntimeException { - public UnsupportedCountOfArgumentsException(int expectedMin, int expectedMax, int provided) { + public UnsupportedCountOfArgumentsException(int expectedMin, int expectedMax, Integer provided) { super("Invalid count of arguments provided: " + provided + " expected more than: " + expectedMin + " less than: " + expectedMax); } diff --git a/src/test/java/org/pkwmtt/examCalendar/dto/ExamDtoTest.java b/src/test/java/org/pkwmtt/examCalendar/dto/ExamDtoTest.java index 459d052..a98fd0e 100644 --- a/src/test/java/org/pkwmtt/examCalendar/dto/ExamDtoTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/dto/ExamDtoTest.java @@ -208,9 +208,6 @@ void toLongDescription() { assertTrue(violations.stream().anyMatch(v -> v.getPropertyPath().toString().equals("description"))); } - // TODO: change to too large set in Exam builder tests - // TODO: check if groups exists - @Test void dateNotInFuture() { // given diff --git a/src/test/java/org/pkwmtt/examCalendar/entity/ExamTest.java b/src/test/java/org/pkwmtt/examCalendar/entity/ExamTest.java new file mode 100644 index 0000000..d813ede --- /dev/null +++ b/src/test/java/org/pkwmtt/examCalendar/entity/ExamTest.java @@ -0,0 +1,87 @@ +package org.pkwmtt.examCalendar.entity; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.pkwmtt.exceptions.UnsupportedCountOfArgumentsException; + +import java.time.LocalDateTime; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * tests of custom Exam builder + */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class ExamTest { + + ExamType examType; + Set studentGroups; + LocalDateTime date; + + @BeforeAll + void setup(){ + examType = ExamType.builder().name("project").build(); + studentGroups = Set.of(StudentGroup.builder().name("12K2").build()); + date = LocalDateTime.now().plusDays(1); + + } + + @Test + void shouldBuildExamWithCorrectData() { + Exam exam = Exam.builder() + .title("title") + .description("description") + .examDate(date) + .examType(examType) + .groups(studentGroups) + .build(); + + assertEquals("title", exam.getTitle()); + assertEquals("description", exam.getDescription()); + assertEquals(date, exam.getExamDate()); + assertEquals(examType, exam.getExamType()); + assertEquals(studentGroups, exam.getGroups()); + } + + @Test + void shouldThrowWhenNoGroupsAssigned() { + assertThrows(UnsupportedCountOfArgumentsException.class, () -> Exam.builder() + .title("title") + .description("description") + .examDate(date) + .examType(examType) +// no exam groups specified + .build()); + } + + @Test + void shouldThrowWhenZeroGroupsAssigned() { + assertThrows(UnsupportedCountOfArgumentsException.class, () -> Exam.builder() + .title("title") + .description("description") + .examDate(date) + .examType(examType) + .groups(Set.of()) + .build()); + } + + @Test + void shouldThrowWhenToManyGroupsAssigned() { + Set longStudentGroups = IntStream.range(0, 101) + .mapToObj(i -> StudentGroup.builder().name("group" + i).build()) + .collect(Collectors.toSet()); + + assertThrows(UnsupportedCountOfArgumentsException.class, () -> Exam.builder() + .title("title") + .description("description") + .examDate(date) + .examType(examType) + .groups(longStudentGroups) + .build()); + } +} \ No newline at end of file From 45aab927a953cb3aa0f31dcdcd0cd2e7351a57bd Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Wed, 27 Aug 2025 21:02:02 +0200 Subject: [PATCH 055/116] remove SQL keyword from table name --- init.sql | 118 +++++++----------- .../examCalendar/entity/StudentGroup.java | 2 +- src/test/resources/schema.sql | 86 +++++++------ 3 files changed, 85 insertions(+), 121 deletions(-) diff --git a/init.sql b/init.sql index 7d91d29..5703969 100644 --- a/init.sql +++ b/init.sql @@ -3,7 +3,7 @@ -- https://www.phpmyadmin.net/ -- -- Host: db --- Generation Time: Aug 18, 2025 at 07:00 PM +-- Generation Time: Aug 27, 2025 at 06:52 PM -- Wersja serwera: 9.3.0 -- Wersja PHP: 8.2.27 @@ -38,11 +38,6 @@ CREATE TABLE `exams` ( `exam_type_id` int NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; --- --- Tabela Truncate przed wstawieniem `exams` --- - -TRUNCATE TABLE `exams`; -- -- Zrzut danych tabeli `exams` -- @@ -68,11 +63,6 @@ CREATE TABLE `exams_groups` ( `group_id` int NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; --- --- Tabela Truncate przed wstawieniem `exams_groups` --- - -TRUNCATE TABLE `exams_groups`; -- -- Zrzut danych tabeli `exams_groups` -- @@ -105,11 +95,6 @@ CREATE TABLE `exam_type` ( `name` varchar(255) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; --- --- Tabela Truncate przed wstawieniem `exam_type` --- - -TRUNCATE TABLE `exam_type`; -- -- Zrzut danych tabeli `exam_type` -- @@ -131,11 +116,6 @@ CREATE TABLE `general_group` ( `name` varchar(255) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; --- --- Tabela Truncate przed wstawieniem `general_group` --- - -TRUNCATE TABLE `general_group`; -- -- Zrzut danych tabeli `general_group` -- @@ -148,38 +128,6 @@ INSERT INTO `general_group` (`general_group_id`, `name`) VALUES -- -------------------------------------------------------- --- --- Struktura tabeli dla tabeli `groups` --- - -DROP TABLE IF EXISTS `groups`; -CREATE TABLE `groups` ( - `group_id` int NOT NULL, - `name` varchar(255) NOT NULL -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; - --- --- Tabela Truncate przed wstawieniem `groups` --- - -TRUNCATE TABLE `groups`; --- --- Zrzut danych tabeli `groups` --- - -INSERT INTO `groups` (`group_id`, `name`) VALUES -(9, '11A1'), -(10, '11A2'), -(12, '12E1'), -(13, '12E2'), -(14, '12E3'), -(15, '13K1'), -(16, '13K2'), -(17, '13K3'), -(18, '14M1'); - --- -------------------------------------------------------- - -- -- Struktura tabeli dla tabeli `otp_codes` -- @@ -192,11 +140,6 @@ CREATE TABLE `otp_codes` ( `general_group_id` int NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; --- --- Tabela Truncate przed wstawieniem `otp_codes` --- - -TRUNCATE TABLE `otp_codes`; -- -- Zrzut danych tabeli `otp_codes` -- @@ -209,6 +152,33 @@ INSERT INTO `otp_codes` (`otp_code_id`, `code`, `expire`, `general_group_id`) VA -- -------------------------------------------------------- +-- +-- Struktura tabeli dla tabeli `student_groups` +-- + +DROP TABLE IF EXISTS `student_groups`; +CREATE TABLE `student_groups` ( + `group_id` int NOT NULL, + `name` varchar(255) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; + +-- +-- Zrzut danych tabeli `student_groups` +-- + +INSERT INTO `student_groups` (`group_id`, `name`) VALUES +(9, '11A1'), +(10, '11A2'), +(12, '12E1'), +(13, '12E2'), +(14, '12E3'), +(15, '13K1'), +(16, '13K2'), +(17, '13K3'), +(18, '14M1'); + +-- -------------------------------------------------------- + -- -- Struktura tabeli dla tabeli `users` -- @@ -222,11 +192,6 @@ CREATE TABLE `users` ( `role` enum('ADMIN','REPRESENTATIVE') NOT NULL DEFAULT 'REPRESENTATIVE' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; --- --- Tabela Truncate przed wstawieniem `users` --- - -TRUNCATE TABLE `users`; -- -- Zrzut danych tabeli `users` -- @@ -268,12 +233,6 @@ ALTER TABLE `exam_type` ALTER TABLE `general_group` ADD PRIMARY KEY (`general_group_id`); --- --- Indeksy dla tabeli `groups` --- -ALTER TABLE `groups` - ADD PRIMARY KEY (`group_id`); - -- -- Indeksy dla tabeli `otp_codes` -- @@ -281,6 +240,13 @@ ALTER TABLE `otp_codes` ADD PRIMARY KEY (`otp_code_id`), ADD KEY `general_group_id_idx` (`general_group_id`); +-- +-- Indeksy dla tabeli `student_groups` +-- +ALTER TABLE `student_groups` + ADD PRIMARY KEY (`group_id`), + ADD UNIQUE KEY `name` (`name`); + -- -- Indeksy dla tabeli `users` -- @@ -316,18 +282,18 @@ ALTER TABLE `exam_type` ALTER TABLE `general_group` MODIFY `general_group_id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=21; --- --- AUTO_INCREMENT dla tabeli `groups` --- -ALTER TABLE `groups` - MODIFY `group_id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=21; - -- -- AUTO_INCREMENT dla tabeli `otp_codes` -- ALTER TABLE `otp_codes` MODIFY `otp_code_id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=5; +-- +-- AUTO_INCREMENT dla tabeli `student_groups` +-- +ALTER TABLE `student_groups` + MODIFY `group_id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=21; + -- -- AUTO_INCREMENT dla tabeli `users` -- @@ -349,7 +315,7 @@ ALTER TABLE `exams` -- ALTER TABLE `exams_groups` ADD CONSTRAINT `exams_groups_ibfk_1` FOREIGN KEY (`exam_id`) REFERENCES `exams` (`exam_id`) ON DELETE CASCADE, - ADD CONSTRAINT `exams_groups_ibfk_2` FOREIGN KEY (`group_id`) REFERENCES `groups` (`group_id`) ON DELETE CASCADE; + ADD CONSTRAINT `exams_groups_ibfk_2` FOREIGN KEY (`group_id`) REFERENCES `student_groups` (`group_id`) ON DELETE CASCADE; -- -- Ograniczenia dla tabeli `otp_codes` diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/StudentGroup.java b/src/main/java/org/pkwmtt/examCalendar/entity/StudentGroup.java index 9cb963d..7cb3409 100644 --- a/src/main/java/org/pkwmtt/examCalendar/entity/StudentGroup.java +++ b/src/main/java/org/pkwmtt/examCalendar/entity/StudentGroup.java @@ -12,7 +12,7 @@ @RequiredArgsConstructor @AllArgsConstructor @Builder -@Table(name = "`groups`") +@Table(name = "student_groups") public class StudentGroup { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/test/resources/schema.sql b/src/test/resources/schema.sql index e868d5b..d598883 100644 --- a/src/test/resources/schema.sql +++ b/src/test/resources/schema.sql @@ -1,63 +1,61 @@ DROP TABLE IF EXISTS exams_groups; DROP TABLE IF EXISTS exams; DROP TABLE IF EXISTS exam_type; -DROP TABLE IF EXISTS groups; -DROP TABLE IF EXISTS general_group; DROP TABLE IF EXISTS otp_codes; DROP TABLE IF EXISTS users; +DROP TABLE IF EXISTS student_groups; +DROP TABLE IF EXISTS general_group; -CREATE TABLE exam_type -( - exam_type_id INT AUTO_INCREMENT PRIMARY KEY, - name VARCHAR(255) NOT NULL +CREATE TABLE exam_type ( + exam_type_id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL ); -CREATE TABLE exams -( - exam_id INT AUTO_INCREMENT PRIMARY KEY, - title VARCHAR(255) NOT NULL, - description VARCHAR(255), - exam_date TIMESTAMP NOT NULL, - exam_type_id INT NOT NULL, - CONSTRAINT fk_exam_type FOREIGN KEY (exam_type_id) REFERENCES exam_type (exam_type_id) ON DELETE CASCADE +CREATE TABLE general_group ( + general_group_id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL ); -CREATE TABLE general_group -( - general_group_id INT AUTO_INCREMENT PRIMARY KEY, - name VARCHAR(255) NOT NULL +CREATE TABLE student_groups ( + group_id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL UNIQUE ); -CREATE TABLE groups -( - group_id INT AUTO_INCREMENT PRIMARY KEY, - name VARCHAR(255) NOT NULL +CREATE TABLE exams ( + exam_id INT AUTO_INCREMENT PRIMARY KEY, + title VARCHAR(255) NOT NULL, + description VARCHAR(255), + exam_date TIMESTAMP NOT NULL, + exam_type_id INT NOT NULL, + CONSTRAINT fk_exams_exam_type FOREIGN KEY (exam_type_id) + REFERENCES exam_type (exam_type_id) ON DELETE CASCADE ); -CREATE TABLE exams_groups -( - exam_group_id INT AUTO_INCREMENT PRIMARY KEY, - exam_id INT NOT NULL, - group_id INT NOT NULL, - CONSTRAINT fk_exam FOREIGN KEY (exam_id) REFERENCES exams (exam_id) ON DELETE CASCADE, - CONSTRAINT fk_group FOREIGN KEY (group_id) REFERENCES groups (group_id) ON DELETE CASCADE +CREATE TABLE exams_groups ( + exam_group_id INT AUTO_INCREMENT PRIMARY KEY, + exam_id INT NOT NULL, + group_id INT NOT NULL, + CONSTRAINT fk_exams_groups_exam FOREIGN KEY (exam_id) + REFERENCES exams (exam_id) ON DELETE CASCADE, + CONSTRAINT fk_exams_groups_group FOREIGN KEY (group_id) + REFERENCES student_groups (group_id) ON DELETE CASCADE ); -CREATE TABLE otp_codes -( - otp_code_id INT AUTO_INCREMENT PRIMARY KEY, - code VARCHAR(255) NOT NULL, - expire TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - general_group_id INT NOT NULL, - CONSTRAINT fk_otp_group FOREIGN KEY (general_group_id) REFERENCES general_group (general_group_id) ON DELETE CASCADE +CREATE TABLE otp_codes ( + otp_code_id INT AUTO_INCREMENT PRIMARY KEY, + code VARCHAR(255) NOT NULL, + expire TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + general_group_id INT NOT NULL, + CONSTRAINT fk_otp_codes_general_group FOREIGN KEY (general_group_id) + REFERENCES general_group (general_group_id) ON DELETE CASCADE ); -CREATE TABLE users -( - user_id INT AUTO_INCREMENT PRIMARY KEY, - general_group_id INT NOT NULL, - email VARCHAR(255) NOT NULL, - is_active BOOLEAN NOT NULL DEFAULT TRUE, - role VARCHAR(50) NOT NULL DEFAULT 'REPRESENTATIVE', - CONSTRAINT fk_user_group FOREIGN KEY (general_group_id) REFERENCES general_group (general_group_id) ON DELETE CASCADE +CREATE TABLE users ( + user_id INT AUTO_INCREMENT PRIMARY KEY, + general_group_id INT NOT NULL, + email VARCHAR(255) NOT NULL, + is_active BOOLEAN NOT NULL DEFAULT TRUE, + role VARCHAR(20) NOT NULL DEFAULT 'REPRESENTATIVE', + CONSTRAINT fk_users_general_group FOREIGN KEY (general_group_id) + REFERENCES general_group (general_group_id) ON DELETE CASCADE ); \ No newline at end of file From 1d092fba6dba3fcb4a5cdd995a1aa35fca3b90fe Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Wed, 27 Aug 2025 21:34:25 +0200 Subject: [PATCH 056/116] tests of new implementation of examRepository --- logs/app.log | 6 + .../org/pkwmtt/examCalendar/ExamService.java | 9 -- .../repository/ExamRepository.java | 9 +- .../repository/ExamRepositoryTest.java | 147 +++++++++--------- src/test/resources/application.properties | 7 +- 5 files changed, 87 insertions(+), 91 deletions(-) diff --git a/logs/app.log b/logs/app.log index 1bd1622..9ce0044 100644 --- a/logs/app.log +++ b/logs/app.log @@ -4935,3 +4935,9 @@ Caused by: java.lang.ClassNotFoundException: org.pkwmtt.examCalendar.ExamService ... 27 common frames omitted 2025-08-27 19:49:22 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties 2025-08-27 19:49:23 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-27 20:42:56 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Table "groups" not found (candidates are: "GROUPS"); SQL statement: +insert into "groups" (name,group_id) values (?,default) [42103-232] +2025-08-27 20:56:12 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-27 20:56:14 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-27 20:56:52 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-27 20:56:53 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamService.java b/src/main/java/org/pkwmtt/examCalendar/ExamService.java index f99c912..a535122 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamService.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamService.java @@ -103,15 +103,6 @@ public List getExamTypes() { return examTypeRepository.findAll(); } - - private Set getGroupsFromTimetableService() throws JsonProcessingException { - List generalGroups = timetableService.getGeneralGroupList(); - Set allGroups = new HashSet<>(generalGroups); - for (String groupName : generalGroups) - allGroups.addAll(timetableService.getAvailableSubGroups(groupName)); - return allGroups; - } - /** * verify if groups exists in timetable if exist updates database. * when timetable service is unavailable verifies groups using groupsRepository diff --git a/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java index 2b29276..0eca828 100644 --- a/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java +++ b/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java @@ -13,9 +13,6 @@ public interface ExamRepository extends JpaRepository { -// @Query("SELECT e FROM Exam e JOIN FETCH e.examType JOIN FETCH e.groups g WHERE g IN :gr") -// Set findByGroupsIn(@Param("gr") Set groups); - /** * @param groups set of generalGroups * @return list of exams for generalGroups @@ -33,9 +30,5 @@ public interface ExamRepository extends JpaRepository { JOIN FETCH e.groups g2 WHERE g1.name = :general AND g2.name IN :sub """) - List findAllBySubgroupsOfGeneralGroup(@Param("general") String generalGroup , @Param("sub") Set subgroup); - - - - + List findAllBySubgroupsOfGeneralGroup(@Param("general") String generalGroup, @Param("sub") Set subgroup); } \ No newline at end of file diff --git a/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java b/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java index c9c5e04..68c6ead 100644 --- a/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java @@ -13,7 +13,6 @@ import java.time.LocalDateTime; import java.util.List; import java.util.Set; -import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -32,132 +31,140 @@ class ExamRepositoryTest { @Autowired private GroupRepository groupRepository; - private StudentGroup g12K1; - private StudentGroup g12K2; - private StudentGroup g12K3; - private StudentGroup gL04; - private StudentGroup gL05; - - private Integer exam1Id; - private Integer exam2Id; - private Integer exam3Id; - private Integer exam4Id; - @BeforeAll void setUp() { ExamType examType = ExamType.builder() .name("exam").build(); examTypeRepository.save(examType); - g12K1 = StudentGroup.builder() + StudentGroup g12A = StudentGroup.builder() + .name("12A").build(); + StudentGroup g12A1 = StudentGroup.builder() + .name("12A1").build(); + StudentGroup g12A2 = StudentGroup.builder() + .name("12A2").build(); + + StudentGroup g12K = StudentGroup.builder() + .name("12K").build(); + StudentGroup g12K1 = StudentGroup.builder() .name("12K1").build(); - g12K2 = StudentGroup.builder() + StudentGroup g12K2 = StudentGroup.builder() .name("12K2").build(); - g12K3 = StudentGroup.builder() + StudentGroup g12K3 = StudentGroup.builder() .name("12K3").build(); - gL04 = StudentGroup.builder() + StudentGroup gL04 = StudentGroup.builder() .name("L04").build(); - gL05 = StudentGroup.builder() + StudentGroup gL05 = StudentGroup.builder() .name("L05").build(); + groupRepository.save(g12A); + groupRepository.save(g12A1); + groupRepository.save(g12A2); + + groupRepository.save(g12K); groupRepository.save(g12K1); groupRepository.save(g12K2); groupRepository.save(g12K3); groupRepository.save(gL04); groupRepository.save(gL05); - Exam exam1 = Exam.builder() - .title("math exam") + Exam smallGroupExam1 = Exam.builder() + .title("small Group Exam 1") + .description("Linear Algebra") + .examDate(LocalDateTime.now().plusDays(1)) + .examType(examType) + .groups(Set.of(g12K, gL04)) + .build(); + + Exam smallGroupExam2 = Exam.builder() + .title("small Group Exam 2") .description("Linear Algebra") .examDate(LocalDateTime.now().plusDays(1)) .examType(examType) - .groups(Set.of(gL04)) + .groups(Set.of(gL04, g12K, gL05)) .build(); - Exam exam2 = Exam.builder() - .title("math exam") + Exam smallGroupExam3 = Exam.builder() + .title("small Group Exam 3") .description("Linear Algebra") .examDate(LocalDateTime.now().plusDays(1)) .examType(examType) - .groups(Set.of(g12K1, g12K2, gL04, gL05)) + .groups(Set.of(g12A, gL05)) .build(); - Exam exam3 = Exam.builder() - .title("math exam") + Exam generalGroupExam1 = Exam.builder() + .title("general Group Exam 1") + .description("Linear Algebra") + .examDate(LocalDateTime.now().plusDays(1)) + .examType(examType) + .groups(Set.of(g12K1, g12K2, g12K3)) + .build(); + + Exam generalGroupExam2 = Exam.builder() + .title("general Group Exam 2") .description("Linear Algebra") .examDate(LocalDateTime.now().plusDays(1)) .examType(examType) .groups(Set.of(g12K1)) .build(); - Exam exam4 = Exam.builder() - .title("math exam") + Exam generalGroupExam3 = Exam.builder() + .title("general Group Exam 3") .description("Linear Algebra") .examDate(LocalDateTime.now().plusDays(1)) .examType(examType) - .groups(Set.of(gL04, g12K1)) + .groups(Set.of(g12A1, g12A2)) .build(); - exam1Id = examRepository.save(exam1).getExamId(); - exam2Id = examRepository.save(exam2).getExamId(); - exam3Id = examRepository.save(exam3).getExamId(); - exam4Id = examRepository.save(exam4).getExamId(); + examRepository.save(smallGroupExam1); + examRepository.save(smallGroupExam2); + examRepository.save(smallGroupExam3); + examRepository.save(generalGroupExam1); + examRepository.save(generalGroupExam2); + examRepository.save(generalGroupExam3); } - @Test - void shouldReturnEmptySet() { -// when - Set exams = examRepository.findByGroupsIn(Set.of(g12K3)); -// then - assertTrue(exams.isEmpty()); + void shouldReturnExamsWhenNotAllSubgroupsFromRepositoryMatched() { + List exams = examRepository.findAllBySubgroupsOfGeneralGroup("12K", Set.of("L04")); + assertEquals(2, exams.size()); + List examTitles = exams.stream().map(Exam::getTitle).sorted().toList(); + assertEquals("small Group Exam 1", examTitles.get(0)); + assertEquals("small Group Exam 2", examTitles.get(1)); } @Test - void shouldReturnOneElementOutOfFour() { -// when - Set exams = examRepository.findByGroupsIn(Set.of(g12K2, gL05)); - Set examsId = exams.stream().map(Exam::getExamId).collect(Collectors.toSet()); - -// then - + void shouldReturnExamWhenNotAllSubgroupsFromArgumentsMatchedAndNotReturnExamsForWrongGeneralGroup() { + List exams = examRepository.findAllBySubgroupsOfGeneralGroup("12K", Set.of("L05")); assertEquals(1, exams.size()); - assertTrue(examsId.contains(exam2Id)); - + List examTitles = exams.stream().map(Exam::getTitle).sorted().toList(); + assertEquals("small Group Exam 2", examTitles.getFirst()); } @Test - void shouldReturnFourElementsOutOfFour() { -// when - Set exams = examRepository.findByGroupsIn(Set.of(g12K1, gL04)); - Set examsId = exams.stream().map(Exam::getExamId).collect(Collectors.toSet()); -// then - assertEquals(4, exams.size()); - assertTrue(examsId.contains(exam1Id)); - assertTrue(examsId.contains(exam2Id)); - assertTrue(examsId.contains(exam3Id)); - assertTrue(examsId.contains(exam4Id)); + void shouldReturnExamsWhenMultipleArgumentsMatch() { + List exams = examRepository.findAllBySubgroupsOfGeneralGroup("12K", Set.of("L04", "L05")); + assertEquals(2, exams.size()); + List examTitles = exams.stream().map(Exam::getTitle).sorted().toList(); + assertEquals("small Group Exam 1", examTitles.get(0)); + assertEquals("small Group Exam 2", examTitles.get(1)); } @Test - void ShouldReturnEmptySetWhenNoGroups() { -// when - Set exams = examRepository.findByGroupsIn(Set.of()); -// then + void ShouldReturnEmptyListWhenSubgroupsSetIsEmpty() { + List exams = examRepository.findAllBySubgroupsOfGeneralGroup("12K", Set.of()); assertTrue(exams.isEmpty()); } @Test - void ShouldReturnEmptySetWhenGroupNotExistsInDatabase() { -// when - Set exams = examRepository.findByGroupsIn(Set.of( - StudentGroup.builder() - .groupId(Integer.MAX_VALUE) - .name("NotValid").build()) - ); -// then + void shouldReturnEmptyListWhenGeneralGroupIdentifierHasInvalidFormat() { + List exams = examRepository.findAllBySubgroupsOfGeneralGroup("12K2", Set.of("L04")); assertTrue(exams.isEmpty()); } - + @Test + void shouldReturnEmptyListWhenGeneralGroupIdentifierDontMatch() { + List exams = examRepository.findAllBySubgroupsOfGeneralGroup("12B", Set.of("L04", "L05")); + assertTrue(exams.isEmpty()); + } } \ No newline at end of file diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index bb5b179..1bdfc2a 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -1,7 +1,6 @@ -#casue issue by some reason -#spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false -#spring.datasource.username=sa -#spring.datasource.password=sa +spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false +spring.datasource.username=sa +spring.datasource.password= spring.jpa.show-sql=true spring.jpa.database-platform=org.hibernate.dialect.H2Dialect From e8b503446fe4ebbcde2bc9c4ddf13b9b46d78110 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Piotrkowski?= Date: Fri, 29 Aug 2025 07:04:50 +0200 Subject: [PATCH 057/116] feat(auth): update authentication args and improve UserDTO initialization - Refactored user authentication function arguments for clarity - Initialized UserDTO using Optional to handle nullable values - Removed redundant null-checking logic and unnecessary code --- .../org/pkwmtt/security/auth/AuthController.java | 2 +- .../java/org/pkwmtt/security/auth/AuthService.java | 3 +-- .../org/pkwmtt/security/auth/AuthServiceImpl.java | 6 +++--- .../auth/provider/OTPAuthenticationProvider.java | 14 +++----------- .../org/pkwmtt/security/token/dto/UserDTO.java | 7 ++++++- 5 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/pkwmtt/security/auth/AuthController.java b/src/main/java/org/pkwmtt/security/auth/AuthController.java index 8855825..f86dd7a 100644 --- a/src/main/java/org/pkwmtt/security/auth/AuthController.java +++ b/src/main/java/org/pkwmtt/security/auth/AuthController.java @@ -18,7 +18,7 @@ public class AuthController { @PostMapping("/authenticate") public String authenticate(@RequestBody UserRequestDTO requestUser) { - UserDTO user = authService.authenticateUser(requestUser); + UserDTO user = authService.authenticateUser(requestUser.getEmail()); return jwtServiceImpl.generateToken(user); } } diff --git a/src/main/java/org/pkwmtt/security/auth/AuthService.java b/src/main/java/org/pkwmtt/security/auth/AuthService.java index 7b60b77..3fc3cde 100644 --- a/src/main/java/org/pkwmtt/security/auth/AuthService.java +++ b/src/main/java/org/pkwmtt/security/auth/AuthService.java @@ -1,8 +1,7 @@ package org.pkwmtt.security.auth; -import org.pkwmtt.security.auth.dto.UserRequestDTO; import org.pkwmtt.security.token.dto.UserDTO; public interface AuthService { - UserDTO authenticateUser(UserRequestDTO requestUser); + UserDTO authenticateUser(String email); } diff --git a/src/main/java/org/pkwmtt/security/auth/AuthServiceImpl.java b/src/main/java/org/pkwmtt/security/auth/AuthServiceImpl.java index 382c83f..a878b52 100644 --- a/src/main/java/org/pkwmtt/security/auth/AuthServiceImpl.java +++ b/src/main/java/org/pkwmtt/security/auth/AuthServiceImpl.java @@ -15,10 +15,10 @@ public class AuthServiceImpl implements AuthService { private final AuthenticationManager authenticationManager; @Override - public UserDTO authenticateUser(UserRequestDTO requestUser) { + public UserDTO authenticateUser(String email) { Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken( - requestUser.getEmail(), - requestUser.getOtp_code() + email, + null )); if (!authentication.isAuthenticated()) { diff --git a/src/main/java/org/pkwmtt/security/auth/provider/OTPAuthenticationProvider.java b/src/main/java/org/pkwmtt/security/auth/provider/OTPAuthenticationProvider.java index d2b6894..4baf894 100644 --- a/src/main/java/org/pkwmtt/security/auth/provider/OTPAuthenticationProvider.java +++ b/src/main/java/org/pkwmtt/security/auth/provider/OTPAuthenticationProvider.java @@ -22,7 +22,6 @@ public class OTPAuthenticationProvider implements AuthenticationProvider { @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { String email = authentication.getName(); - String otpCode = authentication.getCredentials().toString(); // Fetch user from DB User user = userRepository.findByEmail(email) @@ -33,22 +32,15 @@ public Authentication authenticate(Authentication authentication) throws Authent .map(role -> new SimpleGrantedAuthority("ROLE_" + role.name())) .toList(); - // Validate critical user fields before OTP check + // Validate critical user fields if(!isValidForAuthentication(user)){ throw new BadCredentialsException( "Invalid User Credentials. Please contact the administrator." ); } - // TODO: integrate with real OTP service - boolean OTP_SERVICE_AUTH_RESULT = true; - - if(OTP_SERVICE_AUTH_RESULT){ - UserDTO userMapped = new UserDTO(user); - return new UsernamePasswordAuthenticationToken(userMapped, otpCode, authorities); - } else { - throw new BadCredentialsException("Invalid OTP"); - } + UserDTO userMapped = new UserDTO(user); + return new UsernamePasswordAuthenticationToken(userMapped, null, authorities); } @Override diff --git a/src/main/java/org/pkwmtt/security/token/dto/UserDTO.java b/src/main/java/org/pkwmtt/security/token/dto/UserDTO.java index 8736f61..b193edd 100644 --- a/src/main/java/org/pkwmtt/security/token/dto/UserDTO.java +++ b/src/main/java/org/pkwmtt/security/token/dto/UserDTO.java @@ -1,9 +1,12 @@ package org.pkwmtt.security.token.dto; import lombok.Data; +import org.pkwmtt.entity.GeneralGroup; import org.pkwmtt.entity.User; import org.pkwmtt.enums.Role; +import java.util.Optional; + @Data public class UserDTO { private String email; @@ -12,7 +15,9 @@ public class UserDTO { public UserDTO(User user){ this.email = user.getEmail(); - this.group = user.getGeneralGroup() != null ? user.getGeneralGroup().getName() : null; this.role = user.getRole(); + this.group = Optional.ofNullable(user.getGeneralGroup()) + .map(GeneralGroup::getName) + .orElse(null); } } From 4067cb9ae922fd0aaee3d64507c349fc90bdb44a Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Thu, 28 Aug 2025 12:21:31 +0200 Subject: [PATCH 058/116] test addExam with correct data --- .../org/pkwmtt/examCalendar/ExamService.java | 12 +- .../pkwmtt/examCalendar/ExamServiceTest.java | 132 ++++++++++-------- 2 files changed, 83 insertions(+), 61 deletions(-) diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamService.java b/src/main/java/org/pkwmtt/examCalendar/ExamService.java index a535122..d7101d1 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamService.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamService.java @@ -110,25 +110,25 @@ public List getExamTypes() { * @param examDto containing groups for verification */ private Set verifyAndUpdateExamGroups(ExamDto examDto) { - Set generalGroupsGromRepository; + Set generalGroupsFromRepository; Set generalGroups = examDto.getGeneralGroups(); +// TODO: NullPointerException ??? Set subgroups = examDto.getSubgroups(); // if timetable service is unavailable verify general groups using GroupRepository try { - generalGroupsGromRepository = new HashSet<>(timetableService.getGeneralGroupList()); + generalGroupsFromRepository = new HashSet<>(timetableService.getGeneralGroupList()); } catch (WebPageContentNotAvailableException e) { - generalGroupsGromRepository = verifyUsingRepository(generalGroups); + generalGroupsFromRepository = verifyUsingRepository(generalGroups); } // verify generalGroups using timetable service - if (!generalGroupsGromRepository.containsAll(generalGroups)) { - generalGroups.removeAll(generalGroupsGromRepository); + if (!generalGroupsFromRepository.containsAll(generalGroups)) { + generalGroups.removeAll(generalGroupsFromRepository); throw new InvalidGroupIdentifierException(generalGroups); } // if there are no subgroups save exam for exercise groups or whole year e.g. // 12K2 - exercise group exam // 12K1, 12K2, 12K3 - whole year exam if (subgroups == null || subgroups.isEmpty()) { -// TODO: change groups.name in database to unique return saveNewStudentGroups(generalGroups); // exams for subgroups e.g. L04 must have only superior group to avoid ambiguity } else if (generalGroups.size() == 1) { diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java index ed13578..1df2026 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java @@ -43,74 +43,96 @@ class ExamServiceTest { @InjectMocks private ExamService examService; + /** + * test specification + * generalGroup - 1 item + * subgroup - blank + * timetable service - available + * provided groups - match groups from timetable service + * groupRepository - don't contain provided groups + */ @Test - void addExamWithCorrectData() throws JsonProcessingException { + void addExamWithCorrectData() { // given - String examTypeName = "exam"; - ExamType examType = mock(ExamType.class); - ExamDto examDto = mock(ExamDto.class); - -// List generalGroups = mockGetGeneralGroupList(); -// List subGroups = mockGetSubGroupsList(); - when(groupRepository.findAllByNameIn(anySet())).thenReturn(mock(Set.class)); - when(examTypeRepository.findByName(any(String.class))).thenReturn(Optional.of(mock(ExamType.class))); -// mockExamRepositoryFindByName(); - -// Set studentGroups = getExampleStudentGroupsSet(); - Set studentGroups = mock(Set.class); - -// when(examTypeRepository.findByName(examTypeName)).thenReturn(Optional.of(ExamType.builder().examTypeId(1).name(examTypeName).build())); - - try (MockedStatic mockedMapper = mockStatic(ExamDtoMapper.class)) { - mockedMapper.when(() -> ExamDtoMapper.mapToNewExam(any(ExamDto.class), any(Set.class), any(ExamType.class))).thenReturn(mock(Exam.class)); - - mockedMapper.verify(() -> ExamDtoMapper.mapToNewExam(any(ExamDto.class), any(Set.class), any(ExamType.class)), times(1)); - } - -// mockExamRepositorySaveExam(studentGroups); - when(examRepository.save(any(Exam.class))).thenReturn(mock(Exam.class)); + LocalDateTime date = LocalDateTime.now().plusDays(1); + ExamDto examDto = ExamDto.builder() + .title("title") + .description("description") + .date(date) + .examType("exam") + .generalGroups(new HashSet<>(Set.of("12K2"))) + .build(); + + ExamType examType = ExamType.builder() + .examTypeId(1) + .name("exam") + .build(); + + Exam exam = Exam.builder() + .examId(1) + .groups(Set.of(StudentGroup.builder().groupId(1).name("12K2").build())) + .build(); + + StudentGroup groupWithId = StudentGroup.builder() + .groupId(1) + .name("12K2") + .build(); + + List groupListWithId = new ArrayList<>(); + groupListWithId.add(groupWithId); + + when(examTypeRepository.findByName(examDto.getExamType())).thenReturn(Optional.of(examType)); + when(timetableService.getGeneralGroupList()).thenReturn(new ArrayList<>(List.of("12K1", "12K2", "12K3"))); + when(groupRepository.findAllByNameIn(Set.of("12K2"))).thenReturn(new HashSet<>(Set.of())); + when(groupRepository.saveAll(anyList())).thenReturn(groupListWithId); + when(examRepository.save(any(Exam.class))).thenReturn(exam); // when -// int returnedExamID = examService.addExam(examDto); - examService.addExam(examDto); + int savedId = examService.addExam(examDto); // then -// verify(timetableService, times(1)).getGeneralGroupList(); -// verify(timetableService, times(1)).getAvailableSubGroups("12K1"); -// verify(timetableService, times(1)).getAvailableSubGroups("12K2"); -// verify(timetableService, times(1)).getAvailableSubGroups("12K3"); - + verify(examTypeRepository, times(1)).findByName(examDto.getExamType()); verify(timetableService, times(1)).getGeneralGroupList(); - verify(timetableService, times(1)).getAvailableSubGroups(any(String.class)); - verify(timetableService, times(1)).getAvailableSubGroups(any(String.class)); - verify(timetableService, times(1)).getAvailableSubGroups(any(String.class)); + verify(groupRepository, times(1)).findAllByNameIn(Set.of("12K2")); + + ArgumentCaptor> groupCaptor = ArgumentCaptor.forClass(List.class); + verify(groupRepository, times(1)).saveAll(groupCaptor.capture()); + assertEquals("12K2", groupCaptor.getValue().getFirst().getName()); + + ArgumentCaptor examCaptor = ArgumentCaptor.forClass(Exam.class); + verify(examRepository, times(1)).save(examCaptor.capture()); + Exam savedExam = examCaptor.getValue(); + assertEquals("title", savedExam.getTitle()); + assertEquals("description", savedExam.getDescription()); + assertEquals(date, savedExam.getExamDate()); + assertEquals("exam", savedExam.getExamType().getName()); + assertEquals(1, savedExam.getGroups().size()); + assertEquals("12K2", savedExam.getGroups().iterator().next().getName()); + assertEquals(1, savedId); + } - verify(groupRepository, times(1)).saveAll(anySet()); + @Test + void addExamForMultipleGeneralGroupsWithEmptySubgroups() { + ExamDto examDto = ExamDto.builder() + .title("title") + .description("description") + .date(LocalDateTime.now().plusDays(1)) + .examType("exam") + .generalGroups(Set.of("12K2")) + .subgroups(Set.of()) + .build(); + } -// ArgumentCaptor> studentGroupCaptor = ArgumentCaptor.forClass(Set.class); -// verify(groupRepository, times(1)).saveAll(studentGroupCaptor.capture()); -// -// Set expectedGroups = studentGroups.stream() -// .map(StudentGroup::getName) -// .collect(Collectors.toSet()); -// -// Set providedGroups = studentGroupCaptor.getValue().stream() -// .map(StudentGroup::getName) -// .collect(Collectors.toSet()); -// -// assertEquals(expectedGroups, providedGroups); + @Test + void addExamForSingleSubgroup() { -// verify(examTypeRepository, times(1)).findByName(examTypeName); - verify(examTypeRepository, times(1)).findByName(any(String.class)); + } -// ArgumentCaptor examCaptor = ArgumentCaptor.forClass(Exam.class); -// verify(examRepository, times(1)).save(examCaptor.capture()); - verify(examRepository, times(1)).save(any(Exam.class)); + @Test + void addExamForMultipleSubgroups() { -// assertNull(examCaptor.getValue().getExamId()); -// assertEquals(1, returnedExamID); } @Test - void addExamWithWrongExamType() throws JsonProcessingException { + void addExamWithWrongExamType() { } /************************************************************************************/ From f45093ec57f20faf62e4eb17d30cac06a743fd1f Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Fri, 29 Aug 2025 14:24:06 +0200 Subject: [PATCH 059/116] define testService specification --- .../pkwmtt/examCalendar/ExamServiceTest.java | 158 ++++++++++++++++++ 1 file changed, 158 insertions(+) diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java index 1df2026..0bc6911 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java @@ -43,6 +43,7 @@ class ExamServiceTest { @InjectMocks private ExamService examService; + // /** * test specification * generalGroup - 1 item @@ -109,6 +110,14 @@ void addExamWithCorrectData() { assertEquals(1, savedId); } + /** + * test specification + * generalGroup - 3 item + * subgroup - 0 items + * timetable service - available + * provided groups - match groups from timetable service + * groupRepository - don't contain provided groups + */ @Test void addExamForMultipleGeneralGroupsWithEmptySubgroups() { ExamDto examDto = ExamDto.builder() @@ -121,6 +130,155 @@ void addExamForMultipleGeneralGroupsWithEmptySubgroups() { .build(); } + + + /** + * test specification + * generalGroup - 3 item + * subgroup - 2 items + * timetable service - available + * provided groups - match groups from timetable service + * groupRepository - don't contain provided groups + */ + + /** + * test specification + * generalGroup - 1 item + * subgroup - 1 items + * timetable service - available + * provided groups - match groups from timetable service + * groupRepository - don't contain provided groups + */ + + /** + * test specification + * generalGroup - 1 item + * subgroup - 4 items + * timetable service - available + * provided groups - match groups from timetable service + * groupRepository - don't contain provided groups + */ + + /** + * test specification + * generalGroup - 0 item + * subgroup - 1 items + * timetable service - available + * provided groups - match groups from timetable service + * groupRepository - don't contain provided groups + */ + + /** + * test specification + * generalGroup - blank + * subgroup - 1 items + * timetable service - available + * provided groups - match groups from timetable service + * groupRepository - don't contain provided groups + */ + + // + + // + /** + * test specification + * generalGroup - 2 item + * subgroup - 0 items + * timetable service - available + * provided groups - don't match groups from timetable service + * groupRepository - don't contain provided groups + */ + + /** + * test specification + * generalGroup - 1 item + * subgroup - 3 items + * timetable service - available + * provided groups - partially match groups from timetable service + * groupRepository - don't contain provided groups + */ + // + + // + /** + * test specification + * generalGroup - 1 item + * subgroup - blank + * timetable service - available + * provided groups - match groups from timetable service + * groupRepository - contain provided groups + */ + + /** + * test specification + * generalGroup - 1 item + * subgroup - 4 items + * timetable service - available + * provided groups - match groups from timetable service + * groupRepository - partially contain provided groups + */ + + // + + // + /** + * test specification + * generalGroup - 1 item + * subgroup - blank + * timetable service - unavailable + * provided groups - match groups from timetable service + * groupRepository - don't contain provided groups + */ + + /** + * test specification + * generalGroup - 1 item + * subgroup - 3 items + * timetable service - unavailable + * provided groups - match groups from timetable service + * groupRepository - don't contain provided groups + */ + + /** + * test specification + * generalGroup - 1 item + * subgroup - 4 items + * timetable service - unavailable + * provided groups - match groups from timetable service + * groupRepository - partially contain provided groups + */ + // + + // + /** + * test specification + * generalGroup - 2 item + * subgroup - 0 item + * timetable service - unavailable + * provided groups - match groups from timetable service + * groupRepository - contain provided groups + */ + + /** + * test specification + * generalGroup - 1 item + * subgroup - 4 items + * timetable service - unavailable + * provided groups - match groups from timetable service + * groupRepository - contain provided groups + */ + + /** + * test specification + * generalGroup - 3 item + * subgroup - 1 items + * timetable service - unavailable + * provided groups - match groups from timetable service + * groupRepository - contain provided groups + */ + // + + @Test void addExamForSingleSubgroup() { From d2953c1c0d5230faca061f6d4e35bd0a6aa33680 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Fri, 29 Aug 2025 14:44:50 +0200 Subject: [PATCH 060/116] refactor ExamServiceTest --- .../examCalendar/entity/StudentGroup.java | 3 - .../pkwmtt/examCalendar/ExamServiceTest.java | 84 +++++++++++-------- 2 files changed, 48 insertions(+), 39 deletions(-) diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/StudentGroup.java b/src/main/java/org/pkwmtt/examCalendar/entity/StudentGroup.java index 7cb3409..3332ef2 100644 --- a/src/main/java/org/pkwmtt/examCalendar/entity/StudentGroup.java +++ b/src/main/java/org/pkwmtt/examCalendar/entity/StudentGroup.java @@ -3,9 +3,6 @@ import jakarta.persistence.*; import lombok.*; -import java.util.HashSet; -import java.util.Set; - @Entity @Getter @Setter diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java index 0bc6911..1111d56 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java @@ -1,25 +1,24 @@ package org.pkwmtt.examCalendar; -import com.fasterxml.jackson.core.JsonProcessingException; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.*; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.pkwmtt.examCalendar.dto.ExamDto; import org.pkwmtt.examCalendar.entity.Exam; import org.pkwmtt.examCalendar.entity.ExamType; import org.pkwmtt.examCalendar.entity.StudentGroup; -import org.pkwmtt.examCalendar.mapper.ExamDtoMapper; import org.pkwmtt.examCalendar.repository.ExamRepository; import org.pkwmtt.examCalendar.repository.ExamTypeRepository; import org.pkwmtt.examCalendar.repository.GroupRepository; import org.pkwmtt.timetable.TimetableService; -import java.lang.reflect.Field; import java.time.LocalDateTime; import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; -import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; @@ -44,6 +43,7 @@ class ExamServiceTest { private ExamService examService; // + /** * test specification * generalGroup - 1 item @@ -55,44 +55,25 @@ class ExamServiceTest { @Test void addExamWithCorrectData() { // given - LocalDateTime date = LocalDateTime.now().plusDays(1); - ExamDto examDto = ExamDto.builder() - .title("title") - .description("description") - .date(date) - .examType("exam") - .generalGroups(new HashSet<>(Set.of("12K2"))) - .build(); + Set g12K2 = Set.of("12K2"); - ExamType examType = ExamType.builder() - .examTypeId(1) - .name("exam") - .build(); - - Exam exam = Exam.builder() - .examId(1) - .groups(Set.of(StudentGroup.builder().groupId(1).name("12K2").build())) - .build(); - - StudentGroup groupWithId = StudentGroup.builder() - .groupId(1) - .name("12K2") - .build(); - - List groupListWithId = new ArrayList<>(); - groupListWithId.add(groupWithId); + LocalDateTime date = LocalDateTime.now().plusDays(1); + ExamDto examDto = buildExampleExamDto(g12K2, date); + ExamType examType = buildExampleExamType(); + List studentGroups = buildExampleStudentGroupList(g12K2); + Exam exam = buildExamWithIdAndGroups(1, studentGroups); when(examTypeRepository.findByName(examDto.getExamType())).thenReturn(Optional.of(examType)); when(timetableService.getGeneralGroupList()).thenReturn(new ArrayList<>(List.of("12K1", "12K2", "12K3"))); - when(groupRepository.findAllByNameIn(Set.of("12K2"))).thenReturn(new HashSet<>(Set.of())); - when(groupRepository.saveAll(anyList())).thenReturn(groupListWithId); + when(groupRepository.findAllByNameIn(g12K2)).thenReturn(new HashSet<>(Set.of())); + when(groupRepository.saveAll(anyList())).thenReturn(studentGroups); when(examRepository.save(any(Exam.class))).thenReturn(exam); // when int savedId = examService.addExam(examDto); // then verify(examTypeRepository, times(1)).findByName(examDto.getExamType()); verify(timetableService, times(1)).getGeneralGroupList(); - verify(groupRepository, times(1)).findAllByNameIn(Set.of("12K2")); + verify(groupRepository, times(1)).findAllByNameIn(g12K2); ArgumentCaptor> groupCaptor = ArgumentCaptor.forClass(List.class); verify(groupRepository, times(1)).saveAll(groupCaptor.capture()); @@ -110,6 +91,7 @@ void addExamWithCorrectData() { assertEquals(1, savedId); } + /** * test specification * generalGroup - 3 item @@ -131,7 +113,6 @@ void addExamForMultipleGeneralGroupsWithEmptySubgroups() { } - /** * test specification * generalGroup - 3 item @@ -277,8 +258,6 @@ void addExamForMultipleGeneralGroupsWithEmptySubgroups() { * groupRepository - contain provided groups */ // - - @Test void addExamForSingleSubgroup() { @@ -611,5 +590,38 @@ void shouldThrowExceptionWhenExamNotFound() { // ); // } + private static List buildExampleStudentGroupList(Set groupNames) { + AtomicInteger id = new AtomicInteger(); + return groupNames.stream() + .map(g -> StudentGroup.builder() + .groupId(id.getAndIncrement()) + .name(g) + .build() + ).collect(Collectors.toList()); + } + + private static Exam buildExamWithIdAndGroups(int id, List groups) { + return Exam.builder() + .examId(id) + .groups(new HashSet<>(groups)) + .build(); + } + + private static ExamType buildExampleExamType() { + return ExamType.builder() + .examTypeId(1) + .name("exam") + .build(); + } + + private static ExamDto buildExampleExamDto(Set groups, LocalDateTime date) { + return ExamDto.builder() + .title("title") + .description("description") + .date(date) + .examType("exam") + .generalGroups(new HashSet<>(groups)) + .build(); + } } \ No newline at end of file From e5e40694916103a8be9247c760229b84bf092a02 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Fri, 29 Aug 2025 14:48:28 +0200 Subject: [PATCH 061/116] refactor ExamServiceTest --- .../org/pkwmtt/examCalendar/ExamServiceTest.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java index 1111d56..43a7f9b 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java @@ -58,7 +58,13 @@ void addExamWithCorrectData() { Set g12K2 = Set.of("12K2"); LocalDateTime date = LocalDateTime.now().plusDays(1); - ExamDto examDto = buildExampleExamDto(g12K2, date); + ExamDto examDto = ExamDto.builder() + .title("title") + .description("description") + .date(date) + .examType("exam") + .generalGroups(new HashSet<>(g12K2)) + .build(); ExamType examType = buildExampleExamType(); List studentGroups = buildExampleStudentGroupList(g12K2); Exam exam = buildExamWithIdAndGroups(1, studentGroups); @@ -589,7 +595,6 @@ void shouldThrowExceptionWhenExamNotFound() { // .build() // ); // } - private static List buildExampleStudentGroupList(Set groupNames) { AtomicInteger id = new AtomicInteger(); return groupNames.stream() @@ -614,13 +619,14 @@ private static ExamType buildExampleExamType() { .build(); } - private static ExamDto buildExampleExamDto(Set groups, LocalDateTime date) { + private static ExamDto buildExampleExamDto(Set generalGroups, Set subgroups, LocalDateTime date) { return ExamDto.builder() .title("title") .description("description") .date(date) .examType("exam") - .generalGroups(new HashSet<>(groups)) + .generalGroups(new HashSet<>(generalGroups)) + .subgroups(new HashSet<>(subgroups)) .build(); } From 6b4f2eac900b8c694733b5718175ba50325e3f7f Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Fri, 29 Aug 2025 18:45:07 +0200 Subject: [PATCH 062/116] tests of ExamService with correct data --- .../org/pkwmtt/examCalendar/ExamService.java | 1 - .../pkwmtt/examCalendar/ExamServiceTest.java | 96 +++++++++++++++---- 2 files changed, 79 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamService.java b/src/main/java/org/pkwmtt/examCalendar/ExamService.java index d7101d1..053e5bf 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamService.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamService.java @@ -112,7 +112,6 @@ public List getExamTypes() { private Set verifyAndUpdateExamGroups(ExamDto examDto) { Set generalGroupsFromRepository; Set generalGroups = examDto.getGeneralGroups(); -// TODO: NullPointerException ??? Set subgroups = examDto.getSubgroups(); // if timetable service is unavailable verify general groups using GroupRepository try { diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java index 43a7f9b..5e04618 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java @@ -1,5 +1,7 @@ package org.pkwmtt.examCalendar; +import com.fasterxml.jackson.core.JsonProcessingException; +import lombok.SneakyThrows; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; @@ -44,6 +46,8 @@ class ExamServiceTest { // +// TODO: check for general groups from different years of study + /** * test specification * generalGroup - 1 item @@ -53,7 +57,7 @@ class ExamServiceTest { * groupRepository - don't contain provided groups */ @Test - void addExamWithCorrectData() { + void testBlankSubgroupAndMoreArgumentsThatRequiredReturnedByService() { // given Set g12K2 = Set.of("12K2"); @@ -70,6 +74,7 @@ void addExamWithCorrectData() { Exam exam = buildExamWithIdAndGroups(1, studentGroups); when(examTypeRepository.findByName(examDto.getExamType())).thenReturn(Optional.of(examType)); +// more groups than in set when(timetableService.getGeneralGroupList()).thenReturn(new ArrayList<>(List.of("12K1", "12K2", "12K3"))); when(groupRepository.findAllByNameIn(g12K2)).thenReturn(new HashSet<>(Set.of())); when(groupRepository.saveAll(anyList())).thenReturn(studentGroups); @@ -88,16 +93,9 @@ void addExamWithCorrectData() { ArgumentCaptor examCaptor = ArgumentCaptor.forClass(Exam.class); verify(examRepository, times(1)).save(examCaptor.capture()); Exam savedExam = examCaptor.getValue(); - assertEquals("title", savedExam.getTitle()); - assertEquals("description", savedExam.getDescription()); - assertEquals(date, savedExam.getExamDate()); - assertEquals("exam", savedExam.getExamType().getName()); - assertEquals(1, savedExam.getGroups().size()); - assertEquals("12K2", savedExam.getGroups().iterator().next().getName()); - assertEquals(1, savedId); + assertExam(savedExam, date, savedId, g12K2); } - /** * test specification * generalGroup - 3 item @@ -108,14 +106,10 @@ void addExamWithCorrectData() { */ @Test void addExamForMultipleGeneralGroupsWithEmptySubgroups() { - ExamDto examDto = ExamDto.builder() - .title("title") - .description("description") - .date(LocalDateTime.now().plusDays(1)) - .examType("exam") - .generalGroups(Set.of("12K2")) - .subgroups(Set.of()) - .build(); + // given + Set generalGroups = Set.of("12K1", "12K2", "12K3"); + Set subgroups = Set.of(); + testExamService(generalGroups, subgroups); } @@ -136,6 +130,14 @@ void addExamForMultipleGeneralGroupsWithEmptySubgroups() { * provided groups - match groups from timetable service * groupRepository - don't contain provided groups */ + @Test + void addExamForSingleGeneralGroupAndSingleSubgroup() throws JsonProcessingException { + // given + Set generalGroups = Set.of("12K"); + Set subgroups = Set.of("K04"); + when(timetableService.getAvailableSubGroups(any(String.class))).thenReturn(new ArrayList<>(List.of("K03", "K04", "L04"))); + testExamService(generalGroups, subgroups); + } /** * test specification @@ -145,6 +147,15 @@ void addExamForMultipleGeneralGroupsWithEmptySubgroups() { * provided groups - match groups from timetable service * groupRepository - don't contain provided groups */ + @Test + void addExamForSingleGeneralGroupAndMultipleSubgroup() throws JsonProcessingException { + // given + Set generalGroups = Set.of("12K"); + Set subgroups = Set.of("K04", "P04", "L04", "L03"); + when(timetableService.getAvailableSubGroups(any(String.class))).thenReturn(new ArrayList<>(List.of("K03", "K04", "P04", "L04", "L03"))); + testExamService(generalGroups, subgroups); + } + /** * test specification @@ -154,6 +165,13 @@ void addExamForMultipleGeneralGroupsWithEmptySubgroups() { * provided groups - match groups from timetable service * groupRepository - don't contain provided groups */ +// @Test +// void addExamForEmptyGeneralGroup() { +// // given +// Set generalGroups = Set.of(); +// Set subgroups = Set.of("P04"); +// testExamService(generalGroups, subgroups); +// } /** * test specification @@ -630,4 +648,48 @@ private static ExamDto buildExampleExamDto(Set generalGroups, Set groups) { + assertEquals("title", savedExam.getTitle()); + assertEquals("description", savedExam.getDescription()); + assertEquals(date, savedExam.getExamDate()); + assertEquals("exam", savedExam.getExamType().getName()); + assertEquals(groups.size(), savedExam.getGroups().size()); + assertEquals(groups, savedExam.getGroups().stream().map(StudentGroup::getName).collect(Collectors.toSet())); + assertEquals(1, savedId); + } + + private void testExamService(Set generalGroups, Set subgroups) { + Set combinedGroups = new HashSet<>(generalGroups); + combinedGroups.addAll(subgroups); + + LocalDateTime date = LocalDateTime.now().plusDays(1); + ExamDto examDto = buildExampleExamDto(generalGroups, subgroups, date); + ExamType examType = buildExampleExamType(); + List studentGroups = buildExampleStudentGroupList(combinedGroups); + Exam exam = buildExamWithIdAndGroups(1, studentGroups); + + when(examTypeRepository.findByName(examDto.getExamType())).thenReturn(Optional.of(examType)); + when(timetableService.getGeneralGroupList()).thenReturn(new ArrayList<>(generalGroups)); +// when(timetableService.getGeneralGroupList()).thenReturn(new ArrayList<>(generalGroups)); + + when(groupRepository.findAllByNameIn(combinedGroups)).thenReturn(new HashSet<>(Set.of())); + when(groupRepository.saveAll(anyList())).thenReturn(studentGroups); + when(examRepository.save(any(Exam.class))).thenReturn(exam); +// when + int savedId = examService.addExam(examDto); +// then + verify(examTypeRepository, times(1)).findByName(examDto.getExamType()); + verify(timetableService, times(1)).getGeneralGroupList(); + verify(groupRepository, times(1)).findAllByNameIn(combinedGroups); + + ArgumentCaptor> groupCaptor = ArgumentCaptor.forClass(List.class); + verify(groupRepository, times(1)).saveAll(groupCaptor.capture()); + Set capturedGroups = groupCaptor.getValue().stream().map(StudentGroup::getName).collect(Collectors.toSet()); + assertEquals(combinedGroups, capturedGroups); + + ArgumentCaptor examCaptor = ArgumentCaptor.forClass(Exam.class); + verify(examRepository, times(1)).save(examCaptor.capture()); + Exam savedExam = examCaptor.getValue(); + assertExam(savedExam, date, savedId, combinedGroups); + } } \ No newline at end of file From 194fa51917ec135f7c141a93999964393514ed3f Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Fri, 29 Aug 2025 19:38:48 +0200 Subject: [PATCH 063/116] add more detailed exception messages for invalid generalGroup identifiers --- .../org/pkwmtt/examCalendar/ExamService.java | 4 +- .../pkwmtt/examCalendar/ExamServiceTest.java | 93 +++++++++++++------ 2 files changed, 67 insertions(+), 30 deletions(-) diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamService.java b/src/main/java/org/pkwmtt/examCalendar/ExamService.java index 053e5bf..eb15c14 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamService.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamService.java @@ -156,8 +156,10 @@ private Set verifyAndUpdateExamGroups(ExamDto examDto) { } // only one general group could be assigned to subgroups (when there are more than 1 general group and // more than 0 subgroups) + else if(generalGroups.isEmpty()) + throw new InvalidGroupIdentifierException("general group is missing"); else - throw new InvalidGroupIdentifierException("ambiguous general group for subgroups"); + throw new InvalidGroupIdentifierException("ambiguous general groups for subgroups"); } /** diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java index 5e04618..b1dd182 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java @@ -1,7 +1,6 @@ package org.pkwmtt.examCalendar; import com.fasterxml.jackson.core.JsonProcessingException; -import lombok.SneakyThrows; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; @@ -15,6 +14,7 @@ import org.pkwmtt.examCalendar.repository.ExamRepository; import org.pkwmtt.examCalendar.repository.ExamTypeRepository; import org.pkwmtt.examCalendar.repository.GroupRepository; +import org.pkwmtt.exceptions.InvalidGroupIdentifierException; import org.pkwmtt.timetable.TimetableService; import java.time.LocalDateTime; @@ -46,8 +46,6 @@ class ExamServiceTest { // -// TODO: check for general groups from different years of study - /** * test specification * generalGroup - 1 item @@ -109,7 +107,36 @@ void addExamForMultipleGeneralGroupsWithEmptySubgroups() { // given Set generalGroups = Set.of("12K1", "12K2", "12K3"); Set subgroups = Set.of(); - testExamService(generalGroups, subgroups); + + LocalDateTime date = LocalDateTime.now().plusDays(1); + ExamDto examDto = buildExampleExamDto(generalGroups, subgroups, date); + ExamType examType = buildExampleExamType(); + List studentGroups = buildExampleStudentGroupList(generalGroups); + Exam exam = buildExamWithIdAndGroups(1, studentGroups); + + when(examTypeRepository.findByName(examDto.getExamType())).thenReturn(Optional.of(examType)); + when(timetableService.getGeneralGroupList()).thenReturn(new ArrayList<>(generalGroups)); +// when(timetableService.getGeneralGroupList()).thenReturn(new ArrayList<>(generalGroups)); + + when(groupRepository.findAllByNameIn(generalGroups)).thenReturn(new HashSet<>(Set.of())); + when(groupRepository.saveAll(anyList())).thenReturn(studentGroups); + when(examRepository.save(any(Exam.class))).thenReturn(exam); +// when + int savedId = examService.addExam(examDto); +// then + verify(examTypeRepository, times(1)).findByName(examDto.getExamType()); + verify(timetableService, times(1)).getGeneralGroupList(); + verify(groupRepository, times(1)).findAllByNameIn(generalGroups); + + ArgumentCaptor> groupCaptor = ArgumentCaptor.forClass(List.class); + verify(groupRepository, times(1)).saveAll(groupCaptor.capture()); + Set capturedGroups = groupCaptor.getValue().stream().map(StudentGroup::getName).collect(Collectors.toSet()); + assertEquals(generalGroups, capturedGroups); + + ArgumentCaptor examCaptor = ArgumentCaptor.forClass(Exam.class); + verify(examRepository, times(1)).save(examCaptor.capture()); + Exam savedExam = examCaptor.getValue(); + assertExam(savedExam, date, savedId, generalGroups); } @@ -121,6 +148,17 @@ void addExamForMultipleGeneralGroupsWithEmptySubgroups() { * provided groups - match groups from timetable service * groupRepository - don't contain provided groups */ + @Test + void shouldThrowWhenThereAreMoreThan1GeneralGroupsAndSubgroupsIsPresent() { + // given + LocalDateTime date = LocalDateTime.now().plusDays(1); + Set generalGroups = Set.of("12K1", "12K2", "12K3"); + Set subgroups = Set.of("L04", "L05"); + ExamDto examDto = buildExampleExamDto(generalGroups, subgroups, date); + when(timetableService.getGeneralGroupList()).thenReturn(new ArrayList<>(generalGroups)); + RuntimeException exception = assertThrows(InvalidGroupIdentifierException.class, () -> examService.addExam(examDto)); + assertEquals("Invalid group identifier: ambiguous general groups for subgroups",exception.getMessage()); + } /** * test specification @@ -133,10 +171,10 @@ void addExamForMultipleGeneralGroupsWithEmptySubgroups() { @Test void addExamForSingleGeneralGroupAndSingleSubgroup() throws JsonProcessingException { // given - Set generalGroups = Set.of("12K"); + Set generalGroups = Set.of("12K2"); Set subgroups = Set.of("K04"); when(timetableService.getAvailableSubGroups(any(String.class))).thenReturn(new ArrayList<>(List.of("K03", "K04", "L04"))); - testExamService(generalGroups, subgroups); + testExamServiceForSubgroups(generalGroups, subgroups); } /** @@ -150,10 +188,10 @@ void addExamForSingleGeneralGroupAndSingleSubgroup() throws JsonProcessingExcept @Test void addExamForSingleGeneralGroupAndMultipleSubgroup() throws JsonProcessingException { // given - Set generalGroups = Set.of("12K"); + Set generalGroups = Set.of("12K2"); Set subgroups = Set.of("K04", "P04", "L04", "L03"); when(timetableService.getAvailableSubGroups(any(String.class))).thenReturn(new ArrayList<>(List.of("K03", "K04", "P04", "L04", "L03"))); - testExamService(generalGroups, subgroups); + testExamServiceForSubgroups(generalGroups, subgroups); } @@ -165,25 +203,21 @@ void addExamForSingleGeneralGroupAndMultipleSubgroup() throws JsonProcessingExce * provided groups - match groups from timetable service * groupRepository - don't contain provided groups */ -// @Test -// void addExamForEmptyGeneralGroup() { -// // given -// Set generalGroups = Set.of(); -// Set subgroups = Set.of("P04"); -// testExamService(generalGroups, subgroups); -// } - - /** - * test specification - * generalGroup - blank - * subgroup - 1 items - * timetable service - available - * provided groups - match groups from timetable service - * groupRepository - don't contain provided groups - */ + @Test + void addExamForEmptyGeneralGroup() { + // given + Set generalGroups = Set.of(); + Set subgroups = Set.of("K04"); + LocalDateTime date = LocalDateTime.now().plusDays(1); + ExamDto examDto = buildExampleExamDto(generalGroups, subgroups, date); + RuntimeException exception = assertThrows(InvalidGroupIdentifierException.class, () -> examService.addExam(examDto)); + assertEquals("Invalid group identifier: general group is missing" ,exception.getMessage()); + } // +// TODO: test blank generalGroups in controller + // /** * test specification @@ -658,9 +692,11 @@ private static void assertExam(Exam savedExam, LocalDateTime date, int savedId, assertEquals(1, savedId); } - private void testExamService(Set generalGroups, Set subgroups) { - Set combinedGroups = new HashSet<>(generalGroups); - combinedGroups.addAll(subgroups); + private void testExamServiceForSubgroups(Set generalGroups, Set subgroups) { + Set combinedGroups = new HashSet<>(subgroups); + combinedGroups.addAll(generalGroups.stream() + .map(g -> g.matches(".*\\d$") ? g.substring(0, g.length() - 1) : g) + .collect(Collectors.toSet())); LocalDateTime date = LocalDateTime.now().plusDays(1); ExamDto examDto = buildExampleExamDto(generalGroups, subgroups, date); @@ -670,8 +706,7 @@ private void testExamService(Set generalGroups, Set subgroups) { when(examTypeRepository.findByName(examDto.getExamType())).thenReturn(Optional.of(examType)); when(timetableService.getGeneralGroupList()).thenReturn(new ArrayList<>(generalGroups)); -// when(timetableService.getGeneralGroupList()).thenReturn(new ArrayList<>(generalGroups)); - + when(groupRepository.findAllByNameIn(combinedGroups)).thenReturn(new HashSet<>(Set.of())); when(groupRepository.saveAll(anyList())).thenReturn(studentGroups); when(examRepository.save(any(Exam.class))).thenReturn(exam); From 67bfaddf4979439ec30acc07470fa05759b12bb6 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Fri, 29 Aug 2025 20:17:42 +0200 Subject: [PATCH 064/116] tests for invalid group identifiers in ExamService --- .../pkwmtt/examCalendar/ExamServiceTest.java | 88 ++++++++++++++++--- 1 file changed, 74 insertions(+), 14 deletions(-) diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java index b1dd182..7d48817 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java @@ -227,6 +227,35 @@ void addExamForEmptyGeneralGroup() { * provided groups - don't match groups from timetable service * groupRepository - don't contain provided groups */ + @Test + void shouldThrowWhenGeneralGroupsDontMatchService() { + // given + Set generalGroups = Set.of("12K1", "12K2"); + Set subgroups = Set.of(); + + LocalDateTime date = LocalDateTime.now().plusDays(1); + ExamDto examDto = buildExampleExamDto(generalGroups, subgroups, date); + when(timetableService.getGeneralGroupList()).thenReturn(new ArrayList<>(List.of())); +// when + RuntimeException exception = assertThrows(InvalidGroupIdentifierException.class, () -> examService.addExam(examDto)); +// then + assertEquals("Invalid group identifiers: [12K1, 12K2]", exception.getMessage()); + } + + @Test + void shouldThrowWhenNotAllGeneralGroupsMatchService() { + // given + Set generalGroups = Set.of("12K1", "12K2"); + Set subgroups = Set.of(); + + LocalDateTime date = LocalDateTime.now().plusDays(1); + ExamDto examDto = buildExampleExamDto(generalGroups, subgroups, date); + when(timetableService.getGeneralGroupList()).thenReturn(new ArrayList<>(List.of("12K1"))); +// when + RuntimeException exception = assertThrows(InvalidGroupIdentifierException.class, () -> examService.addExam(examDto)); +// then + assertEquals("Invalid group identifiers: [12K2]", exception.getMessage()); + } /** * test specification @@ -236,13 +265,57 @@ void addExamForEmptyGeneralGroup() { * provided groups - partially match groups from timetable service * groupRepository - don't contain provided groups */ + @Test + void shouldThrowWhenSubgroupsDontMatchService() throws JsonProcessingException { + // given + Set generalGroups = Set.of("12K2"); + Set subgroups = Set.of("K04", "P04", "L04"); + + LocalDateTime date = LocalDateTime.now().plusDays(1); + ExamDto examDto = buildExampleExamDto(generalGroups, subgroups, date); + when(timetableService.getGeneralGroupList()).thenReturn(new ArrayList<>(List.of("12K2"))); + when(timetableService.getAvailableSubGroups("12K2")).thenReturn(List.of("K05")); +// when + RuntimeException exception = assertThrows(InvalidGroupIdentifierException.class, () -> examService.addExam(examDto)); +// then + String message = exception.getMessage(); + assertTrue(message.startsWith("Invalid group identifiers:")); + assertFalse(message.contains("12K2")); + assertTrue(message.contains("K04")); + assertTrue(message.contains("P04")); + assertTrue(message.contains("L04")); + assertFalse(message.contains("K05")); + } + + @Test + void shouldThrowWhenNotAllSubgroupsMatchService() throws JsonProcessingException { + // given + Set generalGroups = Set.of("12K2"); + Set subgroups = Set.of("K04", "P04", "L04"); + + LocalDateTime date = LocalDateTime.now().plusDays(1); + ExamDto examDto = buildExampleExamDto(generalGroups, subgroups, date); + when(timetableService.getGeneralGroupList()).thenReturn(new ArrayList<>(List.of("12K2"))); + when(timetableService.getAvailableSubGroups("12K2")).thenReturn(List.of("P04", "L04", "K05")); +// when + RuntimeException exception = assertThrows(InvalidGroupIdentifierException.class, () -> examService.addExam(examDto)); +// then + String message = exception.getMessage(); + assertTrue(message.startsWith("Invalid group identifiers:")); + assertFalse(message.contains("12K2")); + assertTrue(message.contains("K04")); + assertFalse(message.contains("P04")); + assertFalse(message.contains("L04")); + assertFalse(message.contains("K05")); + } + // // /** * test specification * generalGroup - 1 item - * subgroup - blank + * subgroup - 0 items * timetable service - available * provided groups - match groups from timetable service * groupRepository - contain provided groups @@ -316,19 +389,6 @@ void addExamForEmptyGeneralGroup() { * groupRepository - contain provided groups */ // - @Test - void addExamForSingleSubgroup() { - - } - - @Test - void addExamForMultipleSubgroups() { - - } - - @Test - void addExamWithWrongExamType() { - } /************************************************************************************/ //modify exam From e2ba5526970a9c21e4110e43e97239a66601a055 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Fri, 29 Aug 2025 23:10:22 +0200 Subject: [PATCH 065/116] tests addExam in ExamService --- .../org/pkwmtt/examCalendar/ExamService.java | 4 +- .../pkwmtt/examCalendar/ExamServiceTest.java | 195 ++++++++++++++++-- 2 files changed, 178 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamService.java b/src/main/java/org/pkwmtt/examCalendar/ExamService.java index eb15c14..d92b193 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamService.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamService.java @@ -194,8 +194,8 @@ private Set saveNewStudentGroups(Set groups) { List savedGroups = groupRepository.saveAll(groups.stream() .map(g -> StudentGroup.builder() .name(g) - .build()) - .collect(Collectors.toList()) + .build() + ).collect(Collectors.toList()) ); existingGroups.addAll(savedGroups); return existingGroups; diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java index 7d48817..f199f06 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java @@ -1,6 +1,8 @@ package org.pkwmtt.examCalendar; +import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonProcessingException; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; @@ -15,6 +17,8 @@ import org.pkwmtt.examCalendar.repository.ExamTypeRepository; import org.pkwmtt.examCalendar.repository.GroupRepository; import org.pkwmtt.exceptions.InvalidGroupIdentifierException; +import org.pkwmtt.exceptions.ServiceNotAvailableException; +import org.pkwmtt.exceptions.WebPageContentNotAvailableException; import org.pkwmtt.timetable.TimetableService; import java.time.LocalDateTime; @@ -116,7 +120,6 @@ void addExamForMultipleGeneralGroupsWithEmptySubgroups() { when(examTypeRepository.findByName(examDto.getExamType())).thenReturn(Optional.of(examType)); when(timetableService.getGeneralGroupList()).thenReturn(new ArrayList<>(generalGroups)); -// when(timetableService.getGeneralGroupList()).thenReturn(new ArrayList<>(generalGroups)); when(groupRepository.findAllByNameIn(generalGroups)).thenReturn(new HashSet<>(Set.of())); when(groupRepository.saveAll(anyList())).thenReturn(studentGroups); @@ -320,6 +323,37 @@ void shouldThrowWhenNotAllSubgroupsMatchService() throws JsonProcessingException * provided groups - match groups from timetable service * groupRepository - contain provided groups */ + @Test + void addExamForSingleGeneralGroupWithRepositoryContainingGroup() { + // given + Set generalGroups = Set.of("12K2"); + Set subgroups = Set.of(); + + LocalDateTime date = LocalDateTime.now().plusDays(1); + ExamDto examDto = buildExampleExamDto(generalGroups, subgroups, date); + ExamType examType = buildExampleExamType(); + List studentGroups = buildExampleStudentGroupList(generalGroups); + Exam exam = buildExamWithIdAndGroups(1, studentGroups); + + when(examTypeRepository.findByName(examDto.getExamType())).thenReturn(Optional.of(examType)); + when(timetableService.getGeneralGroupList()).thenReturn(new ArrayList<>(generalGroups)); + + when(groupRepository.findAllByNameIn(generalGroups)).thenReturn(new HashSet<>(studentGroups)); + when(groupRepository.saveAll(any())).thenReturn(List.of()); + when(examRepository.save(any(Exam.class))).thenReturn(exam); +// when + int savedId = examService.addExam(examDto); +// then + verify(examTypeRepository, times(1)).findByName(examDto.getExamType()); + verify(timetableService, times(1)).getGeneralGroupList(); + verify(groupRepository, times(1)).findAllByNameIn(any()); //??? + verify(groupRepository, times(1)).saveAll(List.of()); + + ArgumentCaptor examCaptor = ArgumentCaptor.forClass(Exam.class); + verify(examRepository, times(1)).save(examCaptor.capture()); + Exam savedExam = examCaptor.getValue(); + assertExam(savedExam, date, savedId, generalGroups); + } /** * test specification @@ -329,6 +363,39 @@ void shouldThrowWhenNotAllSubgroupsMatchService() throws JsonProcessingException * provided groups - match groups from timetable service * groupRepository - partially contain provided groups */ + @Test + void addExamForSingleGeneralGroupAndSubgroupsWithRepositoryContainingGroups() throws JsonProcessingException { + // given + Set generalGroups = Set.of("12K2"); + Set subgroups = Set.of("K04", "P04", "L04", "K05"); + Set combinedGroups = Set.of("12K", "K04", "P04", "L04", "K05"); + + LocalDateTime date = LocalDateTime.now().plusDays(1); + ExamDto examDto = buildExampleExamDto(generalGroups, subgroups, date); + ExamType examType = buildExampleExamType(); + List studentGroups = buildExampleStudentGroupList(combinedGroups); + Exam exam = buildExamWithIdAndGroups(1, studentGroups); + + when(examTypeRepository.findByName(examDto.getExamType())).thenReturn(Optional.of(examType)); + when(timetableService.getGeneralGroupList()).thenReturn(new ArrayList<>(generalGroups)); + when(timetableService.getAvailableSubGroups("12K2")).thenReturn(List.of("K04", "P04", "L04", "K05")); + + when(groupRepository.findAllByNameIn(any(Set.class))).thenReturn(new HashSet<>(studentGroups.subList(0,3))); + when(groupRepository.saveAll(any())).thenReturn(studentGroups.subList(3,5)); + when(examRepository.save(any(Exam.class))).thenReturn(exam); +// when + int savedId = examService.addExam(examDto); +// then + verify(examTypeRepository, times(1)).findByName(examDto.getExamType()); + verify(timetableService, times(1)).getGeneralGroupList(); + verify(groupRepository, times(1)).findAllByNameIn(any()); + verify(groupRepository, times(1)).saveAll(any()); + + ArgumentCaptor examCaptor = ArgumentCaptor.forClass(Exam.class); + verify(examRepository, times(1)).save(examCaptor.capture()); + Exam savedExam = examCaptor.getValue(); + assertExam(savedExam, date, savedId, combinedGroups); + } // @@ -336,29 +403,62 @@ void shouldThrowWhenNotAllSubgroupsMatchService() throws JsonProcessingException /** * test specification * generalGroup - 1 item - * subgroup - blank + * subgroup - 0 item * timetable service - unavailable * provided groups - match groups from timetable service * groupRepository - don't contain provided groups */ + @Test + void unavailableServiceAndRepositoryDontMatch() { +// given + Set generalGroups = Set.of("12K2"); + Set subgroups = Set.of(); + + LocalDateTime date = LocalDateTime.now().plusDays(1); + ExamDto examDto = buildExampleExamDto(generalGroups, subgroups, date); + +// more groups than in set + when(timetableService.getGeneralGroupList()).thenThrow(new WebPageContentNotAvailableException()); + when(groupRepository.findAllByNameIn(generalGroups)).thenReturn(new HashSet<>(Set.of())); +// when + RuntimeException exception = assertThrows(ServiceNotAvailableException.class, () -> examService.addExam(examDto)); +// then + assertEquals("Couldn't verify groups using repository" ,exception.getMessage()); + verify(timetableService, times(1)).getGeneralGroupList(); + verify(groupRepository, times(1)).findAllByNameIn(generalGroups); + } - /** - * test specification - * generalGroup - 1 item - * subgroup - 3 items - * timetable service - unavailable - * provided groups - match groups from timetable service - * groupRepository - don't contain provided groups - */ /** * test specification * generalGroup - 1 item - * subgroup - 4 items + * subgroup - 3 items * timetable service - unavailable * provided groups - match groups from timetable service * groupRepository - partially contain provided groups */ + @Test + void unavailableServiceAndRepositoryDontMatchForSubgroups() throws JsonProcessingException { +// given + Set generalGroups = Set.of("12K2"); + Set subgroups = Set.of("L04", "K04", "P04"); + + LocalDateTime date = LocalDateTime.now().plusDays(1); + ExamDto examDto = buildExampleExamDto(generalGroups, subgroups, date); + List studentGroups = buildExampleStudentGroupList(Set.of("12K2", "L04")); + +// more groups than in set + when(timetableService.getGeneralGroupList()).thenThrow(new WebPageContentNotAvailableException()); + when(timetableService.getAvailableSubGroups("12K2")).thenThrow(new WebPageContentNotAvailableException()); + when(groupRepository.findAllByNameIn(any())).thenReturn(new HashSet<>(studentGroups)); +// when + RuntimeException exception = assertThrows(ServiceNotAvailableException.class, () -> examService.addExam(examDto)); +// then + assertEquals("Couldn't verify groups using timetable service" ,exception.getMessage()); + verify(timetableService, times(1)).getGeneralGroupList(); + verify(groupRepository, times(1)).findAllByNameIn(generalGroups); + } + // // @@ -370,6 +470,37 @@ void shouldThrowWhenNotAllSubgroupsMatchService() throws JsonProcessingException * provided groups - match groups from timetable service * groupRepository - contain provided groups */ + @Test + void addExamWhenServiceIsUnavailableAndRepositoryContainsGeneralGroups(){ + // given + Set generalGroups = Set.of("12K1", "12K2"); + Set subgroups = Set.of(); + + LocalDateTime date = LocalDateTime.now().plusDays(1); + ExamDto examDto = buildExampleExamDto(generalGroups, subgroups, date); + ExamType examType = buildExampleExamType(); + List studentGroups = buildExampleStudentGroupList(generalGroups); + Exam exam = buildExamWithIdAndGroups(1, studentGroups); + + when(examTypeRepository.findByName(examDto.getExamType())).thenReturn(Optional.of(examType)); + when(timetableService.getGeneralGroupList()).thenThrow(new WebPageContentNotAvailableException()); + + when(groupRepository.findAllByNameIn(generalGroups)).thenReturn(new HashSet<>(studentGroups)); + when(groupRepository.saveAll(anyList())).thenReturn(studentGroups); + when(examRepository.save(any(Exam.class))).thenReturn(exam); +// when + int savedId = examService.addExam(examDto); +// then + verify(examTypeRepository, times(1)).findByName(examDto.getExamType()); + verify(timetableService, times(1)).getGeneralGroupList(); + verify(groupRepository, times(2)).findAllByNameIn(any()); + verify(groupRepository, times(1)).saveAll(any()); + + ArgumentCaptor examCaptor = ArgumentCaptor.forClass(Exam.class); + verify(examRepository, times(1)).save(examCaptor.capture()); + Exam savedExam = examCaptor.getValue(); + assertExam(savedExam, date, savedId, generalGroups); + } /** * test specification @@ -379,15 +510,41 @@ void shouldThrowWhenNotAllSubgroupsMatchService() throws JsonProcessingException * provided groups - match groups from timetable service * groupRepository - contain provided groups */ + @Test + @Disabled("Not supported yet") + void addExamWhenServiceIsUnavailableAndRepositoryContainsGroups() throws JsonProcessingException { + // given + Set generalGroups = Set.of("12K2"); + Set subgroups = Set.of("L04", "K04", "P04", "K05"); + Set combinedGroups = Set.of("12K2", "L04", "K04", "P04", "K05"); + + LocalDateTime date = LocalDateTime.now().plusDays(1); + ExamDto examDto = buildExampleExamDto(generalGroups, subgroups, date); + ExamType examType = buildExampleExamType(); + List studentGroups = buildExampleStudentGroupList(combinedGroups); + Exam exam = buildExamWithIdAndGroups(1, studentGroups); + + when(examTypeRepository.findByName(examDto.getExamType())).thenReturn(Optional.of(examType)); + when(timetableService.getGeneralGroupList()).thenThrow(new WebPageContentNotAvailableException()); + when(timetableService.getAvailableSubGroups("12K2")).thenThrow(new JsonParseException("parsing subgroups failed")); + + when(groupRepository.findAllByNameIn(any(Set.class))).thenReturn(new HashSet<>(studentGroups)); + when(groupRepository.saveAll(anyList())).thenReturn(List.of()); + when(examRepository.save(any(Exam.class))).thenReturn(exam); +// when + int savedId = examService.addExam(examDto); +// then + verify(examTypeRepository, times(1)).findByName(examDto.getExamType()); + verify(timetableService, times(1)).getGeneralGroupList(); + verify(groupRepository, times(2)).findAllByNameIn(any()); + verify(groupRepository, times(1)).saveAll(any()); + + ArgumentCaptor examCaptor = ArgumentCaptor.forClass(Exam.class); + verify(examRepository, times(1)).save(examCaptor.capture()); + Exam savedExam = examCaptor.getValue(); + assertExam(savedExam, date, savedId, generalGroups); + } - /** - * test specification - * generalGroup - 3 item - * subgroup - 1 items - * timetable service - unavailable - * provided groups - match groups from timetable service - * groupRepository - contain provided groups - */ // /************************************************************************************/ From 3671cf9b4c990a53a02c19c34e205e38c0f28be6 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Fri, 29 Aug 2025 23:33:20 +0200 Subject: [PATCH 066/116] tests getExams in ExamService --- .../pkwmtt/examCalendar/ExamServiceTest.java | 344 +++++------------- 1 file changed, 90 insertions(+), 254 deletions(-) diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java index f199f06..ecc6649 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java @@ -160,7 +160,7 @@ void shouldThrowWhenThereAreMoreThan1GeneralGroupsAndSubgroupsIsPresent() { ExamDto examDto = buildExampleExamDto(generalGroups, subgroups, date); when(timetableService.getGeneralGroupList()).thenReturn(new ArrayList<>(generalGroups)); RuntimeException exception = assertThrows(InvalidGroupIdentifierException.class, () -> examService.addExam(examDto)); - assertEquals("Invalid group identifier: ambiguous general groups for subgroups",exception.getMessage()); + assertEquals("Invalid group identifier: ambiguous general groups for subgroups", exception.getMessage()); } /** @@ -214,7 +214,7 @@ void addExamForEmptyGeneralGroup() { LocalDateTime date = LocalDateTime.now().plusDays(1); ExamDto examDto = buildExampleExamDto(generalGroups, subgroups, date); RuntimeException exception = assertThrows(InvalidGroupIdentifierException.class, () -> examService.addExam(examDto)); - assertEquals("Invalid group identifier: general group is missing" ,exception.getMessage()); + assertEquals("Invalid group identifier: general group is missing", exception.getMessage()); } // @@ -222,6 +222,7 @@ void addExamForEmptyGeneralGroup() { // TODO: test blank generalGroups in controller // + /** * test specification * generalGroup - 2 item @@ -315,6 +316,7 @@ void shouldThrowWhenNotAllSubgroupsMatchService() throws JsonProcessingException // // + /** * test specification * generalGroup - 1 item @@ -380,8 +382,8 @@ void addExamForSingleGeneralGroupAndSubgroupsWithRepositoryContainingGroups() th when(timetableService.getGeneralGroupList()).thenReturn(new ArrayList<>(generalGroups)); when(timetableService.getAvailableSubGroups("12K2")).thenReturn(List.of("K04", "P04", "L04", "K05")); - when(groupRepository.findAllByNameIn(any(Set.class))).thenReturn(new HashSet<>(studentGroups.subList(0,3))); - when(groupRepository.saveAll(any())).thenReturn(studentGroups.subList(3,5)); + when(groupRepository.findAllByNameIn(any(Set.class))).thenReturn(new HashSet<>(studentGroups.subList(0, 3))); + when(groupRepository.saveAll(any())).thenReturn(studentGroups.subList(3, 5)); when(examRepository.save(any(Exam.class))).thenReturn(exam); // when int savedId = examService.addExam(examDto); @@ -400,6 +402,7 @@ void addExamForSingleGeneralGroupAndSubgroupsWithRepositoryContainingGroups() th // // + /** * test specification * generalGroup - 1 item @@ -423,7 +426,7 @@ void unavailableServiceAndRepositoryDontMatch() { // when RuntimeException exception = assertThrows(ServiceNotAvailableException.class, () -> examService.addExam(examDto)); // then - assertEquals("Couldn't verify groups using repository" ,exception.getMessage()); + assertEquals("Couldn't verify groups using repository", exception.getMessage()); verify(timetableService, times(1)).getGeneralGroupList(); verify(groupRepository, times(1)).findAllByNameIn(generalGroups); } @@ -454,7 +457,7 @@ void unavailableServiceAndRepositoryDontMatchForSubgroups() throws JsonProcessin // when RuntimeException exception = assertThrows(ServiceNotAvailableException.class, () -> examService.addExam(examDto)); // then - assertEquals("Couldn't verify groups using timetable service" ,exception.getMessage()); + assertEquals("Couldn't verify groups using timetable service", exception.getMessage()); verify(timetableService, times(1)).getGeneralGroupList(); verify(groupRepository, times(1)).findAllByNameIn(generalGroups); } @@ -462,6 +465,7 @@ void unavailableServiceAndRepositoryDontMatchForSubgroups() throws JsonProcessin // // + /** * test specification * generalGroup - 2 item @@ -471,7 +475,7 @@ void unavailableServiceAndRepositoryDontMatchForSubgroups() throws JsonProcessin * groupRepository - contain provided groups */ @Test - void addExamWhenServiceIsUnavailableAndRepositoryContainsGeneralGroups(){ + void addExamWhenServiceIsUnavailableAndRepositoryContainsGeneralGroups() { // given Set generalGroups = Set.of("12K1", "12K2"); Set subgroups = Set.of(); @@ -617,253 +621,85 @@ void shouldThrowExceptionWhenExamNotFound() { } // getExamByGroup - // FIXME: write test for new version of this method -// @Test -// void shouldThrowWithMoreThan4Arguments() { -//// given -// Set groups = new HashSet<>(); -// groups.add("12K2"); -// groups.add("13L1"); -// groups.add("13A2"); -// groups.add("41S2"); -// groups.add("11S3"); -//// when -// RuntimeException exception = assertThrows( -// UnsupportedCountOfArgumentsException.class, -// () -> examService.getExamByGroup(groups) -// ); -//// then -// assertEquals( -// "Invalid count of arguments provided: 5 expected more than: 1 less than: 5", -// exception.getMessage() -// ); -// } - - -// @Test -// void shouldCallRepositoryWith4Arguments() { -//// given -// Set groups = new HashSet<>(); -// groups.add("12K2"); -// groups.add("13L1"); -// groups.add("13A2"); -// groups.add("41S2"); -// Exam mockExam = mock(Exam.class); -// Set exams = new HashSet<>(); -// exams.add(mockExam); -// when(examRepository.findExamsByGroupsIdentifier(any(), any(), any(), any())).thenReturn(exams); -//// when -// Set result = examService.getExamByGroup(groups); -//// then -// List> cap = new ArrayList<>(); -// for (int i = 0; i < 4; ++i) -// cap.add(ArgumentCaptor.forClass(String.class)); -// -// verify(examRepository).findExamsByGroupsIdentifier( -// cap.get(0).capture(), -// cap.get(1).capture(), -// cap.get(2).capture(), -// cap.get(3).capture() -// ); -// Set passedGroups = cap.stream().map(ArgumentCaptor::getValue).collect(Collectors.toSet()); -// -// assertEquals(groups, passedGroups); -// assertEquals(exams, result); -// } -// -// -// @Test -// void shouldCallRepositoryWith3Arguments() { -//// given -// Set groups = new HashSet<>(); -// groups.add("12K2"); -// groups.add("13L1"); -// groups.add("13A2"); -// Exam mockExam = mock(Exam.class); -// Set exams = new HashSet<>(); -// exams.add(mockExam); -// when(examRepository.findExamsByGroupsIdentifier(any(), any(), any())).thenReturn(exams); -//// when -// Set result = examService.getExamByGroup(groups); -//// then -// List> cap = new ArrayList<>(); -// for (int i = 0; i < 3; ++i) -// cap.add(ArgumentCaptor.forClass(String.class)); -// -// verify(examRepository).findExamsByGroupsIdentifier( -// cap.get(0).capture(), -// cap.get(1).capture(), -// cap.get(2).capture() -// ); -// Set passedGroups = cap.stream().map(ArgumentCaptor::getValue).collect(Collectors.toSet()); -// -// assertEquals(groups, passedGroups); -// assertEquals(exams, result); -// } -// -// @Test -// void shouldCallRepositoryWith2Arguments() { -//// given -// Set groups = new HashSet<>(); -// groups.add("12K2"); -// groups.add("13L1"); -// Exam mockExam = mock(Exam.class); -// Set exams = new HashSet<>(); -// exams.add(mockExam); -// when(examRepository.findExamsByGroupsIdentifier(any(), any())).thenReturn(exams); -//// when -// Set result = examService.getExamByGroup(groups); -//// then -// List> cap = new ArrayList<>(); -// for (int i = 0; i < 2; ++i) -// cap.add(ArgumentCaptor.forClass(String.class)); -// -// verify(examRepository).findExamsByGroupsIdentifier( -// cap.get(0).capture(), -// cap.get(1).capture() -// ); -// Set passedGroups = cap.stream().map(ArgumentCaptor::getValue).collect(Collectors.toSet()); -// -// assertEquals(groups, passedGroups); -// assertEquals(exams, result); -// } -// -// @Test -// void shouldCallRepositoryWithSingleArguments() { -//// given -// Set groups = new HashSet<>(); -// groups.add("12K2"); -// Exam mockExam = mock(Exam.class); -// Set exams = new HashSet<>(); -// exams.add(mockExam); -// when(examRepository.findExamsByGroupsIdentifier(any())).thenReturn(exams); -//// when -// Set result = examService.getExamByGroup(groups); -//// then -// ArgumentCaptor cap = ArgumentCaptor.forClass(String.class); -// -// verify(examRepository).findExamsByGroupsIdentifier(cap.capture()); -// Set passedGroups = new HashSet<>(); -// passedGroups.add(cap.getValue()); -// -// assertEquals(groups, passedGroups); -// assertEquals(exams, result); -// } -// -// -// @Test -// void shouldCallRepositoryWithDuplicatesOf4UniqueArguments() { -//// given -// Set groups = new HashSet<>(); -// groups.add("12K2"); -// groups.add("13L1"); -// groups.add("13A2"); -// groups.add("41S2"); -// groups.add("41S2"); -// groups.add("13L1"); -// Exam mockExam = mock(Exam.class); -// Set exams = new HashSet<>(); -// exams.add(mockExam); -// when(examRepository.findExamsByGroupsIdentifier(any(), any(), any(), any())).thenReturn(exams); -//// when -// Set result = examService.getExamByGroup(groups); - - /// / then -// List> cap = new ArrayList<>(); -// for (int i = 0; i < 4; ++i) -// cap.add(ArgumentCaptor.forClass(String.class)); -// -// verify(examRepository).findExamsByGroupsIdentifier( -// cap.get(0).capture(), -// cap.get(1).capture(), -// cap.get(2).capture(), -// cap.get(3).capture() -// ); -// Set passedGroups = cap.stream().map(ArgumentCaptor::getValue).collect(Collectors.toSet()); -// -// assertEquals(groups, passedGroups); -// assertEquals(exams, result); -// assertEquals(4, passedGroups.size()); -// } - - -// helper methods -// private ExamDto getExampleExamDto(String examTypeName) { -// return ExamDto.builder() -// .title("title") -// .description("desc") -// .date(LocalDateTime.now().plusDays(1)) -// .examType(examTypeName) -// .examGroups(Set.of("12K1", "P05", "L02")) -// .build(); -// } -// -// private ExamType saveExampleExamType(String examTypeName) { -// return ExamType.builder().examTypeId(1).name(examTypeName).build(); -// } -// -// private List mockGetGeneralGroupList(){ -// List groups = List.of("12K1", "12K2", "12K3"); -// when(timetableService.getGeneralGroupList()).thenReturn(groups); -// return groups; -// } -// -// private void mockGetSubGroupsList() throws JsonProcessingException { -// List groups1 = List.of( -// "K01", "K04", "L01", "L02", "L04", "P01", "P04" -// ); -// List groups2 = List.of( -// "K02", "K04", "K05", "L02", "L03", "L04", "L05", "P02", "P04", "P05" -// ); -// List groups3 = List.of( -// "K03", "K05", "L03", "L05", "L06", "P03", "P05" -// ); -// -// when(timetableService.getAvailableSubGroups("12K1")).thenReturn(groups1); -// when(timetableService.getAvailableSubGroups("12K2")).thenReturn(groups2); -// when(timetableService.getAvailableSubGroups("12K3")).thenReturn(groups3); -// return new ArrayList(groups1, groups2, groups3); -// } -// -// private void mockGroupRepositoryFindByName() { -// when(groupRepository.findAllByNameIn(Set.of("12K1", "P05", "L02"))).thenReturn( -// Stream.of("12K1", "P05", "L02").map(s -> -// StudentGroup.builder() -// .name(s) -// .build() -// ).collect(Collectors.toSet()) -// ); -// } -// -// private void mockExamRepositoryFindByName() { -// when(examTypeRepository.findByName("exam")).thenReturn(Optional.of(ExamType.builder() -// .examTypeId(1) -// .name("exam") -// .build())); -// } -// -// private Set getExampleStudentGroupsSet() { -// return Stream.of("12K1", "P05", "L02").map(s -> -// StudentGroup.builder() -// .name(s) -// .build() -// ).collect(Collectors.toSet()); -// } -// -// private void mockExamRepositorySaveExam(Set studentGroups) { -// when(examRepository.save(any(Exam.class))).thenReturn(Exam.builder() -// .examId(1) -// .title("title") -// .description("desc") -// .examDate(LocalDateTime.now().plusDays(1)) -// .examType(ExamType.builder() -// .examTypeId(1) -// .name("exam") -// .build()) -// .groups(studentGroups) -// .build() -// ); -// } + + @Test + void getExamsForNormalGroups() { +// given + Set generalGroups = Set.of("12K2"); + Set subgroups = Set.of("L04", "K04", "P04"); + +// when + examService.getExamByGroups(generalGroups, subgroups); +// then + verify(examRepository, times(1)).findAllByGroups_NameIn(generalGroups); + verify(examRepository, times(1)).findAllBySubgroupsOfGeneralGroup("12K", subgroups); + } + + @Test + void getExamsForGroupWithoutDigitAsFirstCharacter() { +// given + Set generalGroups = Set.of("1Er"); + Set subgroups = Set.of("L01", "K01", "P01"); + +// when + examService.getExamByGroups(generalGroups, subgroups); +// then + verify(examRepository, times(1)).findAllByGroups_NameIn(generalGroups); + verify(examRepository, times(1)).findAllBySubgroupsOfGeneralGroup("1Er", subgroups); + } + + @Test + void getExamsWithEmptySubgroups() { +// given + Set generalGroups = Set.of("12K2"); + Set subgroups = Set.of(); + +// when + examService.getExamByGroups(generalGroups, subgroups); +// then + verify(examRepository, times(1)).findAllByGroups_NameIn(generalGroups); + verify(examRepository, never()).findAllBySubgroupsOfGeneralGroup(any(), any()); + } + + @Test + void getExamsWithBlankSubgroups() { +// given + Set generalGroups = Set.of("12K2"); + Set subgroups = null; + +// when + examService.getExamByGroups(generalGroups, subgroups); +// then + verify(examRepository, times(1)).findAllByGroups_NameIn(generalGroups); + verify(examRepository, never()).findAllBySubgroupsOfGeneralGroup(any(), any()); + } + + @Test + void shouldNotThrowWhenGroupsAreFromTheSameYearOfStudy() { +// given + Set generalGroups = Set.of("12K1", "12K2"); + Set subgroups = Set.of("L01", "K01", "P01"); + +// when + examService.getExamByGroups(generalGroups, subgroups); +// then + verify(examRepository, times(1)).findAllByGroups_NameIn(generalGroups); + verify(examRepository, times(1)).findAllBySubgroupsOfGeneralGroup("12K", subgroups); + } + + @Test + void shouldThrowWhenGeneralGroupsAreFromDifferentYearOfStudy() { +// given + Set generalGroups = Set.of("12K1", "12A2"); + Set subgroups = Set.of("L01", "K01", "P01"); +// when + RuntimeException exception = assertThrows(InvalidGroupIdentifierException.class, () -> examService.getExamByGroups(generalGroups, subgroups)); +// then + assertEquals("Invalid group identifier: ambiguous superior group identifier for subgroups", exception.getMessage()); + } + + + private static List buildExampleStudentGroupList(Set groupNames) { AtomicInteger id = new AtomicInteger(); return groupNames.stream() From fb1ef5d40556d4f2b17296009490fee7cd49ee65 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Sat, 30 Aug 2025 01:08:23 +0200 Subject: [PATCH 067/116] resolve issue with some tests from ExamController --- .../examCalendar/ExamControllerTest.java | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java index 3fb89c8..cebb628 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java @@ -10,6 +10,7 @@ import org.pkwmtt.examCalendar.entity.StudentGroup; import org.pkwmtt.examCalendar.repository.ExamRepository; import org.pkwmtt.examCalendar.repository.ExamTypeRepository; +import org.pkwmtt.timetable.TimetableService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; @@ -20,10 +21,9 @@ import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.hamcrest.Matchers.containsString; import static org.junit.jupiter.api.Assertions.*; @@ -90,20 +90,24 @@ void addExamWithCorrectData() throws Exception { examDtoRequest.getDate().truncatedTo(ChronoUnit.MINUTES), examResponse.getExamDate().truncatedTo(ChronoUnit.MINUTES) ); - assertEquals(examDtoRequest.getExamGroups(), examResponse.getGroups()); +// assertEquals(examDtoRequest.getGeneralGroups(), examResponse.getGroups().stream()); +// assertEquals(examDtoRequest.getGeneralGroups(), examResponse.getGroups()); + assertEquals(examDtoRequest.getExamType(), examResponse.getExamType().getName()); } +// TODO: change Map to Map @Test void addExamWithBlankExamTitle() throws Exception { // given createExampleExamType("Project"); - Map requestData = new HashMap<>(); + Map requestData = new HashMap<>(); // no exam title requestData.put("description", "first exam"); requestData.put("date", LocalDateTime.now().plusDays(1).toString()); - requestData.put("examGroups", "12K2, L04"); requestData.put("examType", "Project"); + requestData.put("generalGroups", List.of("12K2")); + requestData.put("subgroups", List.of("L04")); // when MvcResult result = assertPostRequest(status().isBadRequest(), requestData); @@ -413,7 +417,7 @@ void getExamByIdWithCorrectId() throws Exception { exam.getExamDate().truncatedTo(ChronoUnit.MINUTES), LocalDateTime.parse(responseNode.get("date").textValue()).truncatedTo(ChronoUnit.MINUTES) ); - assertEquals(exam.getGroups(), responseNode.get("examGroups").asText()); +// assertEquals(exam.getGroups(), responseNode.get("examGroups").asText()); assertEquals(mapper.readTree(mapper.writeValueAsString(exam.getExamType())), responseNode.get("examType")); } @@ -495,14 +499,13 @@ private ExamType createExampleExamType(String name) { * @return created Exam */ private Exam createExampleExam(ExamType type) { - Set examGroups = new HashSet<>(); - examGroups.add(StudentGroup.builder().name("11K1").build()); - examGroups.add(StudentGroup.builder().name("L01").build()); return Exam.builder() .title("Exam") .description("Exam description") .examDate(LocalDateTime.now().plusDays(1)) - .groups(examGroups) + .groups(Stream.of("12K2", "L04") + .map(g -> StudentGroup.builder().name(g).build()) + .collect(Collectors.toSet())) .examType(type) .build(); } @@ -512,13 +515,14 @@ private Exam createExampleExam(ExamType type) { * @return created ExamDto */ private ExamDto createExampleExamDto(String examTypeName) { - return new ExamDto( - "Math exam", - "first exam", - LocalDateTime.now().plusDays(1), - examTypeName, - Set.of("11k1", "L01") - ); + return ExamDto.builder() + .title("Math exam") + .description("first exam") + .date(LocalDateTime.now().plusDays(1)) + .examType(examTypeName) + .generalGroups(Set.of("12K2")) + .subgroups(Set.of("L04")) + .build(); } /** From 12ca37facfb8a24dc1401606aebda995435355a8 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Sat, 30 Aug 2025 15:57:35 +0200 Subject: [PATCH 068/116] modify old examController tests for new implementation --- .../examCalendar/ExamControllerTest.java | 238 +++++++++--------- 1 file changed, 126 insertions(+), 112 deletions(-) diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java index cebb628..e8fc720 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.transaction.Transactional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.pkwmtt.examCalendar.dto.ExamDto; @@ -10,7 +11,7 @@ import org.pkwmtt.examCalendar.entity.StudentGroup; import org.pkwmtt.examCalendar.repository.ExamRepository; import org.pkwmtt.examCalendar.repository.ExamTypeRepository; -import org.pkwmtt.timetable.TimetableService; +import org.pkwmtt.examCalendar.repository.GroupRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; @@ -49,11 +50,14 @@ class ExamControllerTest { @Autowired private ObjectMapper mapper; + @Autowired + private GroupRepository groupRepository; @BeforeEach void setupBeforeEach() { examRepository.deleteAll(); examTypeRepository.deleteAll(); + groupRepository.deleteAll(); } // @@ -62,6 +66,7 @@ void setupBeforeEach() { * check if addExam endpoint create new exam with correct URI and correct data */ @Test + @Transactional void addExamWithCorrectData() throws Exception { // given createExampleExamType("Project"); @@ -83,6 +88,17 @@ void addExamWithCorrectData() throws Exception { Exam examResponse = examRepository.findById(id).orElseThrow(); + Set responseSubgroups = examResponse.getGroups().stream() + .map(StudentGroup::getName) + .collect(Collectors.toSet()); + Set responseGeneralGroups = responseSubgroups.stream() + .filter(g -> g.matches("^\\d.*")) + .collect(Collectors.toSet()); + responseSubgroups.removeAll(responseGeneralGroups); + + assertEquals(responseGeneralGroups, Set.of("12K")); + assertEquals(responseSubgroups, examDtoRequest.getSubgroups()); + assertEquals(examDtoRequest.getTitle(), examResponse.getTitle()); assertEquals(examDtoRequest.getDescription(), examResponse.getDescription()); // compare dates with minutes level precision @@ -90,25 +106,21 @@ void addExamWithCorrectData() throws Exception { examDtoRequest.getDate().truncatedTo(ChronoUnit.MINUTES), examResponse.getExamDate().truncatedTo(ChronoUnit.MINUTES) ); -// assertEquals(examDtoRequest.getGeneralGroups(), examResponse.getGroups().stream()); -// assertEquals(examDtoRequest.getGeneralGroups(), examResponse.getGroups()); assertEquals(examDtoRequest.getExamType(), examResponse.getExamType().getName()); } -// TODO: change Map to Map @Test void addExamWithBlankExamTitle() throws Exception { // given createExampleExamType("Project"); - Map requestData = new HashMap<>(); -// no exam title - requestData.put("description", "first exam"); - requestData.put("date", LocalDateTime.now().plusDays(1).toString()); - requestData.put("examType", "Project"); - requestData.put("generalGroups", List.of("12K2")); - requestData.put("subgroups", List.of("L04")); - + ExamDto requestData = ExamDto.builder() + .description("first exam") + .date(LocalDateTime.now().plusDays(1)) + .examType("Project") + .generalGroups(Set.of("12K2")) + .subgroups(Set.of("L04")) + .build(); // when MvcResult result = assertPostRequest(status().isBadRequest(), requestData); @@ -120,13 +132,13 @@ void addExamWithBlankExamTitle() throws Exception { void addExamWithBlankExamDescription() throws Exception { // given createExampleExamType("Project"); - Map requestData = new HashMap<>(); - requestData.put("title", "Math exam"); -// no exam description - requestData.put("date", LocalDateTime.now().plusDays(1).toString()); - requestData.put("examGroups", "12K2, L04"); - requestData.put("examType", "Project"); - + ExamDto requestData = ExamDto.builder() + .title("Math exam") + .date(LocalDateTime.now().plusDays(1)) + .examType("Project") + .generalGroups(Set.of("12K2")) + .subgroups(Set.of("L04")) + .build(); // when MvcResult result = assertPostRequest(status().isCreated(), requestData); @@ -142,13 +154,13 @@ void addExamWithBlankExamDescription() throws Exception { void addExamWithBlankDate() throws Exception { // given createExampleExamType("Project"); - Map requestData = new HashMap<>(); - requestData.put("title", "Math exam"); - requestData.put("description", "first exam"); -// no date - requestData.put("examGroups", "12K2, L04"); - requestData.put("examType", "Project"); - + ExamDto requestData = ExamDto.builder() + .title("Math exam") + .description("first exam") + .examType("Project") + .generalGroups(Set.of("12K2")) + .subgroups(Set.of("L04")) + .build(); // when MvcResult result = assertPostRequest(status().isBadRequest(), requestData); @@ -160,30 +172,47 @@ void addExamWithBlankDate() throws Exception { void addExamWithBlankExamGroups() throws Exception { // given createExampleExamType("Project"); - Map requestData = new HashMap<>(); - requestData.put("title", "Math exam"); - requestData.put("description", "first exam"); - requestData.put("date", LocalDateTime.now().plusDays(1).toString()); -// no examGroups - requestData.put("examType", "Project"); + ExamDto requestData = ExamDto.builder() + .title("Math exam") + .description("first exam") + .date(LocalDateTime.now().plusDays(1)) + .examType("Project") + .build(); // when MvcResult result = assertPostRequest(status().isBadRequest(), requestData); // then - assertResponseMessage("examGroups : must not be blank", result); + assertResponseMessage("generalGroups : must not be empty", result); + } + + @Test + void addExamWithBlankGeneralGroups() throws Exception { +// TODO + } + + @Test + void addExamWithBlankSubgroups() throws Exception { +// TODO + } + + @Test + void addExamWithMultipleGeneralGroupsAndSubgroups() throws Exception { +// TODO } @Test void addExamWithNullExamTypes() throws Exception { // given - createExampleExamType("Project"); - Map requestData = new HashMap<>(); - requestData.put("title", "Math exam"); - requestData.put("description", "first exam"); - requestData.put("date", LocalDateTime.now().plusDays(1).toString()); - requestData.put("examGroups", "12K2, L04"); -// no examType + ExamDto requestData = ExamDto.builder() + .title("Math exam") + .description("first exam") + .date(LocalDateTime.now().plusDays(1)) + .examType(null) // brak typu egzaminu + .generalGroups(Set.of("12K2")) + .subgroups(Set.of("L04")) +// no examType + .build(); // when MvcResult result = assertPostRequest(status().isBadRequest(), requestData); @@ -196,13 +225,14 @@ void addExamWithNullExamTypes() throws Exception { void addExamWithNotFutureDate() throws Exception { // given createExampleExamType("Project"); - Map requestData = new HashMap<>(); - requestData.put("title", "Math exam"); - requestData.put("description", "first exam"); - requestData.put("date", LocalDateTime.now().minusDays(1).toString()); - requestData.put("examGroups", "12K2, L04"); - requestData.put("examType", "Project"); - + ExamDto requestData = ExamDto.builder() + .title("Math exam") + .description("first exam") + .date(LocalDateTime.now().minusDays(1)) + .examType("Project") + .generalGroups(Set.of("12K2")) + .subgroups(Set.of("L04")) + .build(); // when MvcResult result = assertPostRequest(status().isBadRequest(), requestData); @@ -214,12 +244,14 @@ void addExamWithNotFutureDate() throws Exception { void addExamWithEmptyStringExamTitle() throws Exception { // given createExampleExamType("Project"); - Map requestData = new HashMap<>(); - requestData.put("title", ""); - requestData.put("description", "first exam"); - requestData.put("date", LocalDateTime.now().plusDays(1).toString()); - requestData.put("examGroups", "12K2, L04"); - requestData.put("examType", "Project"); + ExamDto requestData = ExamDto.builder() + .title("") + .description("first exam") + .date(LocalDateTime.now().plusDays(1)) + .examType("Project") + .generalGroups(Set.of("12K2")) + .subgroups(Set.of("L04")) + .build(); // when MvcResult result = assertPostRequest(status().isBadRequest(), requestData); @@ -228,34 +260,18 @@ void addExamWithEmptyStringExamTitle() throws Exception { assertResponseMessage("title : must not be blank", result); } - @Test - void addExamWithEmptyStringExamGroups() throws Exception { -// given - createExampleExamType("Project"); - Map requestData = new HashMap<>(); - requestData.put("title", "Math exam"); - requestData.put("description", "first exam"); - requestData.put("date", LocalDateTime.now().plusDays(1).toString()); - requestData.put("examGroups", ""); - requestData.put("examType", "Project"); - -// when - MvcResult result = assertPostRequest(status().isBadRequest(), requestData); - -// then - assertResponseMessage("examGroups : must not be blank", result); - } - @Test void addExamWithTooLongExamTitle() throws Exception { // given createExampleExamType("Project"); - Map requestData = new HashMap<>(); - requestData.put("title", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); - requestData.put("description", "first exam"); - requestData.put("date", LocalDateTime.now().plusDays(1).toString()); - requestData.put("examGroups", "12K2, L04"); - requestData.put("examType", "Project"); + ExamDto requestData = ExamDto.builder() + .title("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") // 256 znaków + .description("first exam") + .date(LocalDateTime.now().plusDays(1)) + .examType("Project") + .generalGroups(Set.of("12K2")) + .subgroups(Set.of("L04")) + .build(); // when MvcResult result = assertPostRequest(status().isBadRequest(), requestData); @@ -268,12 +284,14 @@ void addExamWithTooLongExamTitle() throws Exception { void addExamWithTooLongDescription() throws Exception { // given createExampleExamType("Project"); - Map requestData = new HashMap<>(); - requestData.put("title", "Math exam"); - requestData.put("description", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); - requestData.put("date", LocalDateTime.now().plusDays(1).toString()); - requestData.put("examGroups", "12K2, L04"); - requestData.put("examType", "Project"); + ExamDto requestData = ExamDto.builder() + .title("Math exam") + .description("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") // 256 znaków + .date(LocalDateTime.now().plusDays(1)) + .examType("Project") + .generalGroups(Set.of("12K2")) + .subgroups(Set.of("L04")) + .build(); // when MvcResult result = assertPostRequest(status().isBadRequest(), requestData); @@ -282,34 +300,18 @@ void addExamWithTooLongDescription() throws Exception { assertResponseMessage("description : max size of field is 255", result); } - @Test - void addExamWithTooLongExamGroups() throws Exception { -// given - createExampleExamType("Project"); - Map requestData = new HashMap<>(); - requestData.put("title", "Math exam"); - requestData.put("description", "first exam"); - requestData.put("date", LocalDateTime.now().plusDays(1).toString()); - requestData.put("examGroups", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); - requestData.put("examType", "Project"); - -// when - MvcResult result = assertPostRequest(status().isBadRequest(), requestData); - -// then - assertResponseMessage("examGroups : max size of field is 255", result); - } - @Test void addExamWithNonExistingExamType() throws Exception { // given createExampleExamType("Project"); - Map requestData = new HashMap<>(); - requestData.put("title", "Math exam"); - requestData.put("description", "first exam"); - requestData.put("date", LocalDateTime.now().plusDays(1).toString()); - requestData.put("examGroups", "12K2, L04"); - requestData.put("examType", "NonExistingExamType"); + ExamDto requestData = ExamDto.builder() + .title("Math exam") + .description("first exam") + .date(LocalDateTime.now().plusDays(1)) + .examType("NonExistingExamType") + .generalGroups(Set.of("12K2")) + .subgroups(Set.of("L04")) + .build(); // when MvcResult result = assertPostRequest(status().isBadRequest(), requestData); @@ -323,6 +325,7 @@ void addExamWithNonExistingExamType() throws Exception { // @Test + @Transactional void modifyExamWithCorrectData() throws Exception { // given ExamType examType = createExampleExamType("Exam"); @@ -335,13 +338,23 @@ void modifyExamWithCorrectData() throws Exception { // then Exam responseExam = examRepository.findById(id).orElseThrow(); + + Set responseSubgroups = responseExam.getGroups().stream() + .map(StudentGroup::getName) + .collect(Collectors.toSet()); + Set responseGeneralGroups = responseSubgroups.stream() + .filter(g -> g.matches("^\\d.*")) + .collect(Collectors.toSet()); + responseSubgroups.removeAll(responseGeneralGroups); + assertEquals("Math exam", responseExam.getTitle()); assertEquals("first exam", responseExam.getDescription()); assertEquals( LocalDateTime.now().plusDays(1).truncatedTo(ChronoUnit.MINUTES), responseExam.getExamDate().truncatedTo(ChronoUnit.MINUTES) ); - assertEquals("12K2, L04", responseExam.getGroups()); + assertEquals(Set.of("12K"), responseGeneralGroups); + assertEquals(Set.of("L04"), responseSubgroups); } @Test @@ -415,7 +428,7 @@ void getExamByIdWithCorrectId() throws Exception { assertEquals(exam.getDescription(), responseNode.get("description").asText()); assertEquals( exam.getExamDate().truncatedTo(ChronoUnit.MINUTES), - LocalDateTime.parse(responseNode.get("date").textValue()).truncatedTo(ChronoUnit.MINUTES) + LocalDateTime.parse(responseNode.get("examDate").textValue()).truncatedTo(ChronoUnit.MINUTES) ); // assertEquals(exam.getGroups(), responseNode.get("examGroups").asText()); assertEquals(mapper.readTree(mapper.writeValueAsString(exam.getExamType())), responseNode.get("examType")); @@ -499,13 +512,14 @@ private ExamType createExampleExamType(String name) { * @return created Exam */ private Exam createExampleExam(ExamType type) { + List savedGroups = groupRepository.saveAll(Stream.of("12K2", "L04") + .map(g -> StudentGroup.builder().name(g).build()) + .collect(Collectors.toList())); return Exam.builder() .title("Exam") .description("Exam description") .examDate(LocalDateTime.now().plusDays(1)) - .groups(Stream.of("12K2", "L04") - .map(g -> StudentGroup.builder().name(g).build()) - .collect(Collectors.toSet())) + .groups(new HashSet<>(savedGroups)) .examType(type) .build(); } From 86d1dbf65b4e05ef9c86bd7fade351fd5be1f100 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Sat, 30 Aug 2025 16:12:01 +0200 Subject: [PATCH 069/116] add InvalidGroupIdentifierException handling --- .../examCalendar/ExamControllerAdvice.java | 6 +++ .../examCalendar/ExamControllerTest.java | 54 +++++++++++++++++-- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java b/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java index 5e3237f..828c2c7 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java @@ -2,6 +2,7 @@ import jakarta.validation.ConstraintViolation; import jakarta.validation.ConstraintViolationException; +import org.pkwmtt.exceptions.InvalidGroupIdentifierException; import org.pkwmtt.exceptions.dto.ErrorResponseDTO; import org.pkwmtt.exceptions.ExamTypeNotExistsException; import org.pkwmtt.exceptions.NoSuchElementWithProvidedIdException; @@ -45,5 +46,10 @@ public ResponseEntity handleConstraintViolationException(Const return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorResponseDTO(message)); } + @ExceptionHandler(InvalidGroupIdentifierException.class) + public ResponseEntity handleInvalidGroupIdentifierException(InvalidGroupIdentifierException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorResponseDTO(e.getMessage())); + } + } diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java index e8fc720..921da1d 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java @@ -188,17 +188,65 @@ void addExamWithBlankExamGroups() throws Exception { @Test void addExamWithBlankGeneralGroups() throws Exception { -// TODO +// given + createExampleExamType("Project"); + ExamDto requestData = ExamDto.builder() + .title("Math exam") + .description("first exam") + .date(LocalDateTime.now().plusDays(1)) + .examType("Project") +// null generalGroups + .subgroups(Set.of("L04")) + .build(); + +// when + MvcResult result = assertPostRequest(status().isBadRequest(), requestData); +// then + assertResponseMessage("generalGroups : must not be empty", result); } @Test + @Transactional void addExamWithBlankSubgroups() throws Exception { -// TODO +// given + createExampleExamType("Project"); + ExamDto requestData = ExamDto.builder() + .title("Math exam") + .description("first exam") + .date(LocalDateTime.now().plusDays(1)) + .examType("Project") + .generalGroups(Set.of("12K2")) +// null subgroups + .build(); + +// when + MvcResult result = assertPostRequest(status().isCreated(), requestData); +// then + String location = result.getResponse().getHeader("Location"); + @SuppressWarnings("DataFlowIssue") + int id = Integer.parseInt(location.substring(location.lastIndexOf("/") + 1)); + Exam examResponse = examRepository.findById(id).orElseThrow(); + + assertEquals("12K2", examResponse.getGroups().iterator().next().getName()); } @Test void addExamWithMultipleGeneralGroupsAndSubgroups() throws Exception { -// TODO + // given + createExampleExamType("Project"); + ExamDto requestData = ExamDto.builder() + .title("Math exam") + .description("first exam") + .date(LocalDateTime.now().plusDays(1)) + .examType("Project") + .generalGroups(Set.of("12K1","12K2")) + .subgroups(Set.of("L04")) + .build(); + +// when + MvcResult result = assertPostRequest(status().isBadRequest(), requestData); +// then + assertResponseMessage("Invalid group identifier: ambiguous general groups for subgroups", result); } @Test From 4003b73235d2e2f18f01ba166777887b87766fb9 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Sat, 30 Aug 2025 19:21:54 +0200 Subject: [PATCH 070/116] fix bug with swapping generalGroups with subgroups --- .../examCalendar/ExamControllerAdvice.java | 6 + .../org/pkwmtt/examCalendar/ExamService.java | 10 ++ .../examCalendar/ExamControllerTest.java | 113 ++++++++++++++++-- .../pkwmtt/examCalendar/ExamServiceTest.java | 33 +++-- 4 files changed, 143 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java b/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java index 828c2c7..060646f 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java @@ -3,6 +3,7 @@ import jakarta.validation.ConstraintViolation; import jakarta.validation.ConstraintViolationException; import org.pkwmtt.exceptions.InvalidGroupIdentifierException; +import org.pkwmtt.exceptions.SpecifiedGeneralGroupDoesntExistsException; import org.pkwmtt.exceptions.dto.ErrorResponseDTO; import org.pkwmtt.exceptions.ExamTypeNotExistsException; import org.pkwmtt.exceptions.NoSuchElementWithProvidedIdException; @@ -51,5 +52,10 @@ public ResponseEntity handleInvalidGroupIdentifierException(In return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorResponseDTO(e.getMessage())); } + @ExceptionHandler(SpecifiedGeneralGroupDoesntExistsException.class) + public ResponseEntity handleInvalidGroupIdentifierException(SpecifiedGeneralGroupDoesntExistsException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorResponseDTO(e.getMessage())); + } + } diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamService.java b/src/main/java/org/pkwmtt/examCalendar/ExamService.java index d92b193..b19448f 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamService.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamService.java @@ -78,6 +78,11 @@ public Exam getExamById(int id) { } public List getExamByGroups(Set generalGroups, Set subgroups) { +// verify generalGroups identifiers + generalGroups.forEach(group -> { + if(!Character.isDigit(group.charAt(0))) + throw new SpecifiedGeneralGroupDoesntExistsException(group); + }); // get exams for general groups List exams = new ArrayList<>(examRepository.findAllByGroups_NameIn(generalGroups)); // convert general group identifiers. e.g. 12K2 to 12K @@ -88,6 +93,11 @@ public List getExamByGroups(Set generalGroups, Set subgrou }).collect(Collectors.toSet()); // check if subgroups are provided if(subgroups != null && !subgroups.isEmpty()){ +// verify subgroups identifiers + subgroups.forEach(group -> { + if(!Character.isAlphabetic(group.charAt(0))) + throw new SpecifiedSubGroupDoesntExistsException(group); + }); // check if superior group identifies the groups unambiguously if(superiorGroups.size() != 1) throw new InvalidGroupIdentifierException("ambiguous superior group identifier for subgroups"); diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java index 921da1d..2bad079 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java @@ -92,8 +92,8 @@ void addExamWithCorrectData() throws Exception { .map(StudentGroup::getName) .collect(Collectors.toSet()); Set responseGeneralGroups = responseSubgroups.stream() - .filter(g -> g.matches("^\\d.*")) - .collect(Collectors.toSet()); + .filter(g -> g.matches("^\\d.*")) + .collect(Collectors.toSet()); responseSubgroups.removeAll(responseGeneralGroups); assertEquals(responseGeneralGroups, Set.of("12K")); @@ -239,7 +239,7 @@ void addExamWithMultipleGeneralGroupsAndSubgroups() throws Exception { .description("first exam") .date(LocalDateTime.now().plusDays(1)) .examType("Project") - .generalGroups(Set.of("12K1","12K2")) + .generalGroups(Set.of("12K1", "12K2")) .subgroups(Set.of("L04")) .build(); @@ -501,7 +501,49 @@ void getNonExistingExamById() throws Exception { // @Test - void getExams() { + void getExamsWithGeneralGroups() throws Exception { +// given + Exam exam1 = examRepository.save(createAndSaveExamWithTitleAndGroups("ex1", Set.of("12K2"))); + Exam exam2 = examRepository.save(createAndSaveExamWithTitleAndGroups("ex2", Set.of("12K2", "12K1"))); + Exam exam3 = examRepository.save(createAndSaveExamWithTitleAndGroups("ex3", Set.of("12A2"))); + Exam exam4 = examRepository.save(createAndSaveExamWithTitleAndGroups("ex4", Set.of("12K", "L04"))); + +// when + MvcResult result = assertGetByGroupsRequest(status().isOk(), Set.of("12K2")); + +// then + JsonNode responseArray = mapper.readTree(result.getResponse().getContentAsString()); + assertEquals(2, responseArray.size()); + assertTrue(responseArray.valueStream().anyMatch(e -> e.get("title").asText().equals(exam1.getTitle()))); + assertTrue(responseArray.valueStream().anyMatch(e -> e.get("title").asText().equals(exam2.getTitle()))); + assertTrue(responseArray.valueStream().noneMatch(e -> e.get("title").asText().equals(exam3.getTitle()))); + assertTrue(responseArray.valueStream().noneMatch(e -> e.get("title").asText().equals(exam4.getTitle()))); + } + + @Test + void getExamsWithSubgroups() throws Exception { +// given + Exam exam1 = examRepository.save(createAndSaveExamWithTitleAndGroups("ex1", Set.of("12K2"))); + Exam exam2 = examRepository.save(createAndSaveExamWithTitleAndGroups("ex2", Set.of("12K2", "11K2"))); + Exam exam3 = examRepository.save(createAndSaveExamWithTitleAndGroups("ex3", Set.of("12A2"))); + Exam exam4 = examRepository.save(createAndSaveExamWithTitleAndGroups("ex4", Set.of("12K", "L04"))); + Exam exam5 = examRepository.save(createAndSaveExamWithTitleAndGroups("ex5", Set.of("11K", "L04"))); + +// when + MvcResult result = assertGetByGroupsRequest(status().isOk(), Set.of("11K2"), Set.of("L04")); + +// then + JsonNode responseArray = mapper.readTree(result.getResponse().getContentAsString()); + assertEquals(2, responseArray.size()); + assertTrue(responseArray.valueStream().anyMatch(e -> e.get("title").asText().equals(exam2.getTitle()))); + assertTrue(responseArray.valueStream().anyMatch(e -> e.get("title").asText().equals(exam5.getTitle()))); + assertTrue(responseArray.valueStream().noneMatch(e -> e.get("title").asText().equals(exam1.getTitle()))); + assertTrue(responseArray.valueStream().noneMatch(e -> e.get("title").asText().equals(exam3.getTitle()))); + assertTrue(responseArray.valueStream().noneMatch(e -> e.get("title").asText().equals(exam4.getTitle()))); + } + + @Test + void getExamsMultipleGeneralGroupsAndSubgroups() { // TODO: test getExamsByGroups after implementing new version } @@ -545,6 +587,7 @@ void getExamTypesWhenExamTypesNotExists() throws Exception { /** * this method create examType object and add it to repository + * * @param name of new examType * @return created examType object */ @@ -556,6 +599,7 @@ private ExamType createExampleExamType(String name) { /** * this method don't add created Exam to repository, because in that case id of created Exam would be unreachable + * * @param type ExamType object which is required argument of Exam * @return created Exam */ @@ -572,6 +616,26 @@ private Exam createExampleExam(ExamType type) { .build(); } + private Exam createAndSaveExamWithTitleAndGroups(String title, Set groups) { + ExamType examType = examTypeRepository.findByName("Project") + .orElseGet(() -> createExampleExamType("Project")); + + Set groupsFromRepository = groupRepository.findAll().stream().map(StudentGroup::getName).collect(Collectors.toSet()); + List savedGroups = groupRepository.saveAll(groups.stream().filter(g -> !groupsFromRepository.contains(g)) + .map(g -> StudentGroup.builder().name(g).build()) + .collect(Collectors.toList())); + + Set groupsToSave = groupRepository.findAll().stream().filter(g -> groups.contains(g.getName())).collect(Collectors.toSet()); + + return Exam.builder() + .title(title) + .description("Exam description") + .examDate(LocalDateTime.now().plusDays(1)) + .groups(groupsToSave) + .examType(examType) + .build(); + } + /** * @param examTypeName name of type of exam as String * @return created ExamDto @@ -589,8 +653,9 @@ private ExamDto createExampleExamDto(String examTypeName) { /** * compare error message form response with expected value + * * @param expectedMessage full message that is expected in response - * @param result response generated by mockMvc.perform() or one of assert[httpMethod]Request() + * @param result response generated by mockMvc.perform() or one of assert[httpMethod]Request() * @throws Exception */ private void assertResponseMessage(String expectedMessage, MvcResult result) throws Exception { @@ -602,9 +667,10 @@ private void assertResponseMessage(String expectedMessage, MvcResult result) thr /** * method send POST request to ExamController with content as JSON attached to body and then check if response * code is the same as expected + * * @param expectedStatus status().[http response] (example: status().isCreated() ) - * @param content object that would be mapped to JSON by ObjectMapper and then attached to request - * it could be dto object or Map + * @param content object that would be mapped to JSON by ObjectMapper and then attached to request + * it could be dto object or Map * @return MvcResult object which could be used to capture response body * @throws Exception */ @@ -621,9 +687,10 @@ private MvcResult assertPostRequest(ResultMatcher expectedStatus, Object content /** * method send PUT request to ExamController with content as JSON attached to body and examId as pathID. * Then check if response code is the same as expected + * * @param expectedStatus status().[http response] (example: status().isNoContent() ) - * @param content object that would be mapped to JSON by ObjectMapper and then attached to request - * @param pathId id of resource that would be updated + * @param content object that would be mapped to JSON by ObjectMapper and then attached to request + * @param pathId id of resource that would be updated * @return MvcResult object which could be used to capture response body * @throws Exception */ @@ -640,8 +707,9 @@ private MvcResult assertPutRequest(ResultMatcher expectedStatus, Object content, /** * method send DELETE request to ExamController with examId as pathID. * Then check if response code is the same as expected + * * @param expectedStatus status().[http response] (example: status().isNoContent() ) - * @param pathId id of resource that would be deleted + * @param pathId id of resource that would be deleted * @return MvcResult object which could be used to capture response body * @throws Exception */ @@ -657,8 +725,9 @@ private MvcResult assertDeleteRequest(ResultMatcher expectedStatus, int pathId) /** * method send GET request to ExamController at /pkwmtt/api/v1/exams/{id} URI with examId as pathID. * Then check if response code is the same as expected + * * @param expectedStatus status().[http response] (example: status().isOk() ) - * @param pathId id of resource that would be returned + * @param pathId id of resource that would be returned * @return MvcResult object which could be used to capture response body * @throws Exception */ @@ -671,9 +740,31 @@ private MvcResult assertGetByIdRequest(ResultMatcher expectedStatus, int pathId) .andReturn(); } + private MvcResult assertGetByGroupsRequest(ResultMatcher expectedStatus, Set generalGroups) throws Exception { + return mockMvc.perform(MockMvcRequestBuilders + .get("/pkwmtt/api/v1/exams/by-groups") + .param("generalGroups", generalGroups.toArray(new String[0])) + .contentType("application/json") + ).andDo(print()) + .andExpect(expectedStatus) + .andReturn(); + } + + private MvcResult assertGetByGroupsRequest(ResultMatcher expectedStatus, Set generalGroups, Set subgroups) throws Exception { + return mockMvc.perform(MockMvcRequestBuilders + .get("/pkwmtt/api/v1/exams/by-groups") + .param("generalGroups", generalGroups.toArray(new String[0])) + .param("subgroups", subgroups.toArray(new String[0])) + .contentType("application/json") + ).andDo(print()) + .andExpect(expectedStatus) + .andReturn(); + } + /** * method send GET request to ExamController at /pkwmtt/api/v1/exams/exam-types URI. * Then check if response code is the same as expected + * * @param expectedStatus expectedStatus status().[http response] (example: status().isOk() ) * @return MvcResult object which could be used to capture response body * @throws Exception diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java index ecc6649..03ca988 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java @@ -16,9 +16,7 @@ import org.pkwmtt.examCalendar.repository.ExamRepository; import org.pkwmtt.examCalendar.repository.ExamTypeRepository; import org.pkwmtt.examCalendar.repository.GroupRepository; -import org.pkwmtt.exceptions.InvalidGroupIdentifierException; -import org.pkwmtt.exceptions.ServiceNotAvailableException; -import org.pkwmtt.exceptions.WebPageContentNotAvailableException; +import org.pkwmtt.exceptions.*; import org.pkwmtt.timetable.TimetableService; import java.time.LocalDateTime; @@ -627,7 +625,6 @@ void getExamsForNormalGroups() { // given Set generalGroups = Set.of("12K2"); Set subgroups = Set.of("L04", "K04", "P04"); - // when examService.getExamByGroups(generalGroups, subgroups); // then @@ -640,7 +637,6 @@ void getExamsForGroupWithoutDigitAsFirstCharacter() { // given Set generalGroups = Set.of("1Er"); Set subgroups = Set.of("L01", "K01", "P01"); - // when examService.getExamByGroups(generalGroups, subgroups); // then @@ -653,7 +649,6 @@ void getExamsWithEmptySubgroups() { // given Set generalGroups = Set.of("12K2"); Set subgroups = Set.of(); - // when examService.getExamByGroups(generalGroups, subgroups); // then @@ -666,7 +661,6 @@ void getExamsWithBlankSubgroups() { // given Set generalGroups = Set.of("12K2"); Set subgroups = null; - // when examService.getExamByGroups(generalGroups, subgroups); // then @@ -679,7 +673,6 @@ void shouldNotThrowWhenGroupsAreFromTheSameYearOfStudy() { // given Set generalGroups = Set.of("12K1", "12K2"); Set subgroups = Set.of("L01", "K01", "P01"); - // when examService.getExamByGroups(generalGroups, subgroups); // then @@ -687,6 +680,30 @@ void shouldNotThrowWhenGroupsAreFromTheSameYearOfStudy() { verify(examRepository, times(1)).findAllBySubgroupsOfGeneralGroup("12K", subgroups); } + @Test + void shouldThrowWhenSubgroupsAreSwappedWithGeneralGroups() { +// given + Set generalGroups = new HashSet<>(Set.of("L01", "K01", "P01")); + Set subgroups = new HashSet<>( Set.of("12K1")); +// when then + assertThrows( + SpecifiedGeneralGroupDoesntExistsException.class, + () -> examService.getExamByGroups(generalGroups, subgroups) + ); + } + + @Test + void shouldThrowWhenSubgroupsAreTheGeneralGroups() { +// given + Set generalGroups = new HashSet<>(Set.of("12K1")); + Set subgroups = new HashSet<>( Set.of("12K1", "12K2", "12K3")); +// when, then + assertThrows( + SpecifiedSubGroupDoesntExistsException.class, + () -> examService.getExamByGroups(generalGroups, subgroups) + ); + } + @Test void shouldThrowWhenGeneralGroupsAreFromDifferentYearOfStudy() { // given From 67a222afb44e475b8adfc5f53c087908a3006bb2 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Sat, 30 Aug 2025 19:33:51 +0200 Subject: [PATCH 071/116] complete integration tests for ExamCalendar --- .../examCalendar/ExamControllerAdvice.java | 12 +++++---- .../examCalendar/ExamControllerTest.java | 25 ++++++++++++++++--- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java b/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java index 060646f..03421e4 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java @@ -2,11 +2,8 @@ import jakarta.validation.ConstraintViolation; import jakarta.validation.ConstraintViolationException; -import org.pkwmtt.exceptions.InvalidGroupIdentifierException; -import org.pkwmtt.exceptions.SpecifiedGeneralGroupDoesntExistsException; +import org.pkwmtt.exceptions.*; import org.pkwmtt.exceptions.dto.ErrorResponseDTO; -import org.pkwmtt.exceptions.ExamTypeNotExistsException; -import org.pkwmtt.exceptions.NoSuchElementWithProvidedIdException; import org.pkwmtt.timetable.TimetableController; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -19,7 +16,7 @@ @RestControllerAdvice(assignableTypes = {ExamController.class}) public class ExamControllerAdvice { -// TODO: handle or remove UnsupportedCountOfArgumentsException +//UnsupportedCountOfArgumentsException isn't handled but probably would never be thrown @ExceptionHandler(NoSuchElementWithProvidedIdException.class) public ResponseEntity handleNoSuchElementWithProvidedIdException(NoSuchElementWithProvidedIdException e) { @@ -57,5 +54,10 @@ public ResponseEntity handleInvalidGroupIdentifierException(Sp return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorResponseDTO(e.getMessage())); } + @ExceptionHandler(SpecifiedSubGroupDoesntExistsException.class) + public ResponseEntity handleInvalidGroupIdentifierException(SpecifiedSubGroupDoesntExistsException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorResponseDTO(e.getMessage())); + } + } diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java index 2bad079..b139bb1 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java @@ -530,7 +530,7 @@ void getExamsWithSubgroups() throws Exception { Exam exam5 = examRepository.save(createAndSaveExamWithTitleAndGroups("ex5", Set.of("11K", "L04"))); // when - MvcResult result = assertGetByGroupsRequest(status().isOk(), Set.of("11K2"), Set.of("L04")); + MvcResult result = assertGetByGroupsRequest(status().isOk(), Set.of("11K2"), Set.of("L04","P04", "K04")); // then JsonNode responseArray = mapper.readTree(result.getResponse().getContentAsString()); @@ -543,8 +543,27 @@ void getExamsWithSubgroups() throws Exception { } @Test - void getExamsMultipleGeneralGroupsAndSubgroups() { -// TODO: test getExamsByGroups after implementing new version + void getExamsMultipleGeneralGroupsAndSubgroups() throws Exception { + // when + MvcResult result = assertGetByGroupsRequest(status().isBadRequest(), Set.of("11K2", "12A1"), Set.of("L04")); + // then + assertResponseMessage("Invalid group identifier: ambiguous superior group identifier for subgroups",result); + } + + @Test + void getExamsWithSwappedGroupNames() throws Exception { + // when + MvcResult result = assertGetByGroupsRequest(status().isBadRequest(), Set.of("K04"), Set.of("11K2", "12A1")); + // then + assertResponseMessage("Specified general group [K04] doesn't exists",result); + } + + @Test + void getExamsWithInvalidSubgroup() throws Exception { + // when + MvcResult result = assertGetByGroupsRequest(status().isBadRequest(), Set.of("12K1,", "12K2"), Set.of("11K2")); + // then + assertResponseMessage("Specified sub group [11K2] doesn't exists",result); } // From 0f593a5368301f278e6f89d88a4f24b7d3e0b9df Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Sat, 30 Aug 2025 19:44:47 +0200 Subject: [PATCH 072/116] add generalGroups format verification in addExam --- logs/app.log | 116 ++++++++++++++++++ .../examCalendar/ExamControllerAdvice.java | 2 - .../org/pkwmtt/examCalendar/ExamService.java | 25 ++-- .../examCalendar/ExamControllerTest.java | 8 +- .../pkwmtt/examCalendar/ExamServiceTest.java | 4 + 5 files changed, 138 insertions(+), 17 deletions(-) diff --git a/logs/app.log b/logs/app.log index 9ce0044..5b629ec 100644 --- a/logs/app.log +++ b/logs/app.log @@ -4941,3 +4941,119 @@ insert into "groups" (name,group_id) values (?,default) [42103-232] 2025-08-27 20:56:14 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties 2025-08-27 20:56:52 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties 2025-08-27 20:56:53 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-27 21:34:42 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-27 21:34:44 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-29 23:03:31 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-29 23:03:33 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-29 23:06:42 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-29 23:06:44 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-29 23:07:59 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-29 23:08:01 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-29 23:43:26 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-29 23:49:50 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-29 23:50:30 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-29 23:53:50 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-29 23:57:59 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 00:58:59 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 01:03:09 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 01:07:16 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 14:53:20 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 14:56:00 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 15:08:58 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 15:11:10 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 15:12:05 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 15:18:21 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 15:19:58 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 15:20:51 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 15:21:58 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 15:22:58 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 15:24:29 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 15:35:16 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 15:36:07 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 15:38:08 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 15:45:03 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 15:45:33 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 15:48:28 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 15:48:51 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 15:48:52 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Naruszenie ograniczenia Klucza Głównego lub Indeksu Unikalnego: "PUBLIC.CONSTRAINT_INDEX_B ON PUBLIC.student_groups(name NULLS FIRST) VALUES ( /* 4 */ '12K2' )" +Unique index or primary key violation: "PUBLIC.CONSTRAINT_INDEX_B ON PUBLIC.student_groups(name NULLS FIRST) VALUES ( /* 4 */ '12K2' )"; SQL statement: +insert into student_groups (name,group_id) values (?,default) [23505-232] +2025-08-30 15:48:52 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Naruszenie ograniczenia Klucza Głównego lub Indeksu Unikalnego: "PUBLIC.CONSTRAINT_INDEX_B ON PUBLIC.student_groups(name NULLS FIRST) VALUES ( /* 4 */ '12K2' )" +Unique index or primary key violation: "PUBLIC.CONSTRAINT_INDEX_B ON PUBLIC.student_groups(name NULLS FIRST) VALUES ( /* 4 */ '12K2' )"; SQL statement: +insert into student_groups (name,group_id) values (?,default) [23505-232] +2025-08-30 15:48:52 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Naruszenie ograniczenia Klucza Głównego lub Indeksu Unikalnego: "PUBLIC.CONSTRAINT_INDEX_B ON PUBLIC.student_groups(name NULLS FIRST) VALUES ( /* 4 */ '12K2' )" +Unique index or primary key violation: "PUBLIC.CONSTRAINT_INDEX_B ON PUBLIC.student_groups(name NULLS FIRST) VALUES ( /* 4 */ '12K2' )"; SQL statement: +insert into student_groups (name,group_id) values (?,default) [23505-232] +2025-08-30 15:48:53 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Naruszenie ograniczenia Klucza Głównego lub Indeksu Unikalnego: "PUBLIC.CONSTRAINT_INDEX_B ON PUBLIC.student_groups(name NULLS FIRST) VALUES ( /* 4 */ '12K2' )" +Unique index or primary key violation: "PUBLIC.CONSTRAINT_INDEX_B ON PUBLIC.student_groups(name NULLS FIRST) VALUES ( /* 4 */ '12K2' )"; SQL statement: +insert into student_groups (name,group_id) values (?,default) [23505-232] +2025-08-30 15:50:02 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 15:50:52 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 15:52:36 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 15:52:58 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 15:53:00 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 15:53:01 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 16:00:54 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 16:07:14 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 16:07:38 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 16:08:30 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 16:09:03 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 16:11:39 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 16:32:46 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 16:32:46 ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Naruszenie ograniczenia Klucza Głównego lub Indeksu Unikalnego: "PUBLIC.CONSTRAINT_INDEX_B ON PUBLIC.student_groups(name NULLS FIRST) VALUES ( /* 1 */ '12K2' )" +Unique index or primary key violation: "PUBLIC.CONSTRAINT_INDEX_B ON PUBLIC.student_groups(name NULLS FIRST) VALUES ( /* 1 */ '12K2' )"; SQL statement: +insert into student_groups (name,group_id) values (?,default) [23505-232] +2025-08-30 16:56:30 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 17:00:44 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 17:02:28 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 17:03:18 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 17:47:32 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 17:49:15 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 17:49:40 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 17:50:03 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 17:50:54 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 17:52:23 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 17:55:32 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 17:56:04 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 17:56:24 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 17:57:07 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 17:57:41 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 17:58:08 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 18:00:12 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 18:01:17 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 18:04:11 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 18:05:09 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 18:05:33 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 18:06:50 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 18:09:58 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 18:12:59 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 18:14:16 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 18:16:24 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 18:17:01 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 18:17:47 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 18:46:43 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 18:47:01 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 18:48:52 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 18:49:20 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 18:50:03 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 18:57:08 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 18:58:17 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 18:59:03 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 18:59:33 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 19:00:05 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 19:20:40 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 19:24:11 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 19:24:37 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 19:25:01 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 19:27:10 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 19:29:04 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 19:29:22 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 19:32:17 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 19:32:19 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 19:32:21 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 19:43:42 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 19:43:45 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 19:43:46 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 19:45:42 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 19:45:44 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties +2025-08-30 19:45:46 ERROR org.pkwmtt.config.StartupConfig - !Couldn't log the server base url. Check properties in application.properties diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java b/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java index 03421e4..1dd4005 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java @@ -1,10 +1,8 @@ package org.pkwmtt.examCalendar; -import jakarta.validation.ConstraintViolation; import jakarta.validation.ConstraintViolationException; import org.pkwmtt.exceptions.*; import org.pkwmtt.exceptions.dto.ErrorResponseDTO; -import org.pkwmtt.timetable.TimetableController; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamService.java b/src/main/java/org/pkwmtt/examCalendar/ExamService.java index b19448f..cd084aa 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamService.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamService.java @@ -79,10 +79,7 @@ public Exam getExamById(int id) { public List getExamByGroups(Set generalGroups, Set subgroups) { // verify generalGroups identifiers - generalGroups.forEach(group -> { - if(!Character.isDigit(group.charAt(0))) - throw new SpecifiedGeneralGroupDoesntExistsException(group); - }); + verifyGeneralGroupsFormat(generalGroups); // get exams for general groups List exams = new ArrayList<>(examRepository.findAllByGroups_NameIn(generalGroups)); // convert general group identifiers. e.g. 12K2 to 12K @@ -94,10 +91,7 @@ public List getExamByGroups(Set generalGroups, Set subgrou // check if subgroups are provided if(subgroups != null && !subgroups.isEmpty()){ // verify subgroups identifiers - subgroups.forEach(group -> { - if(!Character.isAlphabetic(group.charAt(0))) - throw new SpecifiedSubGroupDoesntExistsException(group); - }); + verifySubgroupsFormat(subgroups); // check if superior group identifies the groups unambiguously if(superiorGroups.size() != 1) throw new InvalidGroupIdentifierException("ambiguous superior group identifier for subgroups"); @@ -178,6 +172,7 @@ else if(generalGroups.isEmpty()) * @throws WebPageContentNotAvailableException when verification not succeeded */ private Set verifyUsingRepository(Set groups) throws WebPageContentNotAvailableException { + verifyGeneralGroupsFormat(groups); Set groupsFromRepository = groupRepository.findAllByNameIn(groups).stream() .map(StudentGroup::getName) .collect(Collectors.toSet() @@ -210,4 +205,18 @@ private Set saveNewStudentGroups(Set groups) { existingGroups.addAll(savedGroups); return existingGroups; } + + private static void verifyGeneralGroupsFormat(Set generalGroups) throws SpecifiedGeneralGroupDoesntExistsException{ + generalGroups.forEach(group -> { + if(!Character.isDigit(group.charAt(0))) + throw new SpecifiedGeneralGroupDoesntExistsException(group); + }); + } + + private static void verifySubgroupsFormat(Set subgroups) { + subgroups.forEach(group -> { + if(!Character.isAlphabetic(group.charAt(0))) + throw new SpecifiedSubGroupDoesntExistsException(group); + }); + } } diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java index b139bb1..b8a05e3 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java @@ -640,7 +640,7 @@ private Exam createAndSaveExamWithTitleAndGroups(String title, Set group .orElseGet(() -> createExampleExamType("Project")); Set groupsFromRepository = groupRepository.findAll().stream().map(StudentGroup::getName).collect(Collectors.toSet()); - List savedGroups = groupRepository.saveAll(groups.stream().filter(g -> !groupsFromRepository.contains(g)) + groupRepository.saveAll(groups.stream().filter(g -> !groupsFromRepository.contains(g)) .map(g -> StudentGroup.builder().name(g).build()) .collect(Collectors.toList())); @@ -675,7 +675,6 @@ private ExamDto createExampleExamDto(String examTypeName) { * * @param expectedMessage full message that is expected in response * @param result response generated by mockMvc.perform() or one of assert[httpMethod]Request() - * @throws Exception */ private void assertResponseMessage(String expectedMessage, MvcResult result) throws Exception { JsonNode jsonResponse = mapper.readTree(result.getResponse().getContentAsString()); @@ -691,7 +690,6 @@ private void assertResponseMessage(String expectedMessage, MvcResult result) thr * @param content object that would be mapped to JSON by ObjectMapper and then attached to request * it could be dto object or Map * @return MvcResult object which could be used to capture response body - * @throws Exception */ private MvcResult assertPostRequest(ResultMatcher expectedStatus, Object content) throws Exception { return mockMvc.perform(MockMvcRequestBuilders @@ -711,7 +709,6 @@ private MvcResult assertPostRequest(ResultMatcher expectedStatus, Object content * @param content object that would be mapped to JSON by ObjectMapper and then attached to request * @param pathId id of resource that would be updated * @return MvcResult object which could be used to capture response body - * @throws Exception */ private MvcResult assertPutRequest(ResultMatcher expectedStatus, Object content, int pathId) throws Exception { return mockMvc.perform(MockMvcRequestBuilders @@ -730,7 +727,6 @@ private MvcResult assertPutRequest(ResultMatcher expectedStatus, Object content, * @param expectedStatus status().[http response] (example: status().isNoContent() ) * @param pathId id of resource that would be deleted * @return MvcResult object which could be used to capture response body - * @throws Exception */ private MvcResult assertDeleteRequest(ResultMatcher expectedStatus, int pathId) throws Exception { return mockMvc.perform(MockMvcRequestBuilders @@ -748,7 +744,6 @@ private MvcResult assertDeleteRequest(ResultMatcher expectedStatus, int pathId) * @param expectedStatus status().[http response] (example: status().isOk() ) * @param pathId id of resource that would be returned * @return MvcResult object which could be used to capture response body - * @throws Exception */ private MvcResult assertGetByIdRequest(ResultMatcher expectedStatus, int pathId) throws Exception { return mockMvc.perform(MockMvcRequestBuilders @@ -786,7 +781,6 @@ private MvcResult assertGetByGroupsRequest(ResultMatcher expectedStatus, Set> groupCaptor = ArgumentCaptor.forClass(List.class); verify(groupRepository, times(1)).saveAll(groupCaptor.capture()); assertEquals("12K2", groupCaptor.getValue().getFirst().getName()); @@ -129,6 +130,7 @@ void addExamForMultipleGeneralGroupsWithEmptySubgroups() { verify(timetableService, times(1)).getGeneralGroupList(); verify(groupRepository, times(1)).findAllByNameIn(generalGroups); + @SuppressWarnings("unchecked") ArgumentCaptor> groupCaptor = ArgumentCaptor.forClass(List.class); verify(groupRepository, times(1)).saveAll(groupCaptor.capture()); Set capturedGroups = groupCaptor.getValue().stream().map(StudentGroup::getName).collect(Collectors.toSet()); @@ -727,6 +729,7 @@ private static List buildExampleStudentGroupList(Set group ).collect(Collectors.toList()); } + private static Exam buildExamWithIdAndGroups(int id, List groups) { return Exam.builder() .examId(id) @@ -787,6 +790,7 @@ private void testExamServiceForSubgroups(Set generalGroups, Set verify(timetableService, times(1)).getGeneralGroupList(); verify(groupRepository, times(1)).findAllByNameIn(combinedGroups); + @SuppressWarnings("unchecked") ArgumentCaptor> groupCaptor = ArgumentCaptor.forClass(List.class); verify(groupRepository, times(1)).saveAll(groupCaptor.capture()); Set capturedGroups = groupCaptor.getValue().stream().map(StudentGroup::getName).collect(Collectors.toSet()); From cd229ee8eb4e8df4e20da9fa97fe20f52b13caba Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Mon, 1 Sep 2025 13:19:55 +0200 Subject: [PATCH 073/116] refactor ExamControllerAdvice --- .../examCalendar/ExamControllerAdvice.java | 29 +++++-------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java b/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java index 1dd4005..20a9e8b 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java @@ -14,15 +14,19 @@ @RestControllerAdvice(assignableTypes = {ExamController.class}) public class ExamControllerAdvice { -//UnsupportedCountOfArgumentsException isn't handled but probably would never be thrown - @ExceptionHandler(NoSuchElementWithProvidedIdException.class) public ResponseEntity handleNoSuchElementWithProvidedIdException(NoSuchElementWithProvidedIdException e) { return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponseDTO(e.getMessage())); } - @ExceptionHandler(ExamTypeNotExistsException.class) - public ResponseEntity handleExamTypeNotExistsException(ExamTypeNotExistsException e) { + @ExceptionHandler({ + ExamTypeNotExistsException.class, + InvalidGroupIdentifierException.class, + SpecifiedGeneralGroupDoesntExistsException.class, + SpecifiedSubGroupDoesntExistsException.class, + UnsupportedCountOfArgumentsException.class + }) + public ResponseEntity handleBadRequest(RuntimeException e) { return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorResponseDTO(e.getMessage())); } @@ -41,21 +45,4 @@ public ResponseEntity handleConstraintViolationException(Const .collect(Collectors.joining(", ")); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorResponseDTO(message)); } - - @ExceptionHandler(InvalidGroupIdentifierException.class) - public ResponseEntity handleInvalidGroupIdentifierException(InvalidGroupIdentifierException e) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorResponseDTO(e.getMessage())); - } - - @ExceptionHandler(SpecifiedGeneralGroupDoesntExistsException.class) - public ResponseEntity handleInvalidGroupIdentifierException(SpecifiedGeneralGroupDoesntExistsException e) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorResponseDTO(e.getMessage())); - } - - @ExceptionHandler(SpecifiedSubGroupDoesntExistsException.class) - public ResponseEntity handleInvalidGroupIdentifierException(SpecifiedSubGroupDoesntExistsException e) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorResponseDTO(e.getMessage())); - } - - } From 8044f28cf94545b59c6a1c16467069b208a60154 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Mon, 1 Sep 2025 13:46:43 +0200 Subject: [PATCH 074/116] change verify groups to use regex --- src/main/java/org/pkwmtt/examCalendar/ExamService.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamService.java b/src/main/java/org/pkwmtt/examCalendar/ExamService.java index cd084aa..058edca 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamService.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamService.java @@ -121,7 +121,7 @@ private Set verifyAndUpdateExamGroups(ExamDto examDto) { try { generalGroupsFromRepository = new HashSet<>(timetableService.getGeneralGroupList()); } catch (WebPageContentNotAvailableException e) { - generalGroupsFromRepository = verifyUsingRepository(generalGroups); + generalGroupsFromRepository = verifyGroupsUsingRepository(generalGroups); } // verify generalGroups using timetable service if (!generalGroupsFromRepository.containsAll(generalGroups)) { @@ -171,7 +171,7 @@ else if(generalGroups.isEmpty()) * @return set of groups (String) when verification succeeded * @throws WebPageContentNotAvailableException when verification not succeeded */ - private Set verifyUsingRepository(Set groups) throws WebPageContentNotAvailableException { + private Set verifyGroupsUsingRepository(Set groups) throws WebPageContentNotAvailableException { verifyGeneralGroupsFormat(groups); Set groupsFromRepository = groupRepository.findAllByNameIn(groups).stream() .map(StudentGroup::getName) @@ -208,14 +208,14 @@ private Set saveNewStudentGroups(Set groups) { private static void verifyGeneralGroupsFormat(Set generalGroups) throws SpecifiedGeneralGroupDoesntExistsException{ generalGroups.forEach(group -> { - if(!Character.isDigit(group.charAt(0))) + if (!group.matches("^\\d.*")) throw new SpecifiedGeneralGroupDoesntExistsException(group); }); } private static void verifySubgroupsFormat(Set subgroups) { subgroups.forEach(group -> { - if(!Character.isAlphabetic(group.charAt(0))) + if(!group.matches("^[A-Z].*")) throw new SpecifiedSubGroupDoesntExistsException(group); }); } From f481029273851772e2466ad3374d13d053a2b45b Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Mon, 1 Sep 2025 14:44:11 +0200 Subject: [PATCH 075/116] allow using whole year of study as generalGroup identifier --- .../org/pkwmtt/examCalendar/ExamService.java | 19 +++++++++------ .../examCalendar/mapper/ExamDtoMapper.java | 2 +- .../repository/ExamRepository.java | 5 +--- .../examCalendar/ExamControllerTest.java | 24 +++++++++++++++++++ .../repository/ExamRepositoryTest.java | 19 ++++++++------- 5 files changed, 48 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamService.java b/src/main/java/org/pkwmtt/examCalendar/ExamService.java index 058edca..58621b0 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamService.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamService.java @@ -77,11 +77,16 @@ public Exam getExamById(int id) { return examRepository.findById(id).orElseThrow(() -> new NoSuchElementWithProvidedIdException(id)); } - public List getExamByGroups(Set generalGroups, Set subgroups) { + public Set getExamByGroups(Set generalGroups, Set subgroups) { // verify generalGroups identifiers verifyGeneralGroupsFormat(generalGroups); // get exams for general groups - List exams = new ArrayList<>(examRepository.findAllByGroups_NameIn(generalGroups)); + Set exams = new HashSet<>(examRepository.findAllByGroups_NameIn(generalGroups)); + exams = exams.stream() + .filter(exam -> exam.getGroups().stream() + .allMatch(group -> group.getName().matches("^\\d.*"))) + .collect(Collectors.toSet()); + // convert general group identifiers. e.g. 12K2 to 12K Set superiorGroups = generalGroups.stream().map(g -> { if (Character.isDigit(g.charAt(g.length() - 1))) @@ -89,11 +94,11 @@ public List getExamByGroups(Set generalGroups, Set subgrou return g; }).collect(Collectors.toSet()); // check if subgroups are provided - if(subgroups != null && !subgroups.isEmpty()){ + if (subgroups != null && !subgroups.isEmpty()) { // verify subgroups identifiers verifySubgroupsFormat(subgroups); // check if superior group identifies the groups unambiguously - if(superiorGroups.size() != 1) + if (superiorGroups.size() != 1) throw new InvalidGroupIdentifierException("ambiguous superior group identifier for subgroups"); exams.addAll(examRepository.findAllBySubgroupsOfGeneralGroup(superiorGroups.iterator().next(), subgroups)); } @@ -160,7 +165,7 @@ private Set verifyAndUpdateExamGroups(ExamDto examDto) { } // only one general group could be assigned to subgroups (when there are more than 1 general group and // more than 0 subgroups) - else if(generalGroups.isEmpty()) + else if (generalGroups.isEmpty()) throw new InvalidGroupIdentifierException("general group is missing"); else throw new InvalidGroupIdentifierException("ambiguous general groups for subgroups"); @@ -206,7 +211,7 @@ private Set saveNewStudentGroups(Set groups) { return existingGroups; } - private static void verifyGeneralGroupsFormat(Set generalGroups) throws SpecifiedGeneralGroupDoesntExistsException{ + private static void verifyGeneralGroupsFormat(Set generalGroups) throws SpecifiedGeneralGroupDoesntExistsException { generalGroups.forEach(group -> { if (!group.matches("^\\d.*")) throw new SpecifiedGeneralGroupDoesntExistsException(group); @@ -215,7 +220,7 @@ private static void verifyGeneralGroupsFormat(Set generalGroups) throws private static void verifySubgroupsFormat(Set subgroups) { subgroups.forEach(group -> { - if(!group.matches("^[A-Z].*")) + if (!group.matches("^[A-Z].*")) throw new SpecifiedSubGroupDoesntExistsException(group); }); } diff --git a/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoMapper.java b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoMapper.java index 33aeaee..ea79230 100644 --- a/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoMapper.java +++ b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoMapper.java @@ -54,7 +54,7 @@ public static Exam mapToExistingExam(ExamDto examDto, Set groups, .build(); } - public static List mapToExamDto(List exams) { + public static List mapToExamDto(Set exams) { return exams.stream().map(ExamDtoMapper::mapToExamDto).collect(Collectors.toList()); } diff --git a/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java index 0eca828..c87b283 100644 --- a/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java +++ b/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java @@ -1,13 +1,10 @@ package org.pkwmtt.examCalendar.repository; import org.pkwmtt.examCalendar.entity.Exam; -import org.pkwmtt.examCalendar.entity.StudentGroup; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import org.springframework.security.core.parameters.P; -import java.util.Collection; import java.util.List; import java.util.Set; @@ -30,5 +27,5 @@ public interface ExamRepository extends JpaRepository { JOIN FETCH e.groups g2 WHERE g1.name = :general AND g2.name IN :sub """) - List findAllBySubgroupsOfGeneralGroup(@Param("general") String generalGroup, @Param("sub") Set subgroup); + Set findAllBySubgroupsOfGeneralGroup(@Param("general") String generalGroup, @Param("sub") Set subgroup); } \ No newline at end of file diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java index b8a05e3..6e3c72b 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java @@ -542,6 +542,30 @@ void getExamsWithSubgroups() throws Exception { assertTrue(responseArray.valueStream().noneMatch(e -> e.get("title").asText().equals(exam4.getTitle()))); } + @Test + void getExamsWithSubgroupsUsingWholeYearIdentifier() throws Exception { +// given + Exam exam1 = examRepository.save(createAndSaveExamWithTitleAndGroups("ex1", Set.of("12K2"))); + Exam exam2 = examRepository.save(createAndSaveExamWithTitleAndGroups("ex2", Set.of("12K2", "11K2"))); + Exam exam3 = examRepository.save(createAndSaveExamWithTitleAndGroups("ex3", Set.of("12A2"))); + Exam exam4 = examRepository.save(createAndSaveExamWithTitleAndGroups("ex4", Set.of("12K", "L04"))); + Exam exam5 = examRepository.save(createAndSaveExamWithTitleAndGroups("ex5", Set.of("11K", "L04"))); + Exam exam6 = examRepository.save(createAndSaveExamWithTitleAndGroups("ex6", Set.of("12K", "L04", "P04"))); + +// when + MvcResult result = assertGetByGroupsRequest(status().isOk(), Set.of("12K"), Set.of("L04", "K04")); + +// then + JsonNode responseArray = mapper.readTree(result.getResponse().getContentAsString()); + assertEquals(2, responseArray.size()); + assertTrue(responseArray.valueStream().anyMatch(e -> e.get("title").asText().equals(exam4.getTitle()))); + assertTrue(responseArray.valueStream().anyMatch(e -> e.get("title").asText().equals(exam6.getTitle()))); + assertTrue(responseArray.valueStream().noneMatch(e -> e.get("title").asText().equals(exam1.getTitle()))); + assertTrue(responseArray.valueStream().noneMatch(e -> e.get("title").asText().equals(exam2.getTitle()))); + assertTrue(responseArray.valueStream().noneMatch(e -> e.get("title").asText().equals(exam3.getTitle()))); + assertTrue(responseArray.valueStream().noneMatch(e -> e.get("title").asText().equals(exam5.getTitle()))); + } + @Test void getExamsMultipleGeneralGroupsAndSubgroups() throws Exception { // when diff --git a/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java b/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java index 68c6ead..651d24d 100644 --- a/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java @@ -13,6 +13,7 @@ import java.time.LocalDateTime; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -126,7 +127,7 @@ void setUp() { @Test void shouldReturnExamsWhenNotAllSubgroupsFromRepositoryMatched() { - List exams = examRepository.findAllBySubgroupsOfGeneralGroup("12K", Set.of("L04")); + Set exams = examRepository.findAllBySubgroupsOfGeneralGroup("12K", Set.of("L04")); assertEquals(2, exams.size()); List examTitles = exams.stream().map(Exam::getTitle).sorted().toList(); assertEquals("small Group Exam 1", examTitles.get(0)); @@ -135,7 +136,7 @@ void shouldReturnExamsWhenNotAllSubgroupsFromRepositoryMatched() { @Test void shouldReturnExamWhenNotAllSubgroupsFromArgumentsMatchedAndNotReturnExamsForWrongGeneralGroup() { - List exams = examRepository.findAllBySubgroupsOfGeneralGroup("12K", Set.of("L05")); + Set exams = examRepository.findAllBySubgroupsOfGeneralGroup("12K", Set.of("L05")); assertEquals(1, exams.size()); List examTitles = exams.stream().map(Exam::getTitle).sorted().toList(); assertEquals("small Group Exam 2", examTitles.getFirst()); @@ -143,28 +144,28 @@ void shouldReturnExamWhenNotAllSubgroupsFromArgumentsMatchedAndNotReturnExamsFor @Test void shouldReturnExamsWhenMultipleArgumentsMatch() { - List exams = examRepository.findAllBySubgroupsOfGeneralGroup("12K", Set.of("L04", "L05")); + Set exams = examRepository.findAllBySubgroupsOfGeneralGroup("12K", Set.of("L04", "L05")); assertEquals(2, exams.size()); - List examTitles = exams.stream().map(Exam::getTitle).sorted().toList(); - assertEquals("small Group Exam 1", examTitles.get(0)); - assertEquals("small Group Exam 2", examTitles.get(1)); + Set examTitles = exams.stream().map(Exam::getTitle).collect(Collectors.toSet()); + assertTrue(examTitles.contains("small Group Exam 1")); + assertTrue(examTitles.contains("small Group Exam 2")); } @Test void ShouldReturnEmptyListWhenSubgroupsSetIsEmpty() { - List exams = examRepository.findAllBySubgroupsOfGeneralGroup("12K", Set.of()); + Set exams = examRepository.findAllBySubgroupsOfGeneralGroup("12K", Set.of()); assertTrue(exams.isEmpty()); } @Test void shouldReturnEmptyListWhenGeneralGroupIdentifierHasInvalidFormat() { - List exams = examRepository.findAllBySubgroupsOfGeneralGroup("12K2", Set.of("L04")); + Set exams = examRepository.findAllBySubgroupsOfGeneralGroup("12K2", Set.of("L04")); assertTrue(exams.isEmpty()); } @Test void shouldReturnEmptyListWhenGeneralGroupIdentifierDontMatch() { - List exams = examRepository.findAllBySubgroupsOfGeneralGroup("12B", Set.of("L04", "L05")); + Set exams = examRepository.findAllBySubgroupsOfGeneralGroup("12B", Set.of("L04", "L05")); assertTrue(exams.isEmpty()); } } \ No newline at end of file From 636949e36ebb832088d781c9640bc3bd991cac25 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Tue, 2 Sep 2025 16:59:06 +0200 Subject: [PATCH 076/116] override equals() and hashcode in Exam class --- .../org/pkwmtt/examCalendar/entity/Exam.java | 25 ++++++++++++++++++- .../examCalendar/ExamControllerTest.java | 2 +- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java b/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java index d3f8602..90588cc 100644 --- a/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java +++ b/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java @@ -9,7 +9,9 @@ import org.pkwmtt.exceptions.UnsupportedCountOfArgumentsException; import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; import java.util.HashSet; +import java.util.Objects; import java.util.Set; @Entity @@ -29,7 +31,6 @@ public class Exam { private String description; -// TODO: set timezone for accurate hours of exam @Column(name = "exam_date", nullable = false) private LocalDateTime examDate; @@ -56,4 +57,26 @@ public Exam build() { return new Exam(examId, title, description, examDate, examType, groups); } } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + + Exam exam = (Exam) o; + return getTitle().equals(exam.getTitle()) && + Objects.equals(getDescription(), exam.getDescription()) && + getExamDate().truncatedTo(ChronoUnit.MINUTES).equals(exam.getExamDate().truncatedTo(ChronoUnit.MINUTES)) && + getExamType().equals(exam.getExamType()) && + getGroups().equals(exam.getGroups()); + } + + @Override + public int hashCode() { + int result = getTitle().hashCode(); + result = 31 * result + Objects.hashCode(getDescription()); + result = 31 * result + getExamDate().truncatedTo(ChronoUnit.MINUTES).hashCode(); + result = 31 * result + getExamType().hashCode(); + result = 31 * result + getGroups().hashCode(); + return result; + } } \ No newline at end of file diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java index 6e3c72b..6155ffd 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java @@ -313,7 +313,7 @@ void addExamWithTooLongExamTitle() throws Exception { // given createExampleExamType("Project"); ExamDto requestData = ExamDto.builder() - .title("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") // 256 znaków + .title("a".repeat(256)) // 256 znaków .description("first exam") .date(LocalDateTime.now().plusDays(1)) .examType("Project") From 85ba074dd4d6c6e661791b3ca3a6716681970610 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Florczak?= <84631301+florczaq@users.noreply.github.com> Date: Wed, 3 Sep 2025 14:00:04 +0200 Subject: [PATCH 077/116] Revert "Merge remote-tracking branch 'origin/main' into examCalendar" This reverts commit 9f6a895f179fa3f92a9a03e48548ae6d2c4aec68, reversing changes made to f481029273851772e2466ad3374d13d053a2b45b. --- pom.xml | 75 +++++++++-------- .../repository/UserRepository.java | 2 - .../pkwmtt/security/auth/AuthController.java | 24 ------ .../org/pkwmtt/security/auth/AuthService.java | 7 -- .../pkwmtt/security/auth/AuthServiceImpl.java | 30 ------- .../security/auth/dto/UserRequestDTO.java | 9 --- .../provider/OTPAuthenticationProvider.java | 61 -------------- .../security/config/SpringSecurity.java | 15 +--- .../org/pkwmtt/security/token/JwtService.java | 9 --- .../pkwmtt/security/token/JwtServiceImpl.java | 81 ------------------- .../pkwmtt/security/token/dto/UserDTO.java | 23 ------ .../pkwmtt/security/token/utils/JwtUtils.java | 20 ----- .../org/pkwmtt/jwt/JwtSecretMakerTest.java | 21 ----- 13 files changed, 37 insertions(+), 340 deletions(-) delete mode 100644 src/main/java/org/pkwmtt/security/auth/AuthController.java delete mode 100644 src/main/java/org/pkwmtt/security/auth/AuthService.java delete mode 100644 src/main/java/org/pkwmtt/security/auth/AuthServiceImpl.java delete mode 100644 src/main/java/org/pkwmtt/security/auth/dto/UserRequestDTO.java delete mode 100644 src/main/java/org/pkwmtt/security/auth/provider/OTPAuthenticationProvider.java delete mode 100644 src/main/java/org/pkwmtt/security/token/JwtService.java delete mode 100644 src/main/java/org/pkwmtt/security/token/JwtServiceImpl.java delete mode 100644 src/main/java/org/pkwmtt/security/token/dto/UserDTO.java delete mode 100644 src/main/java/org/pkwmtt/security/token/utils/JwtUtils.java delete mode 100644 src/test/java/org/pkwmtt/jwt/JwtSecretMakerTest.java diff --git a/pom.xml b/pom.xml index 4945707..205bba9 100644 --- a/pom.xml +++ b/pom.xml @@ -70,46 +70,43 @@ true - - - org.springframework.boot - spring-boot-starter-security - - - org.springframework.security - spring-security-test - test - - - io.jsonwebtoken - jjwt-api - 0.12.7 - - - io.jsonwebtoken - jjwt-impl - 0.12.7 - runtime - - - io.jsonwebtoken - jjwt-jackson - 0.12.7 - runtime - - + + + + + + + - - - junit - junit - 4.13.1 - - - org.mockito - mockito-all - 1.10.19 - + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.security + spring-security-test + test + + + + + junit + junit + 4.13.1 + + + + + + + + + org.mockito + mockito-core + 5.18.0 + + com.h2database h2 diff --git a/src/main/java/org/pkwmtt/examCalendar/repository/UserRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/UserRepository.java index 6b39252..acdf767 100644 --- a/src/main/java/org/pkwmtt/examCalendar/repository/UserRepository.java +++ b/src/main/java/org/pkwmtt/examCalendar/repository/UserRepository.java @@ -2,8 +2,6 @@ import org.pkwmtt.examCalendar.entity.User; import org.springframework.data.jpa.repository.JpaRepository; -import java.util.Optional; public interface UserRepository extends JpaRepository { - Optional findByEmail(String email); } \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/security/auth/AuthController.java b/src/main/java/org/pkwmtt/security/auth/AuthController.java deleted file mode 100644 index f86dd7a..0000000 --- a/src/main/java/org/pkwmtt/security/auth/AuthController.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.pkwmtt.security.auth; - -import lombok.RequiredArgsConstructor; -import org.pkwmtt.security.auth.dto.UserRequestDTO; -import org.pkwmtt.security.token.JwtServiceImpl; -import org.pkwmtt.security.token.dto.UserDTO; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/pkwmtt/auth") -@RequiredArgsConstructor -public class AuthController { - private final AuthService authService; - private final JwtServiceImpl jwtServiceImpl; - - @PostMapping("/authenticate") - public String authenticate(@RequestBody UserRequestDTO requestUser) { - UserDTO user = authService.authenticateUser(requestUser.getEmail()); - return jwtServiceImpl.generateToken(user); - } -} diff --git a/src/main/java/org/pkwmtt/security/auth/AuthService.java b/src/main/java/org/pkwmtt/security/auth/AuthService.java deleted file mode 100644 index 3fc3cde..0000000 --- a/src/main/java/org/pkwmtt/security/auth/AuthService.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.pkwmtt.security.auth; - -import org.pkwmtt.security.token.dto.UserDTO; - -public interface AuthService { - UserDTO authenticateUser(String email); -} diff --git a/src/main/java/org/pkwmtt/security/auth/AuthServiceImpl.java b/src/main/java/org/pkwmtt/security/auth/AuthServiceImpl.java deleted file mode 100644 index a878b52..0000000 --- a/src/main/java/org/pkwmtt/security/auth/AuthServiceImpl.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.pkwmtt.security.auth; - -import lombok.RequiredArgsConstructor; -import org.pkwmtt.security.auth.dto.UserRequestDTO; -import org.pkwmtt.security.token.dto.UserDTO; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class AuthServiceImpl implements AuthService { - private final AuthenticationManager authenticationManager; - - @Override - public UserDTO authenticateUser(String email) { - Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken( - email, - null - )); - - if (!authentication.isAuthenticated()) { - throw new UsernameNotFoundException("Invalid credentials"); - } - - return (UserDTO) authentication.getPrincipal(); - } -} diff --git a/src/main/java/org/pkwmtt/security/auth/dto/UserRequestDTO.java b/src/main/java/org/pkwmtt/security/auth/dto/UserRequestDTO.java deleted file mode 100644 index f30ed0e..0000000 --- a/src/main/java/org/pkwmtt/security/auth/dto/UserRequestDTO.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.pkwmtt.security.auth.dto; - -import lombok.Data; - -@Data -public class UserRequestDTO { - private String otp_code; - private String email; -} diff --git a/src/main/java/org/pkwmtt/security/auth/provider/OTPAuthenticationProvider.java b/src/main/java/org/pkwmtt/security/auth/provider/OTPAuthenticationProvider.java deleted file mode 100644 index 4baf894..0000000 --- a/src/main/java/org/pkwmtt/security/auth/provider/OTPAuthenticationProvider.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.pkwmtt.security.auth.provider; -import lombok.RequiredArgsConstructor; -import org.pkwmtt.entity.User; -import org.pkwmtt.repository.UserRepository; -import org.pkwmtt.security.token.dto.UserDTO; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.stereotype.Component; - -import java.util.List; -import java.util.stream.Stream; - -@Component -@RequiredArgsConstructor -public class OTPAuthenticationProvider implements AuthenticationProvider { - private final UserRepository userRepository; - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - String email = authentication.getName(); - - // Fetch user from DB - User user = userRepository.findByEmail(email) - .orElseThrow(() -> new BadCredentialsException("User not found")); - - // Wrap role in a list to support multiple roles in the future - List authorities = Stream.of(user.getRole()) - .map(role -> new SimpleGrantedAuthority("ROLE_" + role.name())) - .toList(); - - // Validate critical user fields - if(!isValidForAuthentication(user)){ - throw new BadCredentialsException( - "Invalid User Credentials. Please contact the administrator." - ); - } - - UserDTO userMapped = new UserDTO(user); - return new UsernamePasswordAuthenticationToken(userMapped, null, authorities); - } - - @Override - public boolean supports(Class authentication) { - return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication); - } - - /** - * Validates user data before authentication. - * Returns true if user has email, role, group, and is active. - */ - private boolean isValidForAuthentication(User user) { - return user.getEmail() != null && - user.getRole() != null && - user.getGeneralGroup() != null && - user.isActive(); - } -} diff --git a/src/main/java/org/pkwmtt/security/config/SpringSecurity.java b/src/main/java/org/pkwmtt/security/config/SpringSecurity.java index 37aea49..c4dc591 100644 --- a/src/main/java/org/pkwmtt/security/config/SpringSecurity.java +++ b/src/main/java/org/pkwmtt/security/config/SpringSecurity.java @@ -1,29 +1,21 @@ package org.pkwmtt.security.config; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.pkwmtt.security.auth.provider.OTPAuthenticationProvider; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.ProviderManager; import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.web.SecurityFilterChain; -import java.util.List; - import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS; //@EnableWebSecurity @Slf4j @Configuration -@RequiredArgsConstructor public class SpringSecurity { - private final OTPAuthenticationProvider otpAuthenticationProvider; - @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { log.info("Configuring Security Filter Chain..."); @@ -38,9 +30,4 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { log.info("Configuring Success..."); return http.build(); } - - @Bean - public AuthenticationManager authenticationManager() { - return new ProviderManager(List.of(otpAuthenticationProvider)); - } } diff --git a/src/main/java/org/pkwmtt/security/token/JwtService.java b/src/main/java/org/pkwmtt/security/token/JwtService.java deleted file mode 100644 index e248f86..0000000 --- a/src/main/java/org/pkwmtt/security/token/JwtService.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.pkwmtt.security.token; - -import org.pkwmtt.security.token.dto.UserDTO; - -public interface JwtService { - String generateToken(UserDTO user); - Boolean validateToken(String token); - String getUserIdFromToken(String token); -} diff --git a/src/main/java/org/pkwmtt/security/token/JwtServiceImpl.java b/src/main/java/org/pkwmtt/security/token/JwtServiceImpl.java deleted file mode 100644 index f94079e..0000000 --- a/src/main/java/org/pkwmtt/security/token/JwtServiceImpl.java +++ /dev/null @@ -1,81 +0,0 @@ -package org.pkwmtt.security.token; - -import io.jsonwebtoken.JwtException; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.security.Keys; -import lombok.RequiredArgsConstructor; -import org.pkwmtt.security.token.dto.UserDTO; -import org.pkwmtt.security.token.utils.JwtUtils; -import org.springframework.stereotype.Service; - -import javax.crypto.SecretKey; -import java.util.Base64; -import java.util.Date; - -@Service -@RequiredArgsConstructor -public class JwtServiceImpl implements JwtService { - private final JwtUtils jwtUtils; - - - /** - * Generates a JWT token for a given user. - * The token contains user's email, group, and role as claims, - * and is signed with a secret key. - * - * @param user - required user data to include in token claims - * @return signed JWT token as a String - */ - @Override - public String generateToken(UserDTO user) { - return Jwts.builder() - .subject(user.getEmail()) - .claim("group", user.getGroup()) - .claim("role", user.getRole()) - .issuedAt(new Date()) - .expiration((new Date(System.currentTimeMillis() + jwtUtils.getExpirationMs()))) - .signWith(decodeSecretKey()) - .compact(); - } - - - /** - * Decode a secret key for signing JWT. - * The key is decoded from Base64 stored in JwtUtils configuration. - * - * @return secret key for JWT signing - */ - private SecretKey decodeSecretKey(){ - byte[] decodedKey = Base64.getDecoder().decode(jwtUtils.getSecret()); - return Keys.hmacShaKeyFor(decodedKey); - } - - /** - * Validate a JWT token. - * Attempts to parse the token; if parsing fails, the token is considered invalid. - * - * @param token JWT token string to validate - * @return true if the token is valid, false otherwise - */ - @Override - public Boolean validateToken(String token) { - try { - // TODO: add logic to validate the token - return true; - } catch (JwtException | IllegalArgumentException e) { - return false; - } - } - - /** - * Extracts the user identifier (email) from a JWT token. - * - * @param token JWT token to extract user from - * @return user email from token - */ - @Override - public String getUserIdFromToken(String token) { - // TODO: implement token parsing to extract subject/email - return ""; - } -} diff --git a/src/main/java/org/pkwmtt/security/token/dto/UserDTO.java b/src/main/java/org/pkwmtt/security/token/dto/UserDTO.java deleted file mode 100644 index b193edd..0000000 --- a/src/main/java/org/pkwmtt/security/token/dto/UserDTO.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.pkwmtt.security.token.dto; - -import lombok.Data; -import org.pkwmtt.entity.GeneralGroup; -import org.pkwmtt.entity.User; -import org.pkwmtt.enums.Role; - -import java.util.Optional; - -@Data -public class UserDTO { - private String email; - private String group; - private Role role; - - public UserDTO(User user){ - this.email = user.getEmail(); - this.role = user.getRole(); - this.group = Optional.ofNullable(user.getGeneralGroup()) - .map(GeneralGroup::getName) - .orElse(null); - } -} diff --git a/src/main/java/org/pkwmtt/security/token/utils/JwtUtils.java b/src/main/java/org/pkwmtt/security/token/utils/JwtUtils.java deleted file mode 100644 index 21ee1bb..0000000 --- a/src/main/java/org/pkwmtt/security/token/utils/JwtUtils.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.pkwmtt.security.token.utils; - -import lombok.Getter; -import org.springframework.core.env.Environment; -import org.springframework.stereotype.Component; - -@Getter -@Component -public class JwtUtils { - // Secret key used for signing JWTs. If the environment variable JWT_SECRET_KEY - // is not set, a default value "TEST_SECRET" is used. This allows the application - // to start without a real secret, e.g., for local development or tests. - private final String secret; - private final long expirationMs = 1000L * 60 * 60 * 24 * 30 * 6; - - public JwtUtils(Environment environment) { - // Get the secret key from environment variables, or fallback to "TEST_SECRET" - this.secret = environment.getProperty("JWT_SECRET_KEY", "TEST_SECRET"); - } -} diff --git a/src/test/java/org/pkwmtt/jwt/JwtSecretMakerTest.java b/src/test/java/org/pkwmtt/jwt/JwtSecretMakerTest.java deleted file mode 100644 index ed43d5d..0000000 --- a/src/test/java/org/pkwmtt/jwt/JwtSecretMakerTest.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.pkwmtt.jwt; - -import io.jsonwebtoken.Jwts; -import jakarta.xml.bind.DatatypeConverter; -import org.junit.jupiter.api.Test; - -import javax.crypto.SecretKey; -import java.nio.charset.StandardCharsets; -import java.util.Base64; - -public class JwtSecretMakerTest { - - @Test - public void generateSecretKey(){ - SecretKey key = Jwts.SIG.HS512.key().build(); - String encodedKey = DatatypeConverter.printHexBinary(key.getEncoded()); - System.out.printf("\nKey = [%s]\n", encodedKey); - String base64Secret = Base64.getEncoder().encodeToString(encodedKey.getBytes(StandardCharsets.UTF_8)); - System.out.println(base64Secret); - } -} From 37d4f2271198973ec4d4c2a280c3d5b81d958fc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Florczak?= <84631301+florczaq@users.noreply.github.com> Date: Wed, 3 Sep 2025 19:44:18 +0200 Subject: [PATCH 078/116] Add exam callendar --- .../pkwmtt/examCalendar/ExamController.java | 95 ++++++++ .../examCalendar/ExamControllerAdvice.java | 48 ++++ .../org/pkwmtt/examCalendar/ExamService.java | 227 ++++++++++++++++++ .../org/pkwmtt/examCalendar/dto/ExamDto.java | 36 +++ .../org/pkwmtt/examCalendar/entity/Exam.java | 82 +++++++ .../pkwmtt/examCalendar/entity/ExamType.java | 24 ++ .../examCalendar/entity/GeneralGroup.java | 25 ++ .../pkwmtt/examCalendar/entity/OTPCode.java | 32 +++ .../examCalendar/entity/StudentGroup.java | 25 ++ .../org/pkwmtt/examCalendar/entity/User.java | 35 +++ .../org/pkwmtt/examCalendar/enums/Role.java | 6 + .../examCalendar/enums/SubjectType.java | 11 + .../examCalendar/mapper/ExamDtoMapper.java | 76 ++++++ .../repository/ExamRepository.java | 31 +++ .../repository/ExamTypeRepository.java | 10 + .../repository/GeneralGroupRepository.java | 7 + .../repository/GroupRepository.java | 10 + .../repository/OTPCodeRepository.java | 7 + .../repository/UserRepository.java | 7 + 19 files changed, 794 insertions(+) create mode 100644 src/main/java/org/pkwmtt/examCalendar/ExamController.java create mode 100644 src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java create mode 100644 src/main/java/org/pkwmtt/examCalendar/ExamService.java create mode 100644 src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java create mode 100644 src/main/java/org/pkwmtt/examCalendar/entity/Exam.java create mode 100644 src/main/java/org/pkwmtt/examCalendar/entity/ExamType.java create mode 100644 src/main/java/org/pkwmtt/examCalendar/entity/GeneralGroup.java create mode 100644 src/main/java/org/pkwmtt/examCalendar/entity/OTPCode.java create mode 100644 src/main/java/org/pkwmtt/examCalendar/entity/StudentGroup.java create mode 100644 src/main/java/org/pkwmtt/examCalendar/entity/User.java create mode 100644 src/main/java/org/pkwmtt/examCalendar/enums/Role.java create mode 100644 src/main/java/org/pkwmtt/examCalendar/enums/SubjectType.java create mode 100644 src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoMapper.java create mode 100644 src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java create mode 100644 src/main/java/org/pkwmtt/examCalendar/repository/ExamTypeRepository.java create mode 100644 src/main/java/org/pkwmtt/examCalendar/repository/GeneralGroupRepository.java create mode 100644 src/main/java/org/pkwmtt/examCalendar/repository/GroupRepository.java create mode 100644 src/main/java/org/pkwmtt/examCalendar/repository/OTPCodeRepository.java create mode 100644 src/main/java/org/pkwmtt/examCalendar/repository/UserRepository.java diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamController.java b/src/main/java/org/pkwmtt/examCalendar/ExamController.java new file mode 100644 index 0000000..028c08e --- /dev/null +++ b/src/main/java/org/pkwmtt/examCalendar/ExamController.java @@ -0,0 +1,95 @@ +package org.pkwmtt.examCalendar; + +import com.fasterxml.jackson.core.JsonProcessingException; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Positive; +import lombok.RequiredArgsConstructor; +import org.pkwmtt.examCalendar.dto.ExamDto; +import org.pkwmtt.examCalendar.entity.Exam; +import org.pkwmtt.examCalendar.entity.ExamType; +import org.pkwmtt.examCalendar.mapper.ExamDtoMapper; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import java.net.URI; +import java.util.List; +import java.util.Set; + +@Validated +@RequiredArgsConstructor +@RequestMapping("/pkwmtt/api/v1/exams") +@RestController +public class ExamController { + + private final ExamService examService; + + /** + * @param examDto details of exam + * @return 201 created with URI to GET method which returns created resource + */ + @PostMapping("") + public ResponseEntity addExam(@RequestBody @Valid ExamDto examDto){ + int id = examService.addExam(examDto); + URI uri = ServletUriComponentsBuilder + .fromCurrentRequest() + .path("/{id}") + .buildAndExpand(id) + .toUri(); + return ResponseEntity.created(uri).build(); + } + + /** + * @param id of exam or test + * @param examDto new details of exam or test + * @return 204 no content + */ + @PutMapping("/{id}") + public ResponseEntity modifyExam(@PathVariable @Positive int id, @RequestBody @Valid ExamDto examDto) { + examService.modifyExam(examDto, id); + return ResponseEntity.noContent().build(); + } + + /** + * @param id of exam or test + * @return 204 no content + */ + @DeleteMapping("/{id}") + public ResponseEntity deleteExam(@PathVariable int id) { + examService.deleteExam(id); + return ResponseEntity.noContent().build(); + } + + /** + * @param id of exam or test + * @return 200 ok with single exam or test details + */ + @GetMapping("/{id}") + public ResponseEntity getExam(@PathVariable int id) { + return ResponseEntity.ok(examService.getExamById(id)); + } + + /** + * when subgroups isn't null all generalGroups must be form the same year of study. e.g. 12K2, 12K1 is from 12K + * @param generalGroups set of general groups e.g. 12K2 + * @param subgroups set of subgroups of general group e.g. L04 + * @return List of ExamDto for specific groups + */ + @GetMapping("/by-groups") + public ResponseEntity> getExams( + @RequestParam Set generalGroups, + @RequestParam(required = false) Set subgroups + ){ + return ResponseEntity.ok(ExamDtoMapper.mapToExamDto(examService.getExamByGroups(generalGroups, subgroups))); + } + + /** + * @return 200 ok with list of available exam types + */ + @GetMapping("/exam-types") + public ResponseEntity> getExamTypes(){ + return ResponseEntity.ok(examService.getExamTypes()); + } + +} \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java b/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java new file mode 100644 index 0000000..20a9e8b --- /dev/null +++ b/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java @@ -0,0 +1,48 @@ +package org.pkwmtt.examCalendar; + +import jakarta.validation.ConstraintViolationException; +import org.pkwmtt.exceptions.*; +import org.pkwmtt.exceptions.dto.ErrorResponseDTO; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import java.util.stream.Collectors; + +@RestControllerAdvice(assignableTypes = {ExamController.class}) +public class ExamControllerAdvice { + + @ExceptionHandler(NoSuchElementWithProvidedIdException.class) + public ResponseEntity handleNoSuchElementWithProvidedIdException(NoSuchElementWithProvidedIdException e) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponseDTO(e.getMessage())); + } + + @ExceptionHandler({ + ExamTypeNotExistsException.class, + InvalidGroupIdentifierException.class, + SpecifiedGeneralGroupDoesntExistsException.class, + SpecifiedSubGroupDoesntExistsException.class, + UnsupportedCountOfArgumentsException.class + }) + public ResponseEntity handleBadRequest(RuntimeException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorResponseDTO(e.getMessage())); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + String message = e.getBindingResult().getFieldErrors().stream() + .map(field -> field.getField() + " : " + field.getDefaultMessage()) + .collect(Collectors.joining(", ")); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorResponseDTO(message)); + } + + @ExceptionHandler(ConstraintViolationException.class) + public ResponseEntity handleConstraintViolationException(ConstraintViolationException e) { + String message = e.getConstraintViolations().stream() + .map(field -> field.getPropertyPath() + " : " + field.getMessage()) + .collect(Collectors.joining(", ")); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorResponseDTO(message)); + } +} diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamService.java b/src/main/java/org/pkwmtt/examCalendar/ExamService.java new file mode 100644 index 0000000..58621b0 --- /dev/null +++ b/src/main/java/org/pkwmtt/examCalendar/ExamService.java @@ -0,0 +1,227 @@ +package org.pkwmtt.examCalendar; + +import com.fasterxml.jackson.core.JsonProcessingException; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.pkwmtt.examCalendar.dto.ExamDto; +import org.pkwmtt.examCalendar.entity.Exam; +import org.pkwmtt.examCalendar.entity.ExamType; +import org.pkwmtt.examCalendar.entity.StudentGroup; +import org.pkwmtt.examCalendar.mapper.ExamDtoMapper; +import org.pkwmtt.examCalendar.repository.ExamRepository; +import org.pkwmtt.examCalendar.repository.ExamTypeRepository; +import org.pkwmtt.examCalendar.repository.GroupRepository; +import org.pkwmtt.exceptions.*; +import org.pkwmtt.timetable.TimetableService; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +@Transactional +public class ExamService { + + private final ExamRepository examRepository; + private final ExamTypeRepository examTypeRepository; + private final GroupRepository groupRepository; + private final TimetableService timetableService; + + /** + * @param examDto details of exam + * @return id of exam added to database + */ + public int addExam(ExamDto examDto) { + + Set groups = verifyAndUpdateExamGroups(examDto); + +// check if exam type exists + ExamType examType = examTypeRepository.findByName(examDto.getExamType()) + .orElseThrow(() -> new ExamTypeNotExistsException(examDto.getExamType())); + +// save exam in repository and return id of created exam + return examRepository.save(ExamDtoMapper.mapToNewExam(examDto, groups, examType)).getExamId(); + } + + /** + * @param examDto new details of exam that overwrite old ones + * @param id of exam that need to be modified + */ + public void modifyExam(ExamDto examDto, int id) { +// check if exam which would be modified exists + examRepository.findById(id).orElseThrow(() -> new NoSuchElementWithProvidedIdException(id)); + + Set groups = verifyAndUpdateExamGroups(examDto); + +// check if exam type exists + ExamType examType = examTypeRepository.findByName(examDto.getExamType()) + .orElseThrow(() -> new ExamTypeNotExistsException(examDto.getExamType())); + + examRepository.save(ExamDtoMapper.mapToExistingExam(examDto, groups, examType, id)); + } + + /** + * @param id of exam + */ + public void deleteExam(int id) { + examRepository.findById(id).orElseThrow(() -> new NoSuchElementWithProvidedIdException(id)); + examRepository.deleteById(id); + } + + /** + * @param id of exam + * @return exam + */ + public Exam getExamById(int id) { + return examRepository.findById(id).orElseThrow(() -> new NoSuchElementWithProvidedIdException(id)); + } + + public Set getExamByGroups(Set generalGroups, Set subgroups) { +// verify generalGroups identifiers + verifyGeneralGroupsFormat(generalGroups); +// get exams for general groups + Set exams = new HashSet<>(examRepository.findAllByGroups_NameIn(generalGroups)); + exams = exams.stream() + .filter(exam -> exam.getGroups().stream() + .allMatch(group -> group.getName().matches("^\\d.*"))) + .collect(Collectors.toSet()); + +// convert general group identifiers. e.g. 12K2 to 12K + Set superiorGroups = generalGroups.stream().map(g -> { + if (Character.isDigit(g.charAt(g.length() - 1))) + return g.substring(0, g.length() - 1); + return g; + }).collect(Collectors.toSet()); +// check if subgroups are provided + if (subgroups != null && !subgroups.isEmpty()) { +// verify subgroups identifiers + verifySubgroupsFormat(subgroups); +// check if superior group identifies the groups unambiguously + if (superiorGroups.size() != 1) + throw new InvalidGroupIdentifierException("ambiguous superior group identifier for subgroups"); + exams.addAll(examRepository.findAllBySubgroupsOfGeneralGroup(superiorGroups.iterator().next(), subgroups)); + } + return exams; + } + + /** + * @return list of examTypes + */ + public List getExamTypes() { + return examTypeRepository.findAll(); + } + + /** + * verify if groups exists in timetable if exist updates database. + * when timetable service is unavailable verifies groups using groupsRepository + * + * @param examDto containing groups for verification + */ + private Set verifyAndUpdateExamGroups(ExamDto examDto) { + Set generalGroupsFromRepository; + Set generalGroups = examDto.getGeneralGroups(); + Set subgroups = examDto.getSubgroups(); +// if timetable service is unavailable verify general groups using GroupRepository + try { + generalGroupsFromRepository = new HashSet<>(timetableService.getGeneralGroupList()); + } catch (WebPageContentNotAvailableException e) { + generalGroupsFromRepository = verifyGroupsUsingRepository(generalGroups); + } +// verify generalGroups using timetable service + if (!generalGroupsFromRepository.containsAll(generalGroups)) { + generalGroups.removeAll(generalGroupsFromRepository); + throw new InvalidGroupIdentifierException(generalGroups); + } +// if there are no subgroups save exam for exercise groups or whole year e.g. +// 12K2 - exercise group exam +// 12K1, 12K2, 12K3 - whole year exam + if (subgroups == null || subgroups.isEmpty()) { + return saveNewStudentGroups(generalGroups); +// exams for subgroups e.g. L04 must have only superior group to avoid ambiguity + } else if (generalGroups.size() == 1) { +// if there are only one group change it from Set to String + String superiorGroup = generalGroups.iterator().next(); + Set subGroupsFromTimetable; + try { + subGroupsFromTimetable = new HashSet<>(timetableService.getAvailableSubGroups(superiorGroup)); + } catch (JsonProcessingException | + SpecifiedGeneralGroupDoesntExistsException | + WebPageContentNotAvailableException e) { + throw new ServiceNotAvailableException("Couldn't verify groups using timetable service"); +// TODO: add verification with repository when timetable service is unavailable + } +// verify if subgroups for specific general group exists + if (!subGroupsFromTimetable.containsAll(subgroups)) { + subgroups.removeAll(subGroupsFromTimetable); + throw new InvalidGroupIdentifierException(subgroups); + } +// change superior group format e.g. 12K2 to 12K + if (Character.isDigit(superiorGroup.charAt(superiorGroup.length() - 1))) + superiorGroup = superiorGroup.substring(0, superiorGroup.length() - 1); +// save subgroups with superior group identifier + subgroups.add(superiorGroup); + return saveNewStudentGroups(subgroups); + } +// only one general group could be assigned to subgroups (when there are more than 1 general group and +// more than 0 subgroups) + else if (generalGroups.isEmpty()) + throw new InvalidGroupIdentifierException("general group is missing"); + else + throw new InvalidGroupIdentifierException("ambiguous general groups for subgroups"); + } + + /** + * @param groups groups that would be verified using repository + * @return set of groups (String) when verification succeeded + * @throws WebPageContentNotAvailableException when verification not succeeded + */ + private Set verifyGroupsUsingRepository(Set groups) throws WebPageContentNotAvailableException { + verifyGeneralGroupsFormat(groups); + Set groupsFromRepository = groupRepository.findAllByNameIn(groups).stream() + .map(StudentGroup::getName) + .collect(Collectors.toSet() + ); + if (groupsFromRepository.containsAll(groups)) + return groups; + else + throw new ServiceNotAvailableException("Couldn't verify groups using repository"); + } + + /** + * saves groups to groupRepository, existing group names are filtered out before saving + * + * @param groups groups that would be saved to repository + * @return set of StudentsGroup Entity with names from groups. + */ + private Set saveNewStudentGroups(Set groups) { +// remove duplicates before saving records + Set existingGroups = groupRepository.findAllByNameIn(groups); + groups.removeAll(existingGroups.stream() + .map(StudentGroup::getName) + .collect(Collectors.toSet()) + ); + List savedGroups = groupRepository.saveAll(groups.stream() + .map(g -> StudentGroup.builder() + .name(g) + .build() + ).collect(Collectors.toList()) + ); + existingGroups.addAll(savedGroups); + return existingGroups; + } + + private static void verifyGeneralGroupsFormat(Set generalGroups) throws SpecifiedGeneralGroupDoesntExistsException { + generalGroups.forEach(group -> { + if (!group.matches("^\\d.*")) + throw new SpecifiedGeneralGroupDoesntExistsException(group); + }); + } + + private static void verifySubgroupsFormat(Set subgroups) { + subgroups.forEach(group -> { + if (!group.matches("^[A-Z].*")) + throw new SpecifiedSubGroupDoesntExistsException(group); + }); + } +} diff --git a/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java b/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java new file mode 100644 index 0000000..8b5e72a --- /dev/null +++ b/src/main/java/org/pkwmtt/examCalendar/dto/ExamDto.java @@ -0,0 +1,36 @@ +package org.pkwmtt.examCalendar.dto; + +import jakarta.validation.constraints.*; +import lombok.Builder; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.pkwmtt.examCalendar.entity.StudentGroup; + +import java.time.LocalDateTime; +import java.util.Set; + +@Getter +@RequiredArgsConstructor +@Builder +public class ExamDto { + + @NotBlank + @Size(max = 255, message = "max size of field is 255") + private final String title; + + @Size(max = 255, message = "max size of field is 255") + private final String description; + + @Future(message = "Date must be in the future") + @NotNull + private final LocalDateTime date; + + @NotNull + private final String examType; + + @NotEmpty + @Size(min = 1) + private final Set generalGroups; + + private final Set subgroups; +} diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java b/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java new file mode 100644 index 0000000..90588cc --- /dev/null +++ b/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java @@ -0,0 +1,82 @@ +package org.pkwmtt.examCalendar.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.pkwmtt.exceptions.InvalidGroupIdentifierException; +import org.pkwmtt.exceptions.UnsupportedCountOfArgumentsException; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +@Entity +@Getter +@Builder(builderClassName = "Builder", buildMethodName = "build") +@RequiredArgsConstructor +@Table(name = "exams") +@AllArgsConstructor +public class Exam { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "exam_id") + private Integer examId; + + @Column(nullable = false) + private String title; + + private String description; + + @Column(name = "exam_date", nullable = false) + private LocalDateTime examDate; + + @ManyToOne + @JoinColumn(name = "exam_type_id", nullable = false) + private ExamType examType; + + @ManyToMany + @JoinTable( + name="exams_groups", + joinColumns = @JoinColumn(name = "exam_id"), + inverseJoinColumns = @JoinColumn(name = "group_id") + ) + private Set groups = new HashSet<>(); + + @SuppressWarnings("unused") + public static class Builder { + public Exam build() { +// min 1 max 100 elements of set + if(groups == null) + throw new UnsupportedCountOfArgumentsException(1, 100, null); + if(groups.isEmpty() || groups.size() > 100) + throw new UnsupportedCountOfArgumentsException(1, 100, groups.size()); + return new Exam(examId, title, description, examDate, examType, groups); + } + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + + Exam exam = (Exam) o; + return getTitle().equals(exam.getTitle()) && + Objects.equals(getDescription(), exam.getDescription()) && + getExamDate().truncatedTo(ChronoUnit.MINUTES).equals(exam.getExamDate().truncatedTo(ChronoUnit.MINUTES)) && + getExamType().equals(exam.getExamType()) && + getGroups().equals(exam.getGroups()); + } + + @Override + public int hashCode() { + int result = getTitle().hashCode(); + result = 31 * result + Objects.hashCode(getDescription()); + result = 31 * result + getExamDate().truncatedTo(ChronoUnit.MINUTES).hashCode(); + result = 31 * result + getExamType().hashCode(); + result = 31 * result + getGroups().hashCode(); + return result; + } +} \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/ExamType.java b/src/main/java/org/pkwmtt/examCalendar/entity/ExamType.java new file mode 100644 index 0000000..90a9f74 --- /dev/null +++ b/src/main/java/org/pkwmtt/examCalendar/entity/ExamType.java @@ -0,0 +1,24 @@ +package org.pkwmtt.examCalendar.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Entity +@Getter +@Builder +@AllArgsConstructor +@RequiredArgsConstructor +@Table(name = "exam_type") +public class ExamType { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "exam_type_id", nullable = false) + private Integer examTypeId; + + @Column(nullable = false) + private String name; +} \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/GeneralGroup.java b/src/main/java/org/pkwmtt/examCalendar/entity/GeneralGroup.java new file mode 100644 index 0000000..12e328f --- /dev/null +++ b/src/main/java/org/pkwmtt/examCalendar/entity/GeneralGroup.java @@ -0,0 +1,25 @@ +package org.pkwmtt.examCalendar.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.Set; + +@Entity +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Table(name = "general_group") +public class GeneralGroup { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "general_group_id") + private Integer generalGroupId; + + @Column(nullable = false) + private String name; +} diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/OTPCode.java b/src/main/java/org/pkwmtt/examCalendar/entity/OTPCode.java new file mode 100644 index 0000000..da9997c --- /dev/null +++ b/src/main/java/org/pkwmtt/examCalendar/entity/OTPCode.java @@ -0,0 +1,32 @@ +package org.pkwmtt.examCalendar.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Entity +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Table(name = "otp_codes") +public class OTPCode { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "otp_code_id") + private Integer otpCodeId; + + @Column(nullable = false) + private String code; + + @Column(nullable = false) + private LocalDateTime expire; + + @OneToOne + @JoinColumn(name = "general_group_id", nullable = false) + private GeneralGroup generalGroup; +} diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/StudentGroup.java b/src/main/java/org/pkwmtt/examCalendar/entity/StudentGroup.java new file mode 100644 index 0000000..3332ef2 --- /dev/null +++ b/src/main/java/org/pkwmtt/examCalendar/entity/StudentGroup.java @@ -0,0 +1,25 @@ +package org.pkwmtt.examCalendar.entity; + +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@Setter +@RequiredArgsConstructor +@AllArgsConstructor +@Builder +@Table(name = "student_groups") +public class StudentGroup { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "group_id") + private Integer groupId; + + @Column(nullable = false, unique = true) + private String name; + +// FIXME: remove? +// @ManyToMany(mappedBy = "groups") +// private Set exams = new HashSet<>(); +} diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/User.java b/src/main/java/org/pkwmtt/examCalendar/entity/User.java new file mode 100644 index 0000000..4cdfbc9 --- /dev/null +++ b/src/main/java/org/pkwmtt/examCalendar/entity/User.java @@ -0,0 +1,35 @@ +package org.pkwmtt.examCalendar.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.pkwmtt.examCalendar.enums.Role; + +@Entity +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Table(name = "`users`") +public class User { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_id") + private Integer userId; + + @OneToOne + @JoinColumn(name = "general_group_id", nullable = false) + private GeneralGroup generalGroup; + + @Column(nullable = false) + private String email; + + @Column(name = "is_active", nullable = false) + private boolean isActive; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private Role role; +} diff --git a/src/main/java/org/pkwmtt/examCalendar/enums/Role.java b/src/main/java/org/pkwmtt/examCalendar/enums/Role.java new file mode 100644 index 0000000..aafdf12 --- /dev/null +++ b/src/main/java/org/pkwmtt/examCalendar/enums/Role.java @@ -0,0 +1,6 @@ +package org.pkwmtt.examCalendar.enums; + +public enum Role { + ADMIN, + REPRESENTATIVE +} \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/examCalendar/enums/SubjectType.java b/src/main/java/org/pkwmtt/examCalendar/enums/SubjectType.java new file mode 100644 index 0000000..0aad3d5 --- /dev/null +++ b/src/main/java/org/pkwmtt/examCalendar/enums/SubjectType.java @@ -0,0 +1,11 @@ +package org.pkwmtt.examCalendar.enums; + +public enum SubjectType { + LECTURE, + SEMINAR, + EXERCISES, + LABORATORY, + COMPUTER_LABORATORY, + PROJECT, + OTHER +} diff --git a/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoMapper.java b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoMapper.java new file mode 100644 index 0000000..ea79230 --- /dev/null +++ b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoMapper.java @@ -0,0 +1,76 @@ +package org.pkwmtt.examCalendar.mapper; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.pkwmtt.examCalendar.dto.ExamDto; +import org.pkwmtt.examCalendar.entity.Exam; +import org.pkwmtt.examCalendar.entity.ExamType; +import org.pkwmtt.examCalendar.entity.StudentGroup; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * maps ExamDto to Exam entity. Couldn't be utility class, because needs ExamTypeRepository to validate exam types + */ +@Component +@RequiredArgsConstructor +@Slf4j +public class ExamDtoMapper { + private ExamDtoMapper examDtoMapper; + + /** + * @param examDto examDto object received from request + * @return Exam entity WITHOUT examId which should be assigned by database + * Also contains examType field converted from String do ExamType + */ + public static Exam mapToNewExam(ExamDto examDto, Set groups, ExamType examType) { + return Exam.builder() + .title(examDto.getTitle()) + .description(examDto.getDescription()) + .examDate(examDto.getDate()) + .examType(examType) + .groups(groups) + .build(); + } + + /** + * @param examDto examDto object received from request + * @param id of Exam that need to be modified + * @return Exam entity WITH examId that allow to update entity in database instead of creating new one + * Also contains examType field converted from String do ExamType + */ + public static Exam mapToExistingExam(ExamDto examDto, Set groups, ExamType examType, int id) { + return Exam.builder() + .examId(id) + .title(examDto.getTitle()) + .description(examDto.getDescription()) + .examDate(examDto.getDate()) + .examType(examType) + .groups(groups) + .build(); + } + + public static List mapToExamDto(Set exams) { + return exams.stream().map(ExamDtoMapper::mapToExamDto).collect(Collectors.toList()); + } + + public static ExamDto mapToExamDto(Exam exam) { + Set groups = exam.getGroups().stream().map(StudentGroup::getName).collect(Collectors.toSet()); + Set generalGroups = groups.stream().filter(group -> Character.isDigit(group.charAt(0))).collect(Collectors.toSet()); + Set subgroups = groups.stream().filter(group -> Character.isAlphabetic(group.charAt(0))).collect(Collectors.toSet()); + if(groups.size() != subgroups.size() + generalGroups.size()) + log.warn("Some groups of {} were not consumed in ExamDtoMapper.mapToExamDto()", groups); + return ExamDto.builder() + .title(exam.getTitle()) + .description(exam.getDescription()) + .date(exam.getExamDate()) + .examType(exam.getExamType().getName()) + .generalGroups(generalGroups) + .subgroups(subgroups) + .build(); + } +} diff --git a/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java new file mode 100644 index 0000000..c87b283 --- /dev/null +++ b/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java @@ -0,0 +1,31 @@ +package org.pkwmtt.examCalendar.repository; + +import org.pkwmtt.examCalendar.entity.Exam; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; +import java.util.Set; + +public interface ExamRepository extends JpaRepository { + + /** + * @param groups set of generalGroups + * @return list of exams for generalGroups + */ + List findAllByGroups_NameIn(Set groups); + + /** + * @param generalGroup superior group of subgroups e.g. 12K + * @param subgroup exam groups + * @return list of exams for subgroups + */ + @Query(""" + SELECT DISTINCT e FROM Exam e + JOIN e.groups g1 + JOIN FETCH e.groups g2 + WHERE g1.name = :general AND g2.name IN :sub + """) + Set findAllBySubgroupsOfGeneralGroup(@Param("general") String generalGroup, @Param("sub") Set subgroup); +} \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/examCalendar/repository/ExamTypeRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/ExamTypeRepository.java new file mode 100644 index 0000000..c14d733 --- /dev/null +++ b/src/main/java/org/pkwmtt/examCalendar/repository/ExamTypeRepository.java @@ -0,0 +1,10 @@ +package org.pkwmtt.examCalendar.repository; + +import org.pkwmtt.examCalendar.entity.ExamType; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface ExamTypeRepository extends JpaRepository { + Optional findByName(String name); +} \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/examCalendar/repository/GeneralGroupRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/GeneralGroupRepository.java new file mode 100644 index 0000000..62f4fbb --- /dev/null +++ b/src/main/java/org/pkwmtt/examCalendar/repository/GeneralGroupRepository.java @@ -0,0 +1,7 @@ +package org.pkwmtt.examCalendar.repository; + +import org.pkwmtt.examCalendar.entity.GeneralGroup; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface GeneralGroupRepository extends JpaRepository { +} \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/examCalendar/repository/GroupRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/GroupRepository.java new file mode 100644 index 0000000..7a9e4dd --- /dev/null +++ b/src/main/java/org/pkwmtt/examCalendar/repository/GroupRepository.java @@ -0,0 +1,10 @@ +package org.pkwmtt.examCalendar.repository; + +import org.pkwmtt.examCalendar.entity.StudentGroup; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Set; + +public interface GroupRepository extends JpaRepository { + Set findAllByNameIn(Set names); +} \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/examCalendar/repository/OTPCodeRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/OTPCodeRepository.java new file mode 100644 index 0000000..848b4d4 --- /dev/null +++ b/src/main/java/org/pkwmtt/examCalendar/repository/OTPCodeRepository.java @@ -0,0 +1,7 @@ +package org.pkwmtt.examCalendar.repository; + +import org.pkwmtt.examCalendar.entity.OTPCode; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface OTPCodeRepository extends JpaRepository { +} \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/examCalendar/repository/UserRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/UserRepository.java new file mode 100644 index 0000000..acdf767 --- /dev/null +++ b/src/main/java/org/pkwmtt/examCalendar/repository/UserRepository.java @@ -0,0 +1,7 @@ +package org.pkwmtt.examCalendar.repository; + +import org.pkwmtt.examCalendar.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UserRepository extends JpaRepository { +} \ No newline at end of file From 79fafce9d48465fb2997175ffe1b62f972801152 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Florczak?= <84631301+florczaq@users.noreply.github.com> Date: Wed, 3 Sep 2025 19:46:49 +0200 Subject: [PATCH 079/116] Delete old paths --- src/main/java/org/pkwmtt/entity/Exam.java | 38 ------------------- src/main/java/org/pkwmtt/entity/ExamType.java | 18 --------- .../java/org/pkwmtt/entity/GeneralGroup.java | 17 --------- src/main/java/org/pkwmtt/entity/OTPCode.java | 25 ------------ .../java/org/pkwmtt/entity/StudentGroup.java | 23 ----------- src/main/java/org/pkwmtt/entity/User.java | 29 -------------- src/main/java/org/pkwmtt/enums/Role.java | 6 --- .../java/org/pkwmtt/enums/SubjectType.java | 11 ------ .../ExamTypeNotExistsException.java | 7 ++++ .../InvalidGroupIdentifierException.java | 13 +++++++ .../exceptions/NoGroupsProvidedException.java | 7 ++++ .../NoSuchElementWithProvidedIdException.java | 7 ++++ .../ServiceNotAvailableException.java | 7 ++++ .../UnsupportedCountOfArgumentsException.java | 8 ++++ .../org/pkwmtt/repository/ExamRepository.java | 7 ---- .../pkwmtt/repository/ExamTypeRepository.java | 7 ---- .../repository/GeneralGroupRepository.java | 7 ---- .../pkwmtt/repository/GroupRepository.java | 7 ---- .../pkwmtt/repository/OTPCodeRepository.java | 7 ---- .../org/pkwmtt/repository/UserRepository.java | 9 ----- 20 files changed, 49 insertions(+), 211 deletions(-) delete mode 100644 src/main/java/org/pkwmtt/entity/Exam.java delete mode 100644 src/main/java/org/pkwmtt/entity/ExamType.java delete mode 100644 src/main/java/org/pkwmtt/entity/GeneralGroup.java delete mode 100644 src/main/java/org/pkwmtt/entity/OTPCode.java delete mode 100644 src/main/java/org/pkwmtt/entity/StudentGroup.java delete mode 100644 src/main/java/org/pkwmtt/entity/User.java delete mode 100644 src/main/java/org/pkwmtt/enums/Role.java delete mode 100644 src/main/java/org/pkwmtt/enums/SubjectType.java create mode 100644 src/main/java/org/pkwmtt/exceptions/ExamTypeNotExistsException.java create mode 100644 src/main/java/org/pkwmtt/exceptions/InvalidGroupIdentifierException.java create mode 100644 src/main/java/org/pkwmtt/exceptions/NoGroupsProvidedException.java create mode 100644 src/main/java/org/pkwmtt/exceptions/NoSuchElementWithProvidedIdException.java create mode 100644 src/main/java/org/pkwmtt/exceptions/ServiceNotAvailableException.java create mode 100644 src/main/java/org/pkwmtt/exceptions/UnsupportedCountOfArgumentsException.java delete mode 100644 src/main/java/org/pkwmtt/repository/ExamRepository.java delete mode 100644 src/main/java/org/pkwmtt/repository/ExamTypeRepository.java delete mode 100644 src/main/java/org/pkwmtt/repository/GeneralGroupRepository.java delete mode 100644 src/main/java/org/pkwmtt/repository/GroupRepository.java delete mode 100644 src/main/java/org/pkwmtt/repository/OTPCodeRepository.java delete mode 100644 src/main/java/org/pkwmtt/repository/UserRepository.java diff --git a/src/main/java/org/pkwmtt/entity/Exam.java b/src/main/java/org/pkwmtt/entity/Exam.java deleted file mode 100644 index 6cda359..0000000 --- a/src/main/java/org/pkwmtt/entity/Exam.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.pkwmtt.entity; - -import jakarta.persistence.*; -import lombok.Data; -import java.time.LocalDateTime; -import java.util.HashSet; -import java.util.Set; - -@Entity -@Table(name = "`exams`") -@Data -public class Exam { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "exam_id") - private Integer examId; - - @Column(nullable = false) - private String title; - - private String description; - - @Column(name = "`exam_date`", nullable = false) - private LocalDateTime examDate; - - @ManyToOne - @JoinColumn(name = "exam_type_id", nullable = false) - private ExamType examType; - - @ManyToMany - @JoinTable( - name="exams_groups", - joinColumns = @JoinColumn(name = "exam_id"), - inverseJoinColumns = @JoinColumn(name = "group_id") - ) - private Set groups = new HashSet<>();; - -} diff --git a/src/main/java/org/pkwmtt/entity/ExamType.java b/src/main/java/org/pkwmtt/entity/ExamType.java deleted file mode 100644 index fd971ef..0000000 --- a/src/main/java/org/pkwmtt/entity/ExamType.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.pkwmtt.entity; - -import jakarta.persistence.*; -import lombok.Data; - -@Entity -@Data -@Table(name = "`exam_type`") -public class ExamType { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "exam_type_id", nullable = false) - private Integer examTypeId; - - @Column(nullable = false) - private String name; -} diff --git a/src/main/java/org/pkwmtt/entity/GeneralGroup.java b/src/main/java/org/pkwmtt/entity/GeneralGroup.java deleted file mode 100644 index eb00158..0000000 --- a/src/main/java/org/pkwmtt/entity/GeneralGroup.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.pkwmtt.entity; - -import jakarta.persistence.*; -import lombok.Data; - -@Entity -@Data -@Table(name = "`general_group`") -public class GeneralGroup { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "general_group_id") - private Integer generalGroupId; - - @Column(nullable = false) - private String name; -} diff --git a/src/main/java/org/pkwmtt/entity/OTPCode.java b/src/main/java/org/pkwmtt/entity/OTPCode.java deleted file mode 100644 index a202b42..0000000 --- a/src/main/java/org/pkwmtt/entity/OTPCode.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.pkwmtt.entity; - -import jakarta.persistence.*; -import lombok.Data; -import java.time.LocalDateTime; - -@Entity -@Data -@Table(name = "otp_codes") -public class OTPCode { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "otp_code_id") - private Integer otpCodeId; - - @Column(nullable = false) - private String code; - - @Column(nullable = false) - private LocalDateTime expire; - - @OneToOne - @JoinColumn(name = "`general_group_id`", nullable = false) - private GeneralGroup generalGroup; -} diff --git a/src/main/java/org/pkwmtt/entity/StudentGroup.java b/src/main/java/org/pkwmtt/entity/StudentGroup.java deleted file mode 100644 index 5c0f10c..0000000 --- a/src/main/java/org/pkwmtt/entity/StudentGroup.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.pkwmtt.entity; - -import jakarta.persistence.*; -import lombok.Data; - -import java.util.HashSet; -import java.util.Set; - -@Entity -@Data -@Table(name = "`groups`") -public class StudentGroup { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "group_id") - private Integer groupId; - - @Column(nullable = false) - private String name; - - @ManyToMany(mappedBy = "groups") - private Set exams = new HashSet<>(); -} diff --git a/src/main/java/org/pkwmtt/entity/User.java b/src/main/java/org/pkwmtt/entity/User.java deleted file mode 100644 index 81a03c8..0000000 --- a/src/main/java/org/pkwmtt/entity/User.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.pkwmtt.entity; - -import jakarta.persistence.*; -import lombok.Data; -import org.pkwmtt.enums.Role; - -@Entity -@Data -@Table(name = "`users`") -public class User { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "user_id") - private Integer userId; - - @OneToOne - @JoinColumn(name = "general_group_id", nullable = false) - private GeneralGroup generalGroup; - - @Column(nullable = false) - private String email; - - @Column(name = "is_active", nullable = false) - private boolean isActive; - - @Enumerated(EnumType.STRING) - @Column(nullable = false) - private Role role; -} diff --git a/src/main/java/org/pkwmtt/enums/Role.java b/src/main/java/org/pkwmtt/enums/Role.java deleted file mode 100644 index 0a72aa0..0000000 --- a/src/main/java/org/pkwmtt/enums/Role.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.pkwmtt.enums; - -public enum Role { - ADMIN, - REPRESENTATIVE -} \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/enums/SubjectType.java b/src/main/java/org/pkwmtt/enums/SubjectType.java deleted file mode 100644 index f43282e..0000000 --- a/src/main/java/org/pkwmtt/enums/SubjectType.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.pkwmtt.enums; - -public enum SubjectType { - LECTURE, - SEMINAR, - EXERCISES, - LABORATORY, - COMPUTER_LABORATORY, - PROJECT, - OTHER -} diff --git a/src/main/java/org/pkwmtt/exceptions/ExamTypeNotExistsException.java b/src/main/java/org/pkwmtt/exceptions/ExamTypeNotExistsException.java new file mode 100644 index 0000000..5e8171a --- /dev/null +++ b/src/main/java/org/pkwmtt/exceptions/ExamTypeNotExistsException.java @@ -0,0 +1,7 @@ +package org.pkwmtt.exceptions; + +public class ExamTypeNotExistsException extends RuntimeException { + public ExamTypeNotExistsException(String examType) { + super("Invalid exam type " + examType); + } +} diff --git a/src/main/java/org/pkwmtt/exceptions/InvalidGroupIdentifierException.java b/src/main/java/org/pkwmtt/exceptions/InvalidGroupIdentifierException.java new file mode 100644 index 0000000..a47bf28 --- /dev/null +++ b/src/main/java/org/pkwmtt/exceptions/InvalidGroupIdentifierException.java @@ -0,0 +1,13 @@ +package org.pkwmtt.exceptions; + +import java.util.Set; + +public class InvalidGroupIdentifierException extends RuntimeException { + public InvalidGroupIdentifierException(String groupIdentifier) { + super("Invalid group identifier: " + groupIdentifier); + } + + public InvalidGroupIdentifierException(Set groupIdentifiers) { + super("Invalid group identifiers: " + groupIdentifiers.toString()); + } +} diff --git a/src/main/java/org/pkwmtt/exceptions/NoGroupsProvidedException.java b/src/main/java/org/pkwmtt/exceptions/NoGroupsProvidedException.java new file mode 100644 index 0000000..b99f44b --- /dev/null +++ b/src/main/java/org/pkwmtt/exceptions/NoGroupsProvidedException.java @@ -0,0 +1,7 @@ +package org.pkwmtt.exceptions; + +public class NoGroupsProvidedException extends RuntimeException { + public NoGroupsProvidedException() { + super("groups set is empty"); + } +} diff --git a/src/main/java/org/pkwmtt/exceptions/NoSuchElementWithProvidedIdException.java b/src/main/java/org/pkwmtt/exceptions/NoSuchElementWithProvidedIdException.java new file mode 100644 index 0000000..e17eead --- /dev/null +++ b/src/main/java/org/pkwmtt/exceptions/NoSuchElementWithProvidedIdException.java @@ -0,0 +1,7 @@ +package org.pkwmtt.exceptions; + +public class NoSuchElementWithProvidedIdException extends RuntimeException{ + public NoSuchElementWithProvidedIdException(int id) { + super("No such element with id: " + id); + } +} diff --git a/src/main/java/org/pkwmtt/exceptions/ServiceNotAvailableException.java b/src/main/java/org/pkwmtt/exceptions/ServiceNotAvailableException.java new file mode 100644 index 0000000..2f8b1b2 --- /dev/null +++ b/src/main/java/org/pkwmtt/exceptions/ServiceNotAvailableException.java @@ -0,0 +1,7 @@ +package org.pkwmtt.exceptions; + +public class ServiceNotAvailableException extends RuntimeException { + public ServiceNotAvailableException(String message) { + super(message); + } +} diff --git a/src/main/java/org/pkwmtt/exceptions/UnsupportedCountOfArgumentsException.java b/src/main/java/org/pkwmtt/exceptions/UnsupportedCountOfArgumentsException.java new file mode 100644 index 0000000..fc3718c --- /dev/null +++ b/src/main/java/org/pkwmtt/exceptions/UnsupportedCountOfArgumentsException.java @@ -0,0 +1,8 @@ +package org.pkwmtt.exceptions; + +public class UnsupportedCountOfArgumentsException extends RuntimeException { + public UnsupportedCountOfArgumentsException(int expectedMin, int expectedMax, Integer provided) { + super("Invalid count of arguments provided: " + provided + + " expected more than: " + expectedMin + " less than: " + expectedMax); + } +} diff --git a/src/main/java/org/pkwmtt/repository/ExamRepository.java b/src/main/java/org/pkwmtt/repository/ExamRepository.java deleted file mode 100644 index 2faafaa..0000000 --- a/src/main/java/org/pkwmtt/repository/ExamRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.pkwmtt.repository; - -import org.pkwmtt.entity.Exam; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface ExamRepository extends JpaRepository { -} \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/repository/ExamTypeRepository.java b/src/main/java/org/pkwmtt/repository/ExamTypeRepository.java deleted file mode 100644 index 1b7d38c..0000000 --- a/src/main/java/org/pkwmtt/repository/ExamTypeRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.pkwmtt.repository; - -import org.pkwmtt.entity.ExamType; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface ExamTypeRepository extends JpaRepository { -} \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/repository/GeneralGroupRepository.java b/src/main/java/org/pkwmtt/repository/GeneralGroupRepository.java deleted file mode 100644 index a4c1c55..0000000 --- a/src/main/java/org/pkwmtt/repository/GeneralGroupRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.pkwmtt.repository; - -import org.pkwmtt.entity.GeneralGroup; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface GeneralGroupRepository extends JpaRepository { -} \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/repository/GroupRepository.java b/src/main/java/org/pkwmtt/repository/GroupRepository.java deleted file mode 100644 index b2396a9..0000000 --- a/src/main/java/org/pkwmtt/repository/GroupRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.pkwmtt.repository; - -import org.pkwmtt.entity.StudentGroup; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface GroupRepository extends JpaRepository { -} \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/repository/OTPCodeRepository.java b/src/main/java/org/pkwmtt/repository/OTPCodeRepository.java deleted file mode 100644 index 4f79485..0000000 --- a/src/main/java/org/pkwmtt/repository/OTPCodeRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.pkwmtt.repository; - -import org.pkwmtt.entity.OTPCode; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface OTPCodeRepository extends JpaRepository { -} \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/repository/UserRepository.java b/src/main/java/org/pkwmtt/repository/UserRepository.java deleted file mode 100644 index 1e53f12..0000000 --- a/src/main/java/org/pkwmtt/repository/UserRepository.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.pkwmtt.repository; - -import org.pkwmtt.entity.User; -import org.springframework.data.jpa.repository.JpaRepository; -import java.util.Optional; - -public interface UserRepository extends JpaRepository { - Optional findByEmail(String email); -} \ No newline at end of file From 74bf8a6a9ba43d754a5f733d6b415362b697ba9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Florczak?= <84631301+florczaq@users.noreply.github.com> Date: Wed, 3 Sep 2025 20:03:53 +0200 Subject: [PATCH 080/116] Fixes after structure change --- .../java/org/pkwmtt/config/StartupConfig.java | 35 -------- .../repository/UserRepository.java | 3 + .../provider/OTPAuthenticationProvider.java | 47 +++++------ .../pkwmtt/security/token/dto/UserDTO.java | 6 +- .../org/pkwmtt/timetable/dto/SubjectDTO.java | 18 ++-- .../parser/TimetableParserService.java | 2 +- .../org/pkwmtt/cache/CacheConfigTest.java | 83 +++++++++---------- .../timetable/TimetableCacheServiceTest.java | 71 ++++++++-------- .../timetable/TimetableServiceTest.java | 2 + 9 files changed, 117 insertions(+), 150 deletions(-) delete mode 100644 src/main/java/org/pkwmtt/config/StartupConfig.java diff --git a/src/main/java/org/pkwmtt/config/StartupConfig.java b/src/main/java/org/pkwmtt/config/StartupConfig.java deleted file mode 100644 index 234dee7..0000000 --- a/src/main/java/org/pkwmtt/config/StartupConfig.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.pkwmtt.config; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.event.ContextRefreshedEvent; -import org.springframework.context.event.EventListener; -import org.springframework.stereotype.Component; - - -/** - * Logs server base url so you can click on it after start and - * go directly to swagger - */ -@Slf4j -@Component -public class StartupConfig { - - @Value("${server.port:}") - String port = ""; - - @Value("${server.address:}") - String address = ""; - - @EventListener(ContextRefreshedEvent.class) - public void onApplicationEvent() { - try { - if (port.isEmpty() || address.isEmpty()) - throw new Exception(); - log.info("SERVER URL: http://{}:{}", address, port); - } catch (Exception e) { - log.error("!Couldn't log the server base url. Check properties in application.properties"); - } - } - -} diff --git a/src/main/java/org/pkwmtt/examCalendar/repository/UserRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/UserRepository.java index acdf767..14065d5 100644 --- a/src/main/java/org/pkwmtt/examCalendar/repository/UserRepository.java +++ b/src/main/java/org/pkwmtt/examCalendar/repository/UserRepository.java @@ -3,5 +3,8 @@ import org.pkwmtt.examCalendar.entity.User; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + public interface UserRepository extends JpaRepository { + Optional findByEmail (String email); } \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/security/auth/provider/OTPAuthenticationProvider.java b/src/main/java/org/pkwmtt/security/auth/provider/OTPAuthenticationProvider.java index 4baf894..845de0e 100644 --- a/src/main/java/org/pkwmtt/security/auth/provider/OTPAuthenticationProvider.java +++ b/src/main/java/org/pkwmtt/security/auth/provider/OTPAuthenticationProvider.java @@ -1,7 +1,9 @@ package org.pkwmtt.security.auth.provider; + import lombok.RequiredArgsConstructor; -import org.pkwmtt.entity.User; -import org.pkwmtt.repository.UserRepository; +import org.pkwmtt.examCalendar.entity.*; +import org.pkwmtt.examCalendar.repository.UserRepository; +import org.pkwmtt.examCalendar.repository.*; import org.pkwmtt.security.token.dto.UserDTO; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.BadCredentialsException; @@ -18,44 +20,39 @@ @RequiredArgsConstructor public class OTPAuthenticationProvider implements AuthenticationProvider { private final UserRepository userRepository; - + @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { + public Authentication authenticate (Authentication authentication) throws AuthenticationException { String email = authentication.getName(); - + // Fetch user from DB - User user = userRepository.findByEmail(email) - .orElseThrow(() -> new BadCredentialsException("User not found")); - + User user = userRepository.findByEmail(email).orElseThrow(() -> new BadCredentialsException("User not found")); + // Wrap role in a list to support multiple roles in the future - List authorities = Stream.of(user.getRole()) - .map(role -> new SimpleGrantedAuthority("ROLE_" + role.name())) - .toList(); - + List authorities = Stream + .of(user.getRole()) + .map(role -> new SimpleGrantedAuthority("ROLE_" + role.name())) + .toList(); + // Validate critical user fields - if(!isValidForAuthentication(user)){ - throw new BadCredentialsException( - "Invalid User Credentials. Please contact the administrator." - ); + if (!isValidForAuthentication(user)) { + throw new BadCredentialsException("Invalid User Credentials. Please contact the administrator."); } - + UserDTO userMapped = new UserDTO(user); return new UsernamePasswordAuthenticationToken(userMapped, null, authorities); } - + @Override - public boolean supports(Class authentication) { + public boolean supports (Class authentication) { return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication); } - + /** * Validates user data before authentication. * Returns true if user has email, role, group, and is active. */ - private boolean isValidForAuthentication(User user) { - return user.getEmail() != null && - user.getRole() != null && - user.getGeneralGroup() != null && - user.isActive(); + private boolean isValidForAuthentication (User user) { + return user.getEmail() != null && user.getRole() != null && user.getGeneralGroup() != null && user.isActive(); } } diff --git a/src/main/java/org/pkwmtt/security/token/dto/UserDTO.java b/src/main/java/org/pkwmtt/security/token/dto/UserDTO.java index b193edd..7734db6 100644 --- a/src/main/java/org/pkwmtt/security/token/dto/UserDTO.java +++ b/src/main/java/org/pkwmtt/security/token/dto/UserDTO.java @@ -1,9 +1,9 @@ package org.pkwmtt.security.token.dto; import lombok.Data; -import org.pkwmtt.entity.GeneralGroup; -import org.pkwmtt.entity.User; -import org.pkwmtt.enums.Role; +import org.pkwmtt.examCalendar.entity.GeneralGroup; +import org.pkwmtt.examCalendar.entity.User; +import org.pkwmtt.examCalendar.enums.Role; import java.util.Optional; diff --git a/src/main/java/org/pkwmtt/timetable/dto/SubjectDTO.java b/src/main/java/org/pkwmtt/timetable/dto/SubjectDTO.java index 7b71ce3..141c80a 100644 --- a/src/main/java/org/pkwmtt/timetable/dto/SubjectDTO.java +++ b/src/main/java/org/pkwmtt/timetable/dto/SubjectDTO.java @@ -2,7 +2,7 @@ import lombok.*; import lombok.experimental.Accessors; -import org.pkwmtt.enums.SubjectType; +import org.pkwmtt.examCalendar.enums.SubjectType; import java.util.regex.Pattern; @@ -13,15 +13,13 @@ public class SubjectDTO { private String classroom; private int rowId; private SubjectType type; - - - public void deleteTypeAndUnnecessaryCharactersFromName() { - if (name.contains(" ")) + + + public void deleteTypeAndUnnecessaryCharactersFromName () { + if (name.contains(" ")) { this.name = name.substring(0, name.indexOf(' ')); - - name = name - .replaceAll("_", " ") - .replaceAll(Pattern.quote("("), "") - .replaceAll(Pattern.quote(")"), ""); + } + + name = name.replaceAll("_", " ").replaceAll(Pattern.quote("("), "").replaceAll(Pattern.quote(")"), ""); } } diff --git a/src/main/java/org/pkwmtt/timetable/parser/TimetableParserService.java b/src/main/java/org/pkwmtt/timetable/parser/TimetableParserService.java index af32752..46605d3 100644 --- a/src/main/java/org/pkwmtt/timetable/parser/TimetableParserService.java +++ b/src/main/java/org/pkwmtt/timetable/parser/TimetableParserService.java @@ -7,7 +7,7 @@ import org.jsoup.select.Elements; import org.pkwmtt.timetable.dto.DayOfWeekDTO; import org.pkwmtt.timetable.dto.SubjectDTO; -import org.pkwmtt.enums.SubjectType; +import org.pkwmtt.examCalendar.enums.SubjectType; import org.springframework.stereotype.Service; import java.util.ArrayList; diff --git a/src/test/java/org/pkwmtt/cache/CacheConfigTest.java b/src/test/java/org/pkwmtt/cache/CacheConfigTest.java index 6aa8251..c5c7317 100644 --- a/src/test/java/org/pkwmtt/cache/CacheConfigTest.java +++ b/src/test/java/org/pkwmtt/cache/CacheConfigTest.java @@ -5,6 +5,7 @@ import org.pkwmtt.ValuesForTest; import org.pkwmtt.timetable.TimetableCacheService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cache.CacheManager; import test.TestConfig; @@ -14,71 +15,69 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; +@SpringBootTest class CacheConfigTest extends TestConfig { + @Autowired private TimetableCacheService service; - + @Autowired private CacheManager cacheManager; - + @BeforeEach - public void initWireMock() { - EXTERNAL_SERVICE_API_MOCK.stubFor(get(urlPathMatching("/plany/o25.html")) - .willReturn(aResponse() - .withStatus(200) - .withHeader("Content-Type", "text/*") - .withBody(ValuesForTest.timetableHTML))); - - EXTERNAL_SERVICE_API_MOCK.stubFor(get(urlPathMatching("/lista.html")) - .willReturn(aResponse() - .withStatus(200) - .withHeader("Content-Type", "text/*") - .withBody(ValuesForTest.listHTML))); + public void initWireMock () { + EXTERNAL_SERVICE_API_MOCK.stubFor(get(urlPathMatching("/plany/o25.html")).willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "text/*") + .withBody(ValuesForTest.timetableHTML))); + + EXTERNAL_SERVICE_API_MOCK.stubFor(get(urlPathMatching("/lista.html")).willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "text/*") + .withBody(ValuesForTest.listHTML))); } - + @Test - void testCacheKeyPresent_Schedule() { + void testCacheKeyPresent_Schedule () { //given - + //when service.getGeneralGroupSchedule("12K1"); var cache = cacheManager.getCache("timetables"); - + //then assertAll( - () -> { - assertThat(cache).isNotNull(); - assertThat(cache.get("generalGroupMap", String.class)) - .isEqualTo("{\"11K2\":\"plany/o8.html\",\"12K1\":\"plany/o25.html\",\"11A1\":\"plany/o1.html\",\"12K3\":\"plany/o27.html\",\"12K2\":\"plany/o26.html\"}"); - }, - () -> { - var wrapper = cache.get("timetable_12K1"); - assertThat(wrapper).isNotNull(); - assertThat(wrapper.get()).isInstanceOf(String.class); - } + () -> { + assertThat(cache).isNotNull(); + assertThat(cache.get("generalGroupMap", String.class)).isEqualTo( + "{\"11K2\":\"plany/o8.html\",\"12K1\":\"plany/o25.html\",\"11A1\":\"plany/o1.html\",\"12K3\":\"plany/o27.html\",\"12K2\":\"plany/o26.html\"}"); + }, () -> { + var wrapper = cache.get("timetable_12K1"); + assertThat(wrapper).isNotNull(); + assertThat(wrapper.get()).isInstanceOf(String.class); + } ); } - + @Test - void testCacheKeyPresent_HoursList(){ + void testCacheKeyPresent_HoursList () { //given - + //when service.getListOfHours(); var cache = cacheManager.getCache("timetables"); - + //then assertAll( - () -> { - assertThat(cache).isNotNull(); - assertThat(cache.get("hourList", String.class)) - .isEqualTo("[\"7:30- 8:15\",\"8:15- 9:00\",\"9:15-10:00\",\"10:00-10:45\",\"11:00-11:45\",\"11:45-12:30\",\"12:45-13:30\",\"13:30-14:15\",\"14:30-15:15\",\"15:15-16:00\",\"16:15-17:00\",\"17:00-17:45\",\"18:00-18:45\",\"18:45-19:30\",\"19:45-20:30\",\"20:30-21:15\"]"); - }, - () -> { - var wrapper = cache.get("hourList"); - assertThat(wrapper).isNotNull(); - assertThat(wrapper.get()).isInstanceOf(String.class); - } + () -> { + assertThat(cache).isNotNull(); + assertThat(cache.get("hourList", String.class)).isEqualTo( + "[\"7:30- 8:15\",\"8:15- 9:00\",\"9:15-10:00\",\"10:00-10:45\",\"11:00-11:45\",\"11:45-12:30\",\"12:45-13:30\",\"13:30-14:15\",\"14:30-15:15\",\"15:15-16:00\",\"16:15-17:00\",\"17:00-17:45\",\"18:00-18:45\",\"18:45-19:30\",\"19:45-20:30\",\"20:30-21:15\"]"); + }, () -> { + var wrapper = cache.get("hourList"); + assertThat(wrapper).isNotNull(); + assertThat(wrapper.get()).isInstanceOf(String.class); + } ); } } \ No newline at end of file diff --git a/src/test/java/org/pkwmtt/timetable/TimetableCacheServiceTest.java b/src/test/java/org/pkwmtt/timetable/TimetableCacheServiceTest.java index f25888f..0a461ae 100644 --- a/src/test/java/org/pkwmtt/timetable/TimetableCacheServiceTest.java +++ b/src/test/java/org/pkwmtt/timetable/TimetableCacheServiceTest.java @@ -8,6 +8,7 @@ import org.pkwmtt.cache.CacheInspector; import org.pkwmtt.timetable.dto.TimetableDTO; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; import test.TestConfig; import java.util.List; @@ -20,6 +21,7 @@ import static org.junit.jupiter.api.Assertions.*; @Slf4j +@SpringBootTest class TimetableCacheServiceTest extends TestConfig { @Autowired TimetableCacheService cachedService; @@ -29,20 +31,18 @@ class TimetableCacheServiceTest extends TestConfig { @Autowired CacheInspector cacheInspector; - + @BeforeEach - public void initWireMock() { - EXTERNAL_SERVICE_API_MOCK.stubFor(get(urlPathMatching("/plany/o25.html")) - .willReturn(aResponse() - .withStatus(200) - .withHeader("Content-Type", "text/*") - .withBody(ValuesForTest.timetableHTML))); - - EXTERNAL_SERVICE_API_MOCK.stubFor(get(urlPathMatching("/lista.html")) - .willReturn(aResponse() - .withStatus(200) - .withHeader("Content-Type", "text/*") - .withBody(ValuesForTest.listHTML))); + public void initWireMock () { + EXTERNAL_SERVICE_API_MOCK.stubFor(get(urlPathMatching("/plany/o25.html")).willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "text/*") + .withBody(ValuesForTest.timetableHTML))); + + EXTERNAL_SERVICE_API_MOCK.stubFor(get(urlPathMatching("/lista.html")).willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "text/*") + .withBody(ValuesForTest.listHTML))); } @Test @@ -56,13 +56,12 @@ public void shouldHourListBePresentInCache () { //then assertAll( - () -> assertNotNull(cacheData), - () -> assertTrue(cacheData.containsKey(key)), - () -> { - var hourList = cacheData.get(key); - assertNotNull(hourList); - assertThat(hourList).isEqualTo("[\"7:30- 8:15\",\"8:15- 9:00\",\"9:15-10:00\",\"10:00-10:45\",\"11:00-11:45\",\"11:45-12:30\",\"12:45-13:30\",\"13:30-14:15\",\"14:30-15:15\",\"15:15-16:00\",\"16:15-17:00\",\"17:00-17:45\",\"18:00-18:45\",\"18:45-19:30\",\"19:45-20:30\",\"20:30-21:15\"]"); - } + () -> assertNotNull(cacheData), () -> assertTrue(cacheData.containsKey(key)), () -> { + var hourList = cacheData.get(key); + assertNotNull(hourList); + assertThat(hourList).isEqualTo( + "[\"7:30- 8:15\",\"8:15- 9:00\",\"9:15-10:00\",\"10:00-10:45\",\"11:00-11:45\",\"11:45-12:30\",\"12:45-13:30\",\"13:30-14:15\",\"14:30-15:15\",\"15:15-16:00\",\"16:15-17:00\",\"17:00-17:45\",\"18:00-18:45\",\"18:45-19:30\",\"19:45-20:30\",\"20:30-21:15\"]"); + } ); } @@ -71,16 +70,21 @@ public void shouldHourListBePresentInCache () { public void shouldReturnGeneralGroupsMap () { //given var expectedMap = Map.of( - "11K2", "plany/o8.html", - "12K1", "plany/o25.html", - "11A1", "plany/o1.html", - "12K3", "plany/o27.html", - "12K2", "plany/o26.html" + "11K2", + "plany/o8.html", + "12K1", + "plany/o25.html", + "11A1", + "plany/o1.html", + "12K3", + "plany/o27.html", + "12K2", + "plany/o26.html" ); - + //when var result = cachedService.getGeneralGroupsMap(); - + //then assertThat(result).isEqualTo(expectedMap); } @@ -94,15 +98,14 @@ public void shouldGeneralGroupMapBePresentInCache () { //when Map cacheData = cacheInspector.getAllEntries("timetables"); // get all keys saved in cache - + //then assertAll( - () -> assertNotNull(cacheData), - () -> { - assertTrue(cacheData.containsKey(key)); - var data = cacheData.get(key); - assertThat(data).isEqualTo(expectedValue); - } + () -> assertNotNull(cacheData), () -> { + assertTrue(cacheData.containsKey(key)); + var data = cacheData.get(key); + assertThat(data).isEqualTo(expectedValue); + } ); } diff --git a/src/test/java/org/pkwmtt/timetable/TimetableServiceTest.java b/src/test/java/org/pkwmtt/timetable/TimetableServiceTest.java index c582a3f..58f2055 100644 --- a/src/test/java/org/pkwmtt/timetable/TimetableServiceTest.java +++ b/src/test/java/org/pkwmtt/timetable/TimetableServiceTest.java @@ -13,11 +13,13 @@ import org.pkwmtt.exceptions.SpecifiedGeneralGroupDoesntExistsException; import org.pkwmtt.exceptions.SpecifiedSubGroupDoesntExistsException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; import test.TestConfig; import java.util.List; import java.util.regex.Pattern; +@SpringBootTest class TimetableServiceTest extends TestConfig { @Autowired From eafd99f6872c4c0dcc733b0db47703b9170ca9ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Florczak?= <84631301+florczaq@users.noreply.github.com> Date: Wed, 3 Sep 2025 20:21:11 +0200 Subject: [PATCH 081/116] Otp files --- .../java/org/pkwmtt/otp/OTPController.java | 33 +++++ .../org/pkwmtt/otp/OTPExceptionHandler.java | 25 ++++ src/main/java/org/pkwmtt/otp/OTPService.java | 123 ++++++++++++++++++ .../java/org/pkwmtt/otp/dto/OTPRequest.java | 22 ++++ .../otp/repository/OTPCodeRepository.java | 15 +++ 5 files changed, 218 insertions(+) create mode 100644 src/main/java/org/pkwmtt/otp/OTPController.java create mode 100644 src/main/java/org/pkwmtt/otp/OTPExceptionHandler.java create mode 100644 src/main/java/org/pkwmtt/otp/OTPService.java create mode 100644 src/main/java/org/pkwmtt/otp/dto/OTPRequest.java create mode 100644 src/main/java/org/pkwmtt/otp/repository/OTPCodeRepository.java diff --git a/src/main/java/org/pkwmtt/otp/OTPController.java b/src/main/java/org/pkwmtt/otp/OTPController.java new file mode 100644 index 0000000..7455482 --- /dev/null +++ b/src/main/java/org/pkwmtt/otp/OTPController.java @@ -0,0 +1,33 @@ +package org.pkwmtt.otp; + + +import lombok.RequiredArgsConstructor; +import org.pkwmtt.exceptions.MailCouldNotBeSendException; +import org.pkwmtt.exceptions.OTPCodeNotFoundException; +import org.pkwmtt.exceptions.UserNotFoundException; +import org.pkwmtt.exceptions.WrongOTPFormatException; +import org.pkwmtt.otp.dto.OTPRequest; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/pkwmtt/api/v1/representatives") +@RequiredArgsConstructor +public class OTPController { + private final OTPService service; + + @GetMapping("/authenticate") + public ResponseEntity authenticate (@RequestParam(name = "c") String code) + throws OTPCodeNotFoundException, WrongOTPFormatException, UserNotFoundException { + return ResponseEntity.ok(service.generateTokenForRepresentative(code)); + } + + @PostMapping("/codes/generate") + public ResponseEntity generateCodes (@RequestBody List request) throws MailCouldNotBeSendException { + service.sendOTPCodes(request); + return ResponseEntity.ok().build(); + } + +} diff --git a/src/main/java/org/pkwmtt/otp/OTPExceptionHandler.java b/src/main/java/org/pkwmtt/otp/OTPExceptionHandler.java new file mode 100644 index 0000000..d438de4 --- /dev/null +++ b/src/main/java/org/pkwmtt/otp/OTPExceptionHandler.java @@ -0,0 +1,25 @@ +package org.pkwmtt.otp; + + +import org.pkwmtt.exceptions.MailCouldNotBeSendException; +import org.pkwmtt.exceptions.OTPCodeNotFoundException; +import org.pkwmtt.exceptions.UserNotFoundException; +import org.pkwmtt.exceptions.WrongOTPFormatException; +import org.pkwmtt.exceptions.dto.ErrorResponseDTO; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice(assignableTypes = {OTPController.class}) +public class OTPExceptionHandler { + @ExceptionHandler({OTPCodeNotFoundException.class, WrongOTPFormatException.class, UserNotFoundException.class}) + public ResponseEntity handleBadRequests (Exception e) { + return new ResponseEntity<>(new ErrorResponseDTO(e.getMessage()), HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler({MailCouldNotBeSendException.class}) + public ResponseEntity handleServerErrors (Exception e) { + return new ResponseEntity<>(new ErrorResponseDTO(e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR); + } +} diff --git a/src/main/java/org/pkwmtt/otp/OTPService.java b/src/main/java/org/pkwmtt/otp/OTPService.java new file mode 100644 index 0000000..3d8f74a --- /dev/null +++ b/src/main/java/org/pkwmtt/otp/OTPService.java @@ -0,0 +1,123 @@ +package org.pkwmtt.otp; + +import jakarta.mail.MessagingException; +import lombok.RequiredArgsConstructor; +import org.pkwmtt.examCalendar.entity.GeneralGroup; +import org.pkwmtt.examCalendar.entity.OTPCode; +import org.pkwmtt.examCalendar.repository.GeneralGroupRepository; +import org.pkwmtt.examCalendar.repository.UserRepository; +import org.pkwmtt.exceptions.MailCouldNotBeSendException; +import org.pkwmtt.exceptions.OTPCodeNotFoundException; +import org.pkwmtt.exceptions.UserNotFoundException; +import org.pkwmtt.exceptions.WrongOTPFormatException; +import org.pkwmtt.mail.EmailService; +import org.pkwmtt.mail.dto.MailDTO; +import org.pkwmtt.otp.dto.OTPRequest; +import org.pkwmtt.otp.repository.OTPCodeRepository; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; +import java.util.Random; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Service +@RequiredArgsConstructor +public class OTPService { + private final OTPCodeRepository repository; + private final UserRepository userRepository; + private final GeneralGroupRepository generalGroupRepository; + private final EmailService emailService; + + private GeneralGroup getGeneralGroupAssignedToCode (String code) throws OTPCodeNotFoundException, WrongOTPFormatException { + this.validateCode(code); + + Optional result = repository.findByCode(code); + + if (result.isEmpty()) { + throw new OTPCodeNotFoundException(); + } + + return result.get().getGeneralGroup(); + } + + private void validateCode (String code) throws WrongOTPFormatException { + if (code.length() != 6) { + throw new WrongOTPFormatException("Code should be 6 characters long."); + } + + String regex = "^[A-Z0-9]{6}$"; + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(code); + + if (!matcher.find()) { + throw new WrongOTPFormatException("Wrong format of provided code."); + } + } + + public String generateTokenForRepresentative (String code) + throws OTPCodeNotFoundException, WrongOTPFormatException, UserNotFoundException { + var generalGroup = this.getGeneralGroupAssignedToCode(code); + + var user = userRepository + .findByGeneralGroup(generalGroup) + .orElseThrow(() -> new UserNotFoundException("No user is assigned to this code.")); + + var userEmail = user.getEmail(); + + //TODO DELETE + String token = "example-token_" + generalGroup.getName(); + + //TODO here generate token with provided credentials + + //Delete used code + repository.deleteByCode(code); + return token; + } + + public void sendOTPCodes (List requests) throws MailCouldNotBeSendException { + requests.forEach(request -> { + var code = generateNewCode(); + var mail = createMail(request, code); + + try { + emailService.send(mail); + } catch (MessagingException e) { + throw new MailCouldNotBeSendException("Couldn't send mail for group: " + request.getGeneralGroupName()); + } + + var generalGroup = generalGroupRepository.findByName(request.getGeneralGroupName()); + + if (generalGroup.isEmpty()) { + generalGroup = Optional.of(generalGroupRepository.save(new GeneralGroup(null, request.getGeneralGroupName()))); + } + + repository.save(new OTPCode(code, generalGroup.get())); + }); + } + + private MailDTO createMail (OTPRequest request, String code) { + return new MailDTO() + .setTitle("Kod Starosty " + request.getGeneralGroupName()) + .setRecipient(request.getEmail()) + .setDescription(request.getMailMessage(code)); + } + + private String generateNewCode () { + String availableCharacters = "ABCDEFGHIJKLMNOPQRSTUWXYZ0123456789"; + StringBuilder code = new StringBuilder(); + Random random = new Random(); + + do { + code.setLength(0); + for (int i = 0; i < 6; i++) { + code.append(availableCharacters.charAt(random.nextInt(availableCharacters.length()))); + } + } while (repository.findByCode(code.toString()).isPresent()); + + return code.toString(); + } + + +} diff --git a/src/main/java/org/pkwmtt/otp/dto/OTPRequest.java b/src/main/java/org/pkwmtt/otp/dto/OTPRequest.java new file mode 100644 index 0000000..3c04e03 --- /dev/null +++ b/src/main/java/org/pkwmtt/otp/dto/OTPRequest.java @@ -0,0 +1,22 @@ +package org.pkwmtt.otp.dto; + +import lombok.Data; + +@Data +public class OTPRequest { + private String email; + private String generalGroupName; + + public String getMailMessage (String code) { + return String.format( + """ + Kod starosty %s
+ Poniżej znajduje się kod służący do ulepszenia wersji aplikacji do poziomu starosty.
+ Dzięki temu będziesz mógł dodawać oraz usuwać egzaminy dla swojego kierunku w kalendarzu aplikacji.
+ Wpisz kod w [Ustawienia > Wpisz kod], albo przekaż go osobie odpowiedzialnej za kalendarz egzaminów.
+ Twój kod: %s
+ Na wykorzystanie kodu masz 1 dzień.
+ """, generalGroupName, code + ); + } +} diff --git a/src/main/java/org/pkwmtt/otp/repository/OTPCodeRepository.java b/src/main/java/org/pkwmtt/otp/repository/OTPCodeRepository.java new file mode 100644 index 0000000..fd15d0b --- /dev/null +++ b/src/main/java/org/pkwmtt/otp/repository/OTPCodeRepository.java @@ -0,0 +1,15 @@ +package org.pkwmtt.otp.repository; + +import jakarta.transaction.Transactional; +import org.pkwmtt.examCalendar.entity.OTPCode; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface OTPCodeRepository + extends JpaRepository { + Optional findByCode (String code); + + @Transactional + void deleteByCode (String code); +} \ No newline at end of file From 873194e3138035dfc3655903e53e53febfb905dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Florczak?= <84631301+florczaq@users.noreply.github.com> Date: Wed, 3 Sep 2025 20:24:56 +0200 Subject: [PATCH 082/116] Missing exceptions files --- .../pkwmtt/exceptions/MailCouldNotBeSendException.java | 7 +++++++ .../org/pkwmtt/exceptions/OTPCodeNotFoundException.java | 8 ++++++++ .../exceptions/UnsupportedCountOfArgumentsException.java | 2 +- .../java/org/pkwmtt/exceptions/UserNotFoundException.java | 8 ++++++++ .../org/pkwmtt/exceptions/WrongOTPFormatException.java | 8 ++++++++ 5 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/pkwmtt/exceptions/MailCouldNotBeSendException.java create mode 100644 src/main/java/org/pkwmtt/exceptions/OTPCodeNotFoundException.java create mode 100644 src/main/java/org/pkwmtt/exceptions/UserNotFoundException.java create mode 100644 src/main/java/org/pkwmtt/exceptions/WrongOTPFormatException.java diff --git a/src/main/java/org/pkwmtt/exceptions/MailCouldNotBeSendException.java b/src/main/java/org/pkwmtt/exceptions/MailCouldNotBeSendException.java new file mode 100644 index 0000000..989a843 --- /dev/null +++ b/src/main/java/org/pkwmtt/exceptions/MailCouldNotBeSendException.java @@ -0,0 +1,7 @@ +package org.pkwmtt.exceptions; + +public class MailCouldNotBeSendException extends RuntimeException { + public MailCouldNotBeSendException (String message) { + super(message); + } +} diff --git a/src/main/java/org/pkwmtt/exceptions/OTPCodeNotFoundException.java b/src/main/java/org/pkwmtt/exceptions/OTPCodeNotFoundException.java new file mode 100644 index 0000000..2626ec8 --- /dev/null +++ b/src/main/java/org/pkwmtt/exceptions/OTPCodeNotFoundException.java @@ -0,0 +1,8 @@ +package org.pkwmtt.exceptions; + +public class OTPCodeNotFoundException + extends IllegalArgumentException { + public OTPCodeNotFoundException () { + super("Provided isn't assigned to any group."); + } +} diff --git a/src/main/java/org/pkwmtt/exceptions/UnsupportedCountOfArgumentsException.java b/src/main/java/org/pkwmtt/exceptions/UnsupportedCountOfArgumentsException.java index fc3718c..709978a 100644 --- a/src/main/java/org/pkwmtt/exceptions/UnsupportedCountOfArgumentsException.java +++ b/src/main/java/org/pkwmtt/exceptions/UnsupportedCountOfArgumentsException.java @@ -1,7 +1,7 @@ package org.pkwmtt.exceptions; public class UnsupportedCountOfArgumentsException extends RuntimeException { - public UnsupportedCountOfArgumentsException(int expectedMin, int expectedMax, Integer provided) { + public UnsupportedCountOfArgumentsException(int expectedMin, int expectedMax, int provided) { super("Invalid count of arguments provided: " + provided + " expected more than: " + expectedMin + " less than: " + expectedMax); } diff --git a/src/main/java/org/pkwmtt/exceptions/UserNotFoundException.java b/src/main/java/org/pkwmtt/exceptions/UserNotFoundException.java new file mode 100644 index 0000000..72400d3 --- /dev/null +++ b/src/main/java/org/pkwmtt/exceptions/UserNotFoundException.java @@ -0,0 +1,8 @@ +package org.pkwmtt.exceptions; + +public class UserNotFoundException + extends IllegalArgumentException { + public UserNotFoundException(String message){ + super(message); + } +} diff --git a/src/main/java/org/pkwmtt/exceptions/WrongOTPFormatException.java b/src/main/java/org/pkwmtt/exceptions/WrongOTPFormatException.java new file mode 100644 index 0000000..414d347 --- /dev/null +++ b/src/main/java/org/pkwmtt/exceptions/WrongOTPFormatException.java @@ -0,0 +1,8 @@ +package org.pkwmtt.exceptions; + +public class WrongOTPFormatException + extends IllegalArgumentException { + public WrongOTPFormatException (String message) { + super(message); + } +} From 240797cab28927b7f9eb4f1b7b075f75ed51476b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Florczak?= <84631301+florczaq@users.noreply.github.com> Date: Wed, 3 Sep 2025 20:32:04 +0200 Subject: [PATCH 083/116] Fix exception --- .../pkwmtt/exceptions/UnsupportedCountOfArgumentsException.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/pkwmtt/exceptions/UnsupportedCountOfArgumentsException.java b/src/main/java/org/pkwmtt/exceptions/UnsupportedCountOfArgumentsException.java index 709978a..fc3718c 100644 --- a/src/main/java/org/pkwmtt/exceptions/UnsupportedCountOfArgumentsException.java +++ b/src/main/java/org/pkwmtt/exceptions/UnsupportedCountOfArgumentsException.java @@ -1,7 +1,7 @@ package org.pkwmtt.exceptions; public class UnsupportedCountOfArgumentsException extends RuntimeException { - public UnsupportedCountOfArgumentsException(int expectedMin, int expectedMax, int provided) { + public UnsupportedCountOfArgumentsException(int expectedMin, int expectedMax, Integer provided) { super("Invalid count of arguments provided: " + provided + " expected more than: " + expectedMin + " less than: " + expectedMax); } From b7bfa4aecde7023bb757bc048d7c6d9ad9036635 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Florczak?= <84631301+florczaq@users.noreply.github.com> Date: Wed, 3 Sep 2025 20:39:12 +0200 Subject: [PATCH 084/116] Fix packages errors --- logs/app.log | 28 +++++++++++++++++++ .../pkwmtt/examCalendar/entity/OTPCode.java | 12 ++++++-- .../repository/GeneralGroupRepository.java | 3 ++ .../repository/OTPCodeRepository.java | 7 ----- .../repository/UserRepository.java | 3 ++ 5 files changed, 43 insertions(+), 10 deletions(-) delete mode 100644 src/main/java/org/pkwmtt/examCalendar/repository/OTPCodeRepository.java diff --git a/logs/app.log b/logs/app.log index e69de29..1c61e4d 100644 --- a/logs/app.log +++ b/logs/app.log @@ -0,0 +1,28 @@ +2025-09-03 20:37:03 ERROR o.s.b.d.LoggingFailureAnalysisReporter - + +*************************** +APPLICATION FAILED TO START +*************************** + +Description: + +The bean 'OTPCodeRepository', defined in org.pkwmtt.examCalendar.repository.OTPCodeRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration, could not be registered. A bean with that name has already been defined in org.pkwmtt.otp.repository.OTPCodeRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration and overriding is disabled. + +Action: + +Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true + +2025-09-03 20:38:11 ERROR o.s.b.d.LoggingFailureAnalysisReporter - + +*************************** +APPLICATION FAILED TO START +*************************** + +Description: + +Web server failed to start. Port 8080 was already in use. + +Action: + +Identify and stop the process that's listening on port 8080 or configure this application to listen on another port. + diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/OTPCode.java b/src/main/java/org/pkwmtt/examCalendar/entity/OTPCode.java index da9997c..2694908 100644 --- a/src/main/java/org/pkwmtt/examCalendar/entity/OTPCode.java +++ b/src/main/java/org/pkwmtt/examCalendar/entity/OTPCode.java @@ -19,14 +19,20 @@ public class OTPCode { @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "otp_code_id") private Integer otpCodeId; - + @Column(nullable = false) private String code; - + @Column(nullable = false) private LocalDateTime expire; - + @OneToOne @JoinColumn(name = "general_group_id", nullable = false) private GeneralGroup generalGroup; + + public OTPCode (String code, GeneralGroup generalGroup) { + this.code = code; + this.generalGroup = generalGroup; + this.expire = LocalDateTime.now().plusDays(1); + } } diff --git a/src/main/java/org/pkwmtt/examCalendar/repository/GeneralGroupRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/GeneralGroupRepository.java index 62f4fbb..fa787aa 100644 --- a/src/main/java/org/pkwmtt/examCalendar/repository/GeneralGroupRepository.java +++ b/src/main/java/org/pkwmtt/examCalendar/repository/GeneralGroupRepository.java @@ -3,5 +3,8 @@ import org.pkwmtt.examCalendar.entity.GeneralGroup; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + public interface GeneralGroupRepository extends JpaRepository { + Optional findByName (String generalGroupName); } \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/examCalendar/repository/OTPCodeRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/OTPCodeRepository.java deleted file mode 100644 index 848b4d4..0000000 --- a/src/main/java/org/pkwmtt/examCalendar/repository/OTPCodeRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.pkwmtt.examCalendar.repository; - -import org.pkwmtt.examCalendar.entity.OTPCode; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface OTPCodeRepository extends JpaRepository { -} \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/examCalendar/repository/UserRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/UserRepository.java index 14065d5..3a195fa 100644 --- a/src/main/java/org/pkwmtt/examCalendar/repository/UserRepository.java +++ b/src/main/java/org/pkwmtt/examCalendar/repository/UserRepository.java @@ -1,5 +1,6 @@ package org.pkwmtt.examCalendar.repository; +import org.pkwmtt.examCalendar.entity.GeneralGroup; import org.pkwmtt.examCalendar.entity.User; import org.springframework.data.jpa.repository.JpaRepository; @@ -7,4 +8,6 @@ public interface UserRepository extends JpaRepository { Optional findByEmail (String email); + + Optional findByGeneralGroup (GeneralGroup generalGroup); } \ No newline at end of file From 8173cbb697a85d61c92a767785fb8924d100574e Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Wed, 3 Sep 2025 21:09:41 +0200 Subject: [PATCH 085/116] delete status package --- .../pkwmtt/status/DatabaseStatusChecker.java | 26 ---------- .../status/SystemStatusCheckerService.java | 47 ------------------- .../pkwmtt/status/SystemStatusController.java | 20 -------- 3 files changed, 93 deletions(-) delete mode 100644 src/main/java/org/pkwmtt/status/DatabaseStatusChecker.java delete mode 100644 src/main/java/org/pkwmtt/status/SystemStatusCheckerService.java delete mode 100644 src/main/java/org/pkwmtt/status/SystemStatusController.java diff --git a/src/main/java/org/pkwmtt/status/DatabaseStatusChecker.java b/src/main/java/org/pkwmtt/status/DatabaseStatusChecker.java deleted file mode 100644 index 6af41a8..0000000 --- a/src/main/java/org/pkwmtt/status/DatabaseStatusChecker.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.pkwmtt.status; - - -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import javax.sql.DataSource; -import java.sql.SQLException; - -@Slf4j -@Service -public class DatabaseStatusChecker { - @Getter - private static boolean enabled = false; - - @Autowired - DatabaseStatusChecker (DataSource dataSource) { - try { - enabled = dataSource.getConnection().isValid(2); - } catch (SQLException e) { - log.error("Couldn't check database connection. Service will be unavailable"); - } - } -} diff --git a/src/main/java/org/pkwmtt/status/SystemStatusCheckerService.java b/src/main/java/org/pkwmtt/status/SystemStatusCheckerService.java deleted file mode 100644 index e7a4731..0000000 --- a/src/main/java/org/pkwmtt/status/SystemStatusCheckerService.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.pkwmtt.status; - -import jakarta.annotation.PostConstruct; -import org.pkwmtt.mail.config.MailConfig; -import org.pkwmtt.timetable.TimetableCacheService; -import org.pkwmtt.timetable.TimetableService; -import org.springframework.stereotype.Service; - - -@Service -public class SystemStatusCheckerService { - - private String mailingStatus; - private String databaseStatus; - private String cacheStatus; - private String timetableStatus; - - SystemStatusCheckerService () { - checkStatuses(); - } - - @PostConstruct - private void checkStatuses () { - mailingStatus = assignStatus(MailConfig.isEnabled()); - databaseStatus = assignStatus(DatabaseStatusChecker.isEnabled()); - timetableStatus = assignStatus(TimetableService.isEnabled()); - cacheStatus = assignStatus(TimetableCacheService.isCacheAvailable()); - } - - public String getStatus () { - return String.format( - """ - Server: ✅; - Services: - Mail: %s - Database: %s, - Timetable: %s, - Cache: %s - """, mailingStatus, databaseStatus, timetableStatus, cacheStatus - ); - } - - - private String assignStatus (boolean condition) { - return condition ? "✅" : "❌"; - } -} diff --git a/src/main/java/org/pkwmtt/status/SystemStatusController.java b/src/main/java/org/pkwmtt/status/SystemStatusController.java deleted file mode 100644 index dd055c1..0000000 --- a/src/main/java/org/pkwmtt/status/SystemStatusController.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.pkwmtt.status; - -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/pkwmtt/system/status") -@RequiredArgsConstructor -public class SystemStatusController { - private final SystemStatusCheckerService service; - - @GetMapping - public ResponseEntity getSystemStatus () { - return ResponseEntity.ok(service.getStatus()); - } - -} From 326a002130865d5f23ad658cbbd565d09a7cb2e8 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Wed, 3 Sep 2025 21:38:55 +0200 Subject: [PATCH 086/116] update test database configuration --- .../examCalendar/ExamControllerTest.java | 3 + .../repository/ExamRepositoryTest.java | 3 + src/test/resources/application.properties | 2 +- src/test/resources/schema.sql | 61 ------------------- 4 files changed, 7 insertions(+), 62 deletions(-) delete mode 100644 src/test/resources/schema.sql diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java index 6155ffd..194e9b3 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java @@ -13,6 +13,8 @@ import org.pkwmtt.examCalendar.repository.ExamTypeRepository; import org.pkwmtt.examCalendar.repository.GroupRepository; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.jdbc.EmbeddedDatabaseConnection; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.web.servlet.MockMvc; @@ -37,6 +39,7 @@ */ @SpringBootTest @AutoConfigureMockMvc +@AutoConfigureTestDatabase(connection = EmbeddedDatabaseConnection.H2) class ExamControllerTest { @Autowired diff --git a/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java b/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java index 651d24d..435d157 100644 --- a/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java @@ -8,6 +8,8 @@ import org.pkwmtt.examCalendar.entity.ExamType; import org.pkwmtt.examCalendar.entity.StudentGroup; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.jdbc.EmbeddedDatabaseConnection; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import java.time.LocalDateTime; @@ -21,6 +23,7 @@ @Slf4j @DataJpaTest @TestInstance(TestInstance.Lifecycle.PER_CLASS) +@AutoConfigureTestDatabase(connection = EmbeddedDatabaseConnection.H2) class ExamRepositoryTest { @Autowired diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index 1bdfc2a..e434062 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -6,7 +6,7 @@ spring.jpa.show-sql=true spring.jpa.database-platform=org.hibernate.dialect.H2Dialect spring.datasource.driver-class-name=org.h2.Driver spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false -spring.jpa.hibernate.ddl-auto=none +spring.jpa.hibernate.ddl-auto=create-drop spring.datasource.hikari.initialization-fail-timeout=0 diff --git a/src/test/resources/schema.sql b/src/test/resources/schema.sql deleted file mode 100644 index d598883..0000000 --- a/src/test/resources/schema.sql +++ /dev/null @@ -1,61 +0,0 @@ -DROP TABLE IF EXISTS exams_groups; -DROP TABLE IF EXISTS exams; -DROP TABLE IF EXISTS exam_type; -DROP TABLE IF EXISTS otp_codes; -DROP TABLE IF EXISTS users; -DROP TABLE IF EXISTS student_groups; -DROP TABLE IF EXISTS general_group; - -CREATE TABLE exam_type ( - exam_type_id INT AUTO_INCREMENT PRIMARY KEY, - name VARCHAR(255) NOT NULL -); - -CREATE TABLE general_group ( - general_group_id INT AUTO_INCREMENT PRIMARY KEY, - name VARCHAR(255) NOT NULL -); - -CREATE TABLE student_groups ( - group_id INT AUTO_INCREMENT PRIMARY KEY, - name VARCHAR(255) NOT NULL UNIQUE -); - -CREATE TABLE exams ( - exam_id INT AUTO_INCREMENT PRIMARY KEY, - title VARCHAR(255) NOT NULL, - description VARCHAR(255), - exam_date TIMESTAMP NOT NULL, - exam_type_id INT NOT NULL, - CONSTRAINT fk_exams_exam_type FOREIGN KEY (exam_type_id) - REFERENCES exam_type (exam_type_id) ON DELETE CASCADE -); - -CREATE TABLE exams_groups ( - exam_group_id INT AUTO_INCREMENT PRIMARY KEY, - exam_id INT NOT NULL, - group_id INT NOT NULL, - CONSTRAINT fk_exams_groups_exam FOREIGN KEY (exam_id) - REFERENCES exams (exam_id) ON DELETE CASCADE, - CONSTRAINT fk_exams_groups_group FOREIGN KEY (group_id) - REFERENCES student_groups (group_id) ON DELETE CASCADE -); - -CREATE TABLE otp_codes ( - otp_code_id INT AUTO_INCREMENT PRIMARY KEY, - code VARCHAR(255) NOT NULL, - expire TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - general_group_id INT NOT NULL, - CONSTRAINT fk_otp_codes_general_group FOREIGN KEY (general_group_id) - REFERENCES general_group (general_group_id) ON DELETE CASCADE -); - -CREATE TABLE users ( - user_id INT AUTO_INCREMENT PRIMARY KEY, - general_group_id INT NOT NULL, - email VARCHAR(255) NOT NULL, - is_active BOOLEAN NOT NULL DEFAULT TRUE, - role VARCHAR(20) NOT NULL DEFAULT 'REPRESENTATIVE', - CONSTRAINT fk_users_general_group FOREIGN KEY (general_group_id) - REFERENCES general_group (general_group_id) ON DELETE CASCADE -); \ No newline at end of file From 68a13ea8d4f74bcee2e2ce7897456dd404983104 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Wed, 3 Sep 2025 21:55:44 +0200 Subject: [PATCH 087/116] update application properties --- src/test/resources/application-test.properties | 1 - 1 file changed, 1 deletion(-) delete mode 100644 src/test/resources/application-test.properties diff --git a/src/test/resources/application-test.properties b/src/test/resources/application-test.properties deleted file mode 100644 index 6224c97..0000000 --- a/src/test/resources/application-test.properties +++ /dev/null @@ -1 +0,0 @@ -main.url = http://localhost:9999/ \ No newline at end of file From 5e5a2fd5570bc88f27f7de41de59cffde2e7fdde Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Wed, 3 Sep 2025 22:09:54 +0200 Subject: [PATCH 088/116] revert update application properties --- src/test/resources/application-test.properties | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/test/resources/application-test.properties diff --git a/src/test/resources/application-test.properties b/src/test/resources/application-test.properties new file mode 100644 index 0000000..6224c97 --- /dev/null +++ b/src/test/resources/application-test.properties @@ -0,0 +1 @@ +main.url = http://localhost:9999/ \ No newline at end of file From 780b7d3a8fb08e0d2214b49837fc26ceb69ef24b Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Wed, 3 Sep 2025 22:20:38 +0200 Subject: [PATCH 089/116] application properties profiles --- src/test/java/org/pkwmtt/cache/CacheConfigTest.java | 2 ++ src/test/java/org/pkwmtt/cache/CacheInspector.java | 2 ++ .../java/org/pkwmtt/timetable/TimetableCacheServiceTest.java | 2 ++ src/test/java/org/pkwmtt/timetable/TimetableControllerTest.java | 2 ++ src/test/java/org/pkwmtt/timetable/TimetableServiceTest.java | 2 ++ .../java/org/pkwmtt/timetable/parser/ParserServiceTest.java | 2 ++ 6 files changed, 12 insertions(+) diff --git a/src/test/java/org/pkwmtt/cache/CacheConfigTest.java b/src/test/java/org/pkwmtt/cache/CacheConfigTest.java index c5c7317..53e9c30 100644 --- a/src/test/java/org/pkwmtt/cache/CacheConfigTest.java +++ b/src/test/java/org/pkwmtt/cache/CacheConfigTest.java @@ -7,6 +7,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cache.CacheManager; +import org.springframework.test.context.ActiveProfiles; import test.TestConfig; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; @@ -16,6 +17,7 @@ import static org.junit.jupiter.api.Assertions.assertAll; @SpringBootTest +@ActiveProfiles("test") class CacheConfigTest extends TestConfig { @Autowired diff --git a/src/test/java/org/pkwmtt/cache/CacheInspector.java b/src/test/java/org/pkwmtt/cache/CacheInspector.java index 0b38463..fdf4749 100644 --- a/src/test/java/org/pkwmtt/cache/CacheInspector.java +++ b/src/test/java/org/pkwmtt/cache/CacheInspector.java @@ -6,6 +6,7 @@ import org.springframework.cache.CacheManager; import org.springframework.cache.caffeine.CaffeineCache; import org.springframework.stereotype.Component; +import org.springframework.test.context.ActiveProfiles; import java.util.Map; @@ -13,6 +14,7 @@ @Component @RequiredArgsConstructor +@ActiveProfiles("test") @SuppressWarnings("unused") public class CacheInspector { diff --git a/src/test/java/org/pkwmtt/timetable/TimetableCacheServiceTest.java b/src/test/java/org/pkwmtt/timetable/TimetableCacheServiceTest.java index 0a461ae..2ae1a13 100644 --- a/src/test/java/org/pkwmtt/timetable/TimetableCacheServiceTest.java +++ b/src/test/java/org/pkwmtt/timetable/TimetableCacheServiceTest.java @@ -9,6 +9,7 @@ import org.pkwmtt.timetable.dto.TimetableDTO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; import test.TestConfig; import java.util.List; @@ -22,6 +23,7 @@ @Slf4j @SpringBootTest +@ActiveProfiles("test") class TimetableCacheServiceTest extends TestConfig { @Autowired TimetableCacheService cachedService; diff --git a/src/test/java/org/pkwmtt/timetable/TimetableControllerTest.java b/src/test/java/org/pkwmtt/timetable/TimetableControllerTest.java index 9c5c290..4421ee0 100644 --- a/src/test/java/org/pkwmtt/timetable/TimetableControllerTest.java +++ b/src/test/java/org/pkwmtt/timetable/TimetableControllerTest.java @@ -13,6 +13,7 @@ import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.test.context.ActiveProfiles; import test.TestConfig; import java.time.LocalDateTime; @@ -27,6 +28,7 @@ import static org.junit.jupiter.api.Assertions.*; @Slf4j +@ActiveProfiles("test") class TimetableControllerTest extends TestConfig { @LocalServerPort diff --git a/src/test/java/org/pkwmtt/timetable/TimetableServiceTest.java b/src/test/java/org/pkwmtt/timetable/TimetableServiceTest.java index 58f2055..72ec1dc 100644 --- a/src/test/java/org/pkwmtt/timetable/TimetableServiceTest.java +++ b/src/test/java/org/pkwmtt/timetable/TimetableServiceTest.java @@ -14,12 +14,14 @@ import org.pkwmtt.exceptions.SpecifiedSubGroupDoesntExistsException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; import test.TestConfig; import java.util.List; import java.util.regex.Pattern; @SpringBootTest +@ActiveProfiles("test") class TimetableServiceTest extends TestConfig { @Autowired diff --git a/src/test/java/org/pkwmtt/timetable/parser/ParserServiceTest.java b/src/test/java/org/pkwmtt/timetable/parser/ParserServiceTest.java index 9dd2567..4a0f087 100644 --- a/src/test/java/org/pkwmtt/timetable/parser/ParserServiceTest.java +++ b/src/test/java/org/pkwmtt/timetable/parser/ParserServiceTest.java @@ -9,6 +9,7 @@ import org.pkwmtt.timetable.dto.TimetableDTO; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.ActiveProfiles; import test.TestConfig; import java.io.IOException; @@ -20,6 +21,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; @Suite.SuiteClasses(TimetableParserService.class) +@ActiveProfiles("test") class ParserServiceTest extends TestConfig { TimetableParserService parserService; From a1f031c2c8b68bd9f7b73777923ba9a1962e24f2 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Thu, 4 Sep 2025 16:23:19 +0200 Subject: [PATCH 090/116] replace timetableService with mock in ExamControllerTests --- .../examCalendar/ExamControllerTest.java | 25 +++++++++++++++++++ .../timetable/TimetableCacheServiceTest.java | 1 - .../timetable/TimetableControllerTest.java | 1 - .../timetable/TimetableServiceTest.java | 1 - .../timetable/parser/ParserServiceTest.java | 1 - 5 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java index 194e9b3..0bf5f3f 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java @@ -12,11 +12,13 @@ import org.pkwmtt.examCalendar.repository.ExamRepository; import org.pkwmtt.examCalendar.repository.ExamTypeRepository; import org.pkwmtt.examCalendar.repository.GroupRepository; +import org.pkwmtt.timetable.TimetableService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.jdbc.EmbeddedDatabaseConnection; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultMatcher; @@ -30,6 +32,7 @@ import static org.hamcrest.Matchers.containsString; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -53,9 +56,13 @@ class ExamControllerTest { @Autowired private ObjectMapper mapper; + @Autowired private GroupRepository groupRepository; + @MockitoBean + private TimetableService timetableService; + @BeforeEach void setupBeforeEach() { examRepository.deleteAll(); @@ -76,6 +83,9 @@ void addExamWithCorrectData() throws Exception { ExamDto examDtoRequest = createExampleExamDto("Project"); String json = mapper.writeValueAsString(examDtoRequest); + when(timetableService.getGeneralGroupList()).thenReturn(List.of("12K1","12K2","12K3")); + when(timetableService.getAvailableSubGroups("12K2")).thenReturn(List.of("K04","L04","P04")); + MvcResult result = mockMvc.perform(MockMvcRequestBuilders .post("/pkwmtt/api/v1/exams") .contentType("application/json") @@ -142,6 +152,9 @@ void addExamWithBlankExamDescription() throws Exception { .generalGroups(Set.of("12K2")) .subgroups(Set.of("L04")) .build(); + + when(timetableService.getGeneralGroupList()).thenReturn(List.of("12K1","12K2","12K3")); + when(timetableService.getAvailableSubGroups("12K2")).thenReturn(List.of("K04","L04","P04")); // when MvcResult result = assertPostRequest(status().isCreated(), requestData); @@ -222,6 +235,9 @@ void addExamWithBlankSubgroups() throws Exception { // null subgroups .build(); + when(timetableService.getGeneralGroupList()).thenReturn(List.of("12K1","12K2","12K3")); + when(timetableService.getAvailableSubGroups("12K2")).thenReturn(List.of("K04","L04","P04")); + // when MvcResult result = assertPostRequest(status().isCreated(), requestData); // then @@ -246,6 +262,9 @@ void addExamWithMultipleGeneralGroupsAndSubgroups() throws Exception { .subgroups(Set.of("L04")) .build(); + when(timetableService.getGeneralGroupList()).thenReturn(List.of("12K1","12K2","12K3")); + when(timetableService.getAvailableSubGroups("12K2")).thenReturn(List.of("K04","L04","P04")); + // when MvcResult result = assertPostRequest(status().isBadRequest(), requestData); // then @@ -364,6 +383,9 @@ void addExamWithNonExistingExamType() throws Exception { .subgroups(Set.of("L04")) .build(); + when(timetableService.getGeneralGroupList()).thenReturn(List.of("12K1","12K2","12K3")); + when(timetableService.getAvailableSubGroups("12K2")).thenReturn(List.of("K04","L04","P04")); + // when MvcResult result = assertPostRequest(status().isBadRequest(), requestData); @@ -384,6 +406,9 @@ void modifyExamWithCorrectData() throws Exception { int id = examRepository.save(exam).getExamId(); ExamDto examDto = createExampleExamDto(examType.getName()); + when(timetableService.getGeneralGroupList()).thenReturn(List.of("12K1","12K2","12K3")); + when(timetableService.getAvailableSubGroups("12K2")).thenReturn(List.of("K04","L04","P04")); + // when assertPutRequest(status().isNoContent(), examDto, id); diff --git a/src/test/java/org/pkwmtt/timetable/TimetableCacheServiceTest.java b/src/test/java/org/pkwmtt/timetable/TimetableCacheServiceTest.java index 2ae1a13..09e0014 100644 --- a/src/test/java/org/pkwmtt/timetable/TimetableCacheServiceTest.java +++ b/src/test/java/org/pkwmtt/timetable/TimetableCacheServiceTest.java @@ -23,7 +23,6 @@ @Slf4j @SpringBootTest -@ActiveProfiles("test") class TimetableCacheServiceTest extends TestConfig { @Autowired TimetableCacheService cachedService; diff --git a/src/test/java/org/pkwmtt/timetable/TimetableControllerTest.java b/src/test/java/org/pkwmtt/timetable/TimetableControllerTest.java index 4421ee0..e9cf1a0 100644 --- a/src/test/java/org/pkwmtt/timetable/TimetableControllerTest.java +++ b/src/test/java/org/pkwmtt/timetable/TimetableControllerTest.java @@ -28,7 +28,6 @@ import static org.junit.jupiter.api.Assertions.*; @Slf4j -@ActiveProfiles("test") class TimetableControllerTest extends TestConfig { @LocalServerPort diff --git a/src/test/java/org/pkwmtt/timetable/TimetableServiceTest.java b/src/test/java/org/pkwmtt/timetable/TimetableServiceTest.java index 72ec1dc..357ccf2 100644 --- a/src/test/java/org/pkwmtt/timetable/TimetableServiceTest.java +++ b/src/test/java/org/pkwmtt/timetable/TimetableServiceTest.java @@ -21,7 +21,6 @@ import java.util.regex.Pattern; @SpringBootTest -@ActiveProfiles("test") class TimetableServiceTest extends TestConfig { @Autowired diff --git a/src/test/java/org/pkwmtt/timetable/parser/ParserServiceTest.java b/src/test/java/org/pkwmtt/timetable/parser/ParserServiceTest.java index 4a0f087..b9f856f 100644 --- a/src/test/java/org/pkwmtt/timetable/parser/ParserServiceTest.java +++ b/src/test/java/org/pkwmtt/timetable/parser/ParserServiceTest.java @@ -21,7 +21,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue; @Suite.SuiteClasses(TimetableParserService.class) -@ActiveProfiles("test") class ParserServiceTest extends TestConfig { TimetableParserService parserService; From a97e2053e561c3e1df7fdc39c662af1abc1cd78a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Florczak?= <84631301+florczaq@users.noreply.github.com> Date: Thu, 4 Sep 2025 16:53:21 +0200 Subject: [PATCH 091/116] Generate and return token; Delete structure package; Check if provided general group exists; --- logs/app.log | 438 ++++++++++++++++++ .../java/org/pkwmtt/otp/OTPController.java | 9 +- .../org/pkwmtt/otp/OTPExceptionHandler.java | 8 +- src/main/java/org/pkwmtt/otp/OTPService.java | 114 +++-- .../pkwmtt/security/token/dto/UserDTO.java | 12 +- .../pkwmtt/status/DatabaseStatusChecker.java | 26 -- .../status/SystemStatusCheckerService.java | 47 -- .../pkwmtt/status/SystemStatusController.java | 20 - .../timetable/TimetableCacheService.java | 6 - .../pkwmtt/timetable/TimetableService.java | 3 - 10 files changed, 527 insertions(+), 156 deletions(-) delete mode 100644 src/main/java/org/pkwmtt/status/DatabaseStatusChecker.java delete mode 100644 src/main/java/org/pkwmtt/status/SystemStatusCheckerService.java delete mode 100644 src/main/java/org/pkwmtt/status/SystemStatusController.java diff --git a/logs/app.log b/logs/app.log index 1c61e4d..def9579 100644 --- a/logs/app.log +++ b/logs/app.log @@ -26,3 +26,441 @@ Action: Identify and stop the process that's listening on port 8080 or configure this application to listen on another port. +2025-09-04 16:31:23 ERROR o.s.b.d.LoggingFailureAnalysisReporter - + +*************************** +APPLICATION FAILED TO START +*************************** + +Description: + +Parameter 0 of constructor in org.pkwmtt.otp.OTPController required a bean of type 'org.pkwmtt.otp.OTPService' that could not be found. + + +Action: + +Consider defining a bean of type 'org.pkwmtt.otp.OTPService' in your configuration. + +2025-09-04 16:39:29 ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.IllegalArgumentException: Illegal base64 character 5f] with root cause +java.lang.IllegalArgumentException: Illegal base64 character 5f + at java.base/java.util.Base64$Decoder.decode0(Base64.java:852) + at java.base/java.util.Base64$Decoder.decode(Base64.java:570) + at java.base/java.util.Base64$Decoder.decode(Base64.java:593) + at org.pkwmtt.security.token.JwtServiceImpl.decodeSecretKey(JwtServiceImpl.java:49) + at org.pkwmtt.security.token.JwtServiceImpl.generateToken(JwtServiceImpl.java:37) + at org.pkwmtt.otp.OTPService.generateTokenForRepresentative(OTPService.java:49) + at org.pkwmtt.otp.OTPController.authenticate(OTPController.java:25) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:258) + at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:191) + at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:986) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:891) + at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) + at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) + at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) + at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) + at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564) + at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.security.web.FilterChainProxy.lambda$doFilterInternal$3(FilterChainProxy.java:231) + at org.springframework.security.web.ObservationFilterChainDecorator$FilterObservation$SimpleFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:479) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:340) + at org.springframework.security.web.ObservationFilterChainDecorator.lambda$wrapSecured$0(ObservationFilterChainDecorator.java:82) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:128) + at org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:101) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:125) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:131) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:85) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) + at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$0(ObservationFilterChainDecorator.java:323) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:224) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233) + at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.ServletRequestPathFilter.doFilter(ServletRequestPathFilter.java:52) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebSecurityConfiguration.java:319) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.servlet.handler.HandlerMappingIntrospector.lambda$createCacheFilter$4(HandlerMappingIntrospector.java:267) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebMvcSecurityConfiguration.java:240) + at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:362) + at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:278) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:114) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) + at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) + at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) + at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:116) + at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) + at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) + at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) + at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:398) + at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) + at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:903) + at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1769) + at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1189) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:658) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) + at java.base/java.lang.Thread.run(Thread.java:1583) +2025-09-04 16:40:33 ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.IllegalArgumentException: Illegal base64 character 5f] with root cause +java.lang.IllegalArgumentException: Illegal base64 character 5f + at java.base/java.util.Base64$Decoder.decode0(Base64.java:852) + at java.base/java.util.Base64$Decoder.decode(Base64.java:570) + at java.base/java.util.Base64$Decoder.decode(Base64.java:593) + at org.pkwmtt.security.token.JwtServiceImpl.decodeSecretKey(JwtServiceImpl.java:49) + at org.pkwmtt.security.token.JwtServiceImpl.generateToken(JwtServiceImpl.java:37) + at org.pkwmtt.otp.OTPService.generateTokenForRepresentative(OTPService.java:49) + at org.pkwmtt.otp.OTPController.authenticate(OTPController.java:25) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:258) + at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:191) + at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:986) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:891) + at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) + at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) + at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) + at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) + at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564) + at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.security.web.FilterChainProxy.lambda$doFilterInternal$3(FilterChainProxy.java:231) + at org.springframework.security.web.ObservationFilterChainDecorator$FilterObservation$SimpleFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:479) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:340) + at org.springframework.security.web.ObservationFilterChainDecorator.lambda$wrapSecured$0(ObservationFilterChainDecorator.java:82) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:128) + at org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:101) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:125) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:131) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:85) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) + at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$0(ObservationFilterChainDecorator.java:323) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:224) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233) + at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.ServletRequestPathFilter.doFilter(ServletRequestPathFilter.java:52) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebSecurityConfiguration.java:319) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.servlet.handler.HandlerMappingIntrospector.lambda$createCacheFilter$4(HandlerMappingIntrospector.java:267) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebMvcSecurityConfiguration.java:240) + at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:362) + at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:278) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:114) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) + at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) + at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) + at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:116) + at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) + at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) + at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) + at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:398) + at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) + at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:903) + at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1769) + at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1189) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:658) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) + at java.base/java.lang.Thread.run(Thread.java:1583) +2025-09-04 16:49:48 ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.pkwmtt.exceptions.SpecifiedGeneralGroupDoesntExistsException: Specified general group doesn't exists] with root cause +org.pkwmtt.exceptions.SpecifiedGeneralGroupDoesntExistsException: Specified general group doesn't exists + at org.pkwmtt.otp.OTPService.lambda$sendOTPCodes$1(OTPService.java:74) + at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) + at org.pkwmtt.otp.OTPService.sendOTPCodes(OTPService.java:58) + at org.pkwmtt.otp.OTPController.generateCodes(OTPController.java:28) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:258) + at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:191) + at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:986) + at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:891) + at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) + at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) + at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) + at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) + at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590) + at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) + at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:108) + at org.springframework.security.web.FilterChainProxy.lambda$doFilterInternal$3(FilterChainProxy.java:231) + at org.springframework.security.web.ObservationFilterChainDecorator$FilterObservation$SimpleFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:479) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$1(ObservationFilterChainDecorator.java:340) + at org.springframework.security.web.ObservationFilterChainDecorator.lambda$wrapSecured$0(ObservationFilterChainDecorator.java:82) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:128) + at org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:101) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:125) + at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:131) + at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:85) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107) + at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) + at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82) + at org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) + at org.springframework.security.web.ObservationFilterChainDecorator$AroundFilterObservation$SimpleAroundFilterObservation.lambda$wrap$0(ObservationFilterChainDecorator.java:323) + at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:224) + at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) + at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233) + at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.ServletRequestPathFilter.doFilter(ServletRequestPathFilter.java:52) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebSecurityConfiguration.java:319) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.servlet.handler.HandlerMappingIntrospector.lambda$createCacheFilter$4(HandlerMappingIntrospector.java:267) + at org.springframework.web.filter.CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113) + at org.springframework.web.filter.CompositeFilter.doFilter(CompositeFilter.java:74) + at org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$CompositeFilterChainProxy.doFilter(WebMvcSecurityConfiguration.java:240) + at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:362) + at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:278) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.ServerHttpObservationFilter.doFilterInternal(ServerHttpObservationFilter.java:114) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) + at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) + at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) + at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) + at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) + at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) + at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) + at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:116) + at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) + at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) + at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) + at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:398) + at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) + at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:903) + at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1769) + at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) + at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1189) + at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:658) + at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) + at java.base/java.lang.Thread.run(Thread.java:1583) diff --git a/src/main/java/org/pkwmtt/otp/OTPController.java b/src/main/java/org/pkwmtt/otp/OTPController.java index 7455482..8cd6731 100644 --- a/src/main/java/org/pkwmtt/otp/OTPController.java +++ b/src/main/java/org/pkwmtt/otp/OTPController.java @@ -1,11 +1,9 @@ package org.pkwmtt.otp; +import com.mysql.cj.exceptions.WrongArgumentException; import lombok.RequiredArgsConstructor; -import org.pkwmtt.exceptions.MailCouldNotBeSendException; -import org.pkwmtt.exceptions.OTPCodeNotFoundException; -import org.pkwmtt.exceptions.UserNotFoundException; -import org.pkwmtt.exceptions.WrongOTPFormatException; +import org.pkwmtt.exceptions.*; import org.pkwmtt.otp.dto.OTPRequest; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -25,7 +23,8 @@ public ResponseEntity authenticate (@RequestParam(name = "c") String cod } @PostMapping("/codes/generate") - public ResponseEntity generateCodes (@RequestBody List request) throws MailCouldNotBeSendException { + public ResponseEntity generateCodes (@RequestBody List request) + throws MailCouldNotBeSendException, WrongArgumentException, SpecifiedGeneralGroupDoesntExistsException { service.sendOTPCodes(request); return ResponseEntity.ok().build(); } diff --git a/src/main/java/org/pkwmtt/otp/OTPExceptionHandler.java b/src/main/java/org/pkwmtt/otp/OTPExceptionHandler.java index d438de4..0dc2849 100644 --- a/src/main/java/org/pkwmtt/otp/OTPExceptionHandler.java +++ b/src/main/java/org/pkwmtt/otp/OTPExceptionHandler.java @@ -1,10 +1,8 @@ package org.pkwmtt.otp; -import org.pkwmtt.exceptions.MailCouldNotBeSendException; -import org.pkwmtt.exceptions.OTPCodeNotFoundException; -import org.pkwmtt.exceptions.UserNotFoundException; -import org.pkwmtt.exceptions.WrongOTPFormatException; +import com.mysql.cj.exceptions.WrongArgumentException; +import org.pkwmtt.exceptions.*; import org.pkwmtt.exceptions.dto.ErrorResponseDTO; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -13,7 +11,7 @@ @RestControllerAdvice(assignableTypes = {OTPController.class}) public class OTPExceptionHandler { - @ExceptionHandler({OTPCodeNotFoundException.class, WrongOTPFormatException.class, UserNotFoundException.class}) + @ExceptionHandler({OTPCodeNotFoundException.class, WrongOTPFormatException.class, UserNotFoundException.class, WrongArgumentException.class, SpecifiedGeneralGroupDoesntExistsException.class}) public ResponseEntity handleBadRequests (Exception e) { return new ResponseEntity<>(new ErrorResponseDTO(e.getMessage()), HttpStatus.BAD_REQUEST); } diff --git a/src/main/java/org/pkwmtt/otp/OTPService.java b/src/main/java/org/pkwmtt/otp/OTPService.java index 3d8f74a..4ca962b 100644 --- a/src/main/java/org/pkwmtt/otp/OTPService.java +++ b/src/main/java/org/pkwmtt/otp/OTPService.java @@ -1,26 +1,31 @@ package org.pkwmtt.otp; +import com.mysql.cj.exceptions.WrongArgumentException; import jakarta.mail.MessagingException; import lombok.RequiredArgsConstructor; import org.pkwmtt.examCalendar.entity.GeneralGroup; import org.pkwmtt.examCalendar.entity.OTPCode; +import org.pkwmtt.examCalendar.entity.User; +import org.pkwmtt.examCalendar.enums.Role; import org.pkwmtt.examCalendar.repository.GeneralGroupRepository; import org.pkwmtt.examCalendar.repository.UserRepository; -import org.pkwmtt.exceptions.MailCouldNotBeSendException; -import org.pkwmtt.exceptions.OTPCodeNotFoundException; -import org.pkwmtt.exceptions.UserNotFoundException; -import org.pkwmtt.exceptions.WrongOTPFormatException; +import org.pkwmtt.exceptions.*; import org.pkwmtt.mail.EmailService; import org.pkwmtt.mail.dto.MailDTO; import org.pkwmtt.otp.dto.OTPRequest; import org.pkwmtt.otp.repository.OTPCodeRepository; +import org.pkwmtt.security.token.JwtServiceImpl; +import org.pkwmtt.security.token.dto.UserDTO; +import org.pkwmtt.timetable.TimetableService; import org.springframework.stereotype.Service; import java.util.List; import java.util.Optional; import java.util.Random; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -29,74 +34,94 @@ public class OTPService { private final UserRepository userRepository; private final GeneralGroupRepository generalGroupRepository; private final EmailService emailService; - - private GeneralGroup getGeneralGroupAssignedToCode (String code) throws OTPCodeNotFoundException, WrongOTPFormatException { - this.validateCode(code); - - Optional result = repository.findByCode(code); - - if (result.isEmpty()) { - throw new OTPCodeNotFoundException(); - } - - return result.get().getGeneralGroup(); - } - - private void validateCode (String code) throws WrongOTPFormatException { - if (code.length() != 6) { - throw new WrongOTPFormatException("Code should be 6 characters long."); - } - - String regex = "^[A-Z0-9]{6}$"; - Pattern pattern = Pattern.compile(regex); - Matcher matcher = pattern.matcher(code); - - if (!matcher.find()) { - throw new WrongOTPFormatException("Wrong format of provided code."); - } - } + private final JwtServiceImpl jwtService; + private final TimetableService timetableService; public String generateTokenForRepresentative (String code) throws OTPCodeNotFoundException, WrongOTPFormatException, UserNotFoundException { var generalGroup = this.getGeneralGroupAssignedToCode(code); - var user = userRepository .findByGeneralGroup(generalGroup) .orElseThrow(() -> new UserNotFoundException("No user is assigned to this code.")); var userEmail = user.getEmail(); - - //TODO DELETE - String token = "example-token_" + generalGroup.getName(); - - //TODO here generate token with provided credentials - - //Delete used code + String token = jwtService.generateToken(new UserDTO() + .setEmail(userEmail) + .setRole(Role.REPRESENTATIVE) + .setGroup(generalGroup.getName())); repository.deleteByCode(code); return token; } - public void sendOTPCodes (List requests) throws MailCouldNotBeSendException { + public void sendOTPCodes (List requests) + throws MailCouldNotBeSendException, WrongArgumentException, SpecifiedSubGroupDoesntExistsException { requests.forEach(request -> { var code = generateNewCode(); var mail = createMail(request, code); + if (request.getGeneralGroupName().length() > 3) { + throw new WrongArgumentException( + "Wrong general group provided. Make sure you are not providing subgroup. (f.e 12K1 -> wrong, 12K -> good)"); + } + + if (!generalGroupExists(request.getGeneralGroupName())) { + throw new SpecifiedGeneralGroupDoesntExistsException(); + } + try { emailService.send(mail); } catch (MessagingException e) { throw new MailCouldNotBeSendException("Couldn't send mail for group: " + request.getGeneralGroupName()); } + var generalGroup = generalGroupRepository.findByName(request.getGeneralGroupName()); if (generalGroup.isEmpty()) { generalGroup = Optional.of(generalGroupRepository.save(new GeneralGroup(null, request.getGeneralGroupName()))); } + var user = User + .builder() + .email(request.getEmail()) + .generalGroup(generalGroup.get()) + .role(Role.REPRESENTATIVE) + .isActive(true) + .build(); + + userRepository.save(user); + repository.save(new OTPCode(code, generalGroup.get())); }); } + private GeneralGroup getGeneralGroupAssignedToCode (String code) throws OTPCodeNotFoundException, WrongOTPFormatException { + this.validateCode(code); + + Optional result = repository.findByCode(code); + + if (result.isEmpty()) { + throw new OTPCodeNotFoundException(); + } + + return result.get().getGeneralGroup(); + } + + private void validateCode (String code) throws WrongOTPFormatException { + if (code.length() != 6) { + throw new WrongOTPFormatException("Code should be 6 characters long."); + } + + String regex = "^[A-Z0-9]{6}$"; + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(code); + + if (!matcher.find()) { + throw new WrongOTPFormatException("Wrong format of provided code."); + } + } + + private MailDTO createMail (OTPRequest request, String code) { return new MailDTO() .setTitle("Kod Starosty " + request.getGeneralGroupName()) @@ -119,5 +144,16 @@ private String generateNewCode () { return code.toString(); } + private boolean generalGroupExists (String name) { + Set list = timetableService.getGeneralGroupList().stream().map(item -> { + var lastIndex = item.length() - 1; + if (Character.isDigit(item.charAt(lastIndex))) { + return item.substring(0, lastIndex); + } + return item; + }).collect(Collectors.toSet()); + + return list.contains(name); + } } diff --git a/src/main/java/org/pkwmtt/security/token/dto/UserDTO.java b/src/main/java/org/pkwmtt/security/token/dto/UserDTO.java index 7734db6..2c69368 100644 --- a/src/main/java/org/pkwmtt/security/token/dto/UserDTO.java +++ b/src/main/java/org/pkwmtt/security/token/dto/UserDTO.java @@ -1,6 +1,8 @@ package org.pkwmtt.security.token.dto; import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; import org.pkwmtt.examCalendar.entity.GeneralGroup; import org.pkwmtt.examCalendar.entity.User; import org.pkwmtt.examCalendar.enums.Role; @@ -8,16 +10,16 @@ import java.util.Optional; @Data +@NoArgsConstructor +@Accessors(chain = true) public class UserDTO { private String email; private String group; private Role role; - - public UserDTO(User user){ + + public UserDTO (User user) { this.email = user.getEmail(); this.role = user.getRole(); - this.group = Optional.ofNullable(user.getGeneralGroup()) - .map(GeneralGroup::getName) - .orElse(null); + this.group = Optional.ofNullable(user.getGeneralGroup()).map(GeneralGroup::getName).orElse(null); } } diff --git a/src/main/java/org/pkwmtt/status/DatabaseStatusChecker.java b/src/main/java/org/pkwmtt/status/DatabaseStatusChecker.java deleted file mode 100644 index 6af41a8..0000000 --- a/src/main/java/org/pkwmtt/status/DatabaseStatusChecker.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.pkwmtt.status; - - -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import javax.sql.DataSource; -import java.sql.SQLException; - -@Slf4j -@Service -public class DatabaseStatusChecker { - @Getter - private static boolean enabled = false; - - @Autowired - DatabaseStatusChecker (DataSource dataSource) { - try { - enabled = dataSource.getConnection().isValid(2); - } catch (SQLException e) { - log.error("Couldn't check database connection. Service will be unavailable"); - } - } -} diff --git a/src/main/java/org/pkwmtt/status/SystemStatusCheckerService.java b/src/main/java/org/pkwmtt/status/SystemStatusCheckerService.java deleted file mode 100644 index e7a4731..0000000 --- a/src/main/java/org/pkwmtt/status/SystemStatusCheckerService.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.pkwmtt.status; - -import jakarta.annotation.PostConstruct; -import org.pkwmtt.mail.config.MailConfig; -import org.pkwmtt.timetable.TimetableCacheService; -import org.pkwmtt.timetable.TimetableService; -import org.springframework.stereotype.Service; - - -@Service -public class SystemStatusCheckerService { - - private String mailingStatus; - private String databaseStatus; - private String cacheStatus; - private String timetableStatus; - - SystemStatusCheckerService () { - checkStatuses(); - } - - @PostConstruct - private void checkStatuses () { - mailingStatus = assignStatus(MailConfig.isEnabled()); - databaseStatus = assignStatus(DatabaseStatusChecker.isEnabled()); - timetableStatus = assignStatus(TimetableService.isEnabled()); - cacheStatus = assignStatus(TimetableCacheService.isCacheAvailable()); - } - - public String getStatus () { - return String.format( - """ - Server: ✅; - Services: - Mail: %s - Database: %s, - Timetable: %s, - Cache: %s - """, mailingStatus, databaseStatus, timetableStatus, cacheStatus - ); - } - - - private String assignStatus (boolean condition) { - return condition ? "✅" : "❌"; - } -} diff --git a/src/main/java/org/pkwmtt/status/SystemStatusController.java b/src/main/java/org/pkwmtt/status/SystemStatusController.java deleted file mode 100644 index dd055c1..0000000 --- a/src/main/java/org/pkwmtt/status/SystemStatusController.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.pkwmtt.status; - -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/pkwmtt/system/status") -@RequiredArgsConstructor -public class SystemStatusController { - private final SystemStatusCheckerService service; - - @GetMapping - public ResponseEntity getSystemStatus () { - return ResponseEntity.ok(service.getStatus()); - } - -} diff --git a/src/main/java/org/pkwmtt/timetable/TimetableCacheService.java b/src/main/java/org/pkwmtt/timetable/TimetableCacheService.java index 6bd4861..7a3eea2 100644 --- a/src/main/java/org/pkwmtt/timetable/TimetableCacheService.java +++ b/src/main/java/org/pkwmtt/timetable/TimetableCacheService.java @@ -26,8 +26,6 @@ public class TimetableCacheService { private final ObjectMapper mapper; private final Cache cache; - @Getter - private static boolean cacheAvailable = true; @Value("${main.url:https://podzial.mech.pk.edu.pl/stacjonarne/html/}") private String mainUrl; @@ -36,10 +34,6 @@ public TimetableCacheService (TimetableParserService parser, ObjectMapper mapper this.parser = parser; this.mapper = mapper; cache = cacheManager.getCache("timetables"); - - if (isNull(cache)) { - cacheAvailable = false; - } } /** diff --git a/src/main/java/org/pkwmtt/timetable/TimetableService.java b/src/main/java/org/pkwmtt/timetable/TimetableService.java index 849cd66..e3b93af 100644 --- a/src/main/java/org/pkwmtt/timetable/TimetableService.java +++ b/src/main/java/org/pkwmtt/timetable/TimetableService.java @@ -24,9 +24,6 @@ public class TimetableService { private final TimetableCacheService cachedService; - @Getter - private static final boolean enabled = TimetableCacheService.isConnectionAvailable(); - @Autowired TimetableService (TimetableCacheService cachedService) { this.cachedService = cachedService; From b70fd3b4f79bf5161ec0e5a823d4de6c047d4d75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Florczak?= <84631301+florczaq@users.noreply.github.com> Date: Thu, 4 Sep 2025 16:53:49 +0200 Subject: [PATCH 092/116] Generate and return token; Delete structure package; Check if provided general group exists; --- .../pkwmtt/timetable/TimetableCacheService.java | 16 ---------------- .../org/pkwmtt/timetable/TimetableService.java | 1 - 2 files changed, 17 deletions(-) diff --git a/src/main/java/org/pkwmtt/timetable/TimetableCacheService.java b/src/main/java/org/pkwmtt/timetable/TimetableCacheService.java index 7a3eea2..7964ecf 100644 --- a/src/main/java/org/pkwmtt/timetable/TimetableCacheService.java +++ b/src/main/java/org/pkwmtt/timetable/TimetableCacheService.java @@ -3,7 +3,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.Getter; import org.jsoup.Jsoup; import org.pkwmtt.exceptions.SpecifiedGeneralGroupDoesntExistsException; import org.pkwmtt.exceptions.WebPageContentNotAvailableException; @@ -18,8 +17,6 @@ import java.util.List; import java.util.Map; -import static java.util.Objects.isNull; - @Service public class TimetableCacheService { private final TimetableParserService parser; @@ -36,19 +33,6 @@ public TimetableCacheService (TimetableParserService parser, ObjectMapper mapper cache = cacheManager.getCache("timetables"); } - /** - * @return connection status - */ - public static boolean isConnectionAvailable () { - try { - fetchData("https://podzial.mech.pk.edu.pl/stacjonarne/html/"); - return true; - } catch (Exception e) { - System.out.println(e.getMessage()); - return false; - } - } - /** * Fetches and parses the full timetable for a general group. * diff --git a/src/main/java/org/pkwmtt/timetable/TimetableService.java b/src/main/java/org/pkwmtt/timetable/TimetableService.java index e3b93af..903b220 100644 --- a/src/main/java/org/pkwmtt/timetable/TimetableService.java +++ b/src/main/java/org/pkwmtt/timetable/TimetableService.java @@ -2,7 +2,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.pkwmtt.exceptions.SpecifiedGeneralGroupDoesntExistsException; import org.pkwmtt.exceptions.SpecifiedSubGroupDoesntExistsException; From 82e697ef19fd67bc9a1e6862ad12d54409b52325 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Thu, 4 Sep 2025 17:31:25 +0200 Subject: [PATCH 093/116] separate tests profiles --- .../examCalendar/ExamControllerTest.java | 11 +++- .../repository/ExamRepositoryTest.java | 3 + .../resources/application-database.properties | 15 +++++ src/test/resources/application.properties | 12 ---- src/test/resources/schema.sql | 61 +++++++++++++++++++ 5 files changed, 87 insertions(+), 15 deletions(-) create mode 100644 src/test/resources/application-database.properties delete mode 100644 src/test/resources/application.properties create mode 100644 src/test/resources/schema.sql diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java index 0bf5f3f..44c1549 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java @@ -5,6 +5,7 @@ import jakarta.transaction.Transactional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.Mock; import org.pkwmtt.examCalendar.dto.ExamDto; import org.pkwmtt.examCalendar.entity.Exam; import org.pkwmtt.examCalendar.entity.ExamType; @@ -18,7 +19,8 @@ import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultMatcher; @@ -26,7 +28,9 @@ import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; -import java.util.*; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -43,6 +47,7 @@ @SpringBootTest @AutoConfigureMockMvc @AutoConfigureTestDatabase(connection = EmbeddedDatabaseConnection.H2) +@ActiveProfiles("database") class ExamControllerTest { @Autowired @@ -60,7 +65,7 @@ class ExamControllerTest { @Autowired private GroupRepository groupRepository; - @MockitoBean + @Mock private TimetableService timetableService; @BeforeEach diff --git a/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java b/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java index 435d157..52490f4 100644 --- a/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java @@ -11,6 +11,8 @@ import org.springframework.boot.jdbc.EmbeddedDatabaseConnection; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ActiveProfiles; import java.time.LocalDateTime; import java.util.List; @@ -24,6 +26,7 @@ @DataJpaTest @TestInstance(TestInstance.Lifecycle.PER_CLASS) @AutoConfigureTestDatabase(connection = EmbeddedDatabaseConnection.H2) +@ActiveProfiles("database") class ExamRepositoryTest { @Autowired diff --git a/src/test/resources/application-database.properties b/src/test/resources/application-database.properties new file mode 100644 index 0000000..46c88d1 --- /dev/null +++ b/src/test/resources/application-database.properties @@ -0,0 +1,15 @@ +#spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false +#spring.datasource.username=sa +#spring.datasource.password= + +spring.sql.init.mode=always +spring.sql.init.schema-locations=classpath:schema.sql + +spring.jpa.show-sql=true +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +spring.datasource.driver-class-name=org.h2.Driver +spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false +#spring.jpa.hibernate.ddl-auto=create-drop +spring.datasource.hikari.initialization-fail-timeout=0 + + diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties deleted file mode 100644 index e434062..0000000 --- a/src/test/resources/application.properties +++ /dev/null @@ -1,12 +0,0 @@ -spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false -spring.datasource.username=sa -spring.datasource.password= - -spring.jpa.show-sql=true -spring.jpa.database-platform=org.hibernate.dialect.H2Dialect -spring.datasource.driver-class-name=org.h2.Driver -spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false -spring.jpa.hibernate.ddl-auto=create-drop -spring.datasource.hikari.initialization-fail-timeout=0 - - diff --git a/src/test/resources/schema.sql b/src/test/resources/schema.sql new file mode 100644 index 0000000..d598883 --- /dev/null +++ b/src/test/resources/schema.sql @@ -0,0 +1,61 @@ +DROP TABLE IF EXISTS exams_groups; +DROP TABLE IF EXISTS exams; +DROP TABLE IF EXISTS exam_type; +DROP TABLE IF EXISTS otp_codes; +DROP TABLE IF EXISTS users; +DROP TABLE IF EXISTS student_groups; +DROP TABLE IF EXISTS general_group; + +CREATE TABLE exam_type ( + exam_type_id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL +); + +CREATE TABLE general_group ( + general_group_id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL +); + +CREATE TABLE student_groups ( + group_id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL UNIQUE +); + +CREATE TABLE exams ( + exam_id INT AUTO_INCREMENT PRIMARY KEY, + title VARCHAR(255) NOT NULL, + description VARCHAR(255), + exam_date TIMESTAMP NOT NULL, + exam_type_id INT NOT NULL, + CONSTRAINT fk_exams_exam_type FOREIGN KEY (exam_type_id) + REFERENCES exam_type (exam_type_id) ON DELETE CASCADE +); + +CREATE TABLE exams_groups ( + exam_group_id INT AUTO_INCREMENT PRIMARY KEY, + exam_id INT NOT NULL, + group_id INT NOT NULL, + CONSTRAINT fk_exams_groups_exam FOREIGN KEY (exam_id) + REFERENCES exams (exam_id) ON DELETE CASCADE, + CONSTRAINT fk_exams_groups_group FOREIGN KEY (group_id) + REFERENCES student_groups (group_id) ON DELETE CASCADE +); + +CREATE TABLE otp_codes ( + otp_code_id INT AUTO_INCREMENT PRIMARY KEY, + code VARCHAR(255) NOT NULL, + expire TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + general_group_id INT NOT NULL, + CONSTRAINT fk_otp_codes_general_group FOREIGN KEY (general_group_id) + REFERENCES general_group (general_group_id) ON DELETE CASCADE +); + +CREATE TABLE users ( + user_id INT AUTO_INCREMENT PRIMARY KEY, + general_group_id INT NOT NULL, + email VARCHAR(255) NOT NULL, + is_active BOOLEAN NOT NULL DEFAULT TRUE, + role VARCHAR(20) NOT NULL DEFAULT 'REPRESENTATIVE', + CONSTRAINT fk_users_general_group FOREIGN KEY (general_group_id) + REFERENCES general_group (general_group_id) ON DELETE CASCADE +); \ No newline at end of file From c2d0f4899b5dbf004b9e701e78bde23efb7c4129 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Thu, 4 Sep 2025 20:51:17 +0200 Subject: [PATCH 094/116] merge from main --- pom.xml | 17 ++++ .../org/pkwmtt/security/token/JwtService.java | 9 +++ .../pkwmtt/security/token/JwtServiceImpl.java | 81 +++++++++++++++++++ .../pkwmtt/security/token/utils/JwtUtils.java | 20 +++++ 4 files changed, 127 insertions(+) create mode 100644 src/main/java/org/pkwmtt/security/token/JwtService.java create mode 100644 src/main/java/org/pkwmtt/security/token/JwtServiceImpl.java create mode 100644 src/main/java/org/pkwmtt/security/token/utils/JwtUtils.java diff --git a/pom.xml b/pom.xml index 205bba9..3d7611d 100644 --- a/pom.xml +++ b/pom.xml @@ -88,6 +88,23 @@ spring-security-test test
+ + io.jsonwebtoken + jjwt-api + 0.12.7 + + + io.jsonwebtoken + jjwt-impl + 0.12.7 + runtime + + + io.jsonwebtoken + jjwt-jackson + 0.12.7 + runtime + diff --git a/src/main/java/org/pkwmtt/security/token/JwtService.java b/src/main/java/org/pkwmtt/security/token/JwtService.java new file mode 100644 index 0000000..e248f86 --- /dev/null +++ b/src/main/java/org/pkwmtt/security/token/JwtService.java @@ -0,0 +1,9 @@ +package org.pkwmtt.security.token; + +import org.pkwmtt.security.token.dto.UserDTO; + +public interface JwtService { + String generateToken(UserDTO user); + Boolean validateToken(String token); + String getUserIdFromToken(String token); +} diff --git a/src/main/java/org/pkwmtt/security/token/JwtServiceImpl.java b/src/main/java/org/pkwmtt/security/token/JwtServiceImpl.java new file mode 100644 index 0000000..f94079e --- /dev/null +++ b/src/main/java/org/pkwmtt/security/token/JwtServiceImpl.java @@ -0,0 +1,81 @@ +package org.pkwmtt.security.token; + +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import lombok.RequiredArgsConstructor; +import org.pkwmtt.security.token.dto.UserDTO; +import org.pkwmtt.security.token.utils.JwtUtils; +import org.springframework.stereotype.Service; + +import javax.crypto.SecretKey; +import java.util.Base64; +import java.util.Date; + +@Service +@RequiredArgsConstructor +public class JwtServiceImpl implements JwtService { + private final JwtUtils jwtUtils; + + + /** + * Generates a JWT token for a given user. + * The token contains user's email, group, and role as claims, + * and is signed with a secret key. + * + * @param user - required user data to include in token claims + * @return signed JWT token as a String + */ + @Override + public String generateToken(UserDTO user) { + return Jwts.builder() + .subject(user.getEmail()) + .claim("group", user.getGroup()) + .claim("role", user.getRole()) + .issuedAt(new Date()) + .expiration((new Date(System.currentTimeMillis() + jwtUtils.getExpirationMs()))) + .signWith(decodeSecretKey()) + .compact(); + } + + + /** + * Decode a secret key for signing JWT. + * The key is decoded from Base64 stored in JwtUtils configuration. + * + * @return secret key for JWT signing + */ + private SecretKey decodeSecretKey(){ + byte[] decodedKey = Base64.getDecoder().decode(jwtUtils.getSecret()); + return Keys.hmacShaKeyFor(decodedKey); + } + + /** + * Validate a JWT token. + * Attempts to parse the token; if parsing fails, the token is considered invalid. + * + * @param token JWT token string to validate + * @return true if the token is valid, false otherwise + */ + @Override + public Boolean validateToken(String token) { + try { + // TODO: add logic to validate the token + return true; + } catch (JwtException | IllegalArgumentException e) { + return false; + } + } + + /** + * Extracts the user identifier (email) from a JWT token. + * + * @param token JWT token to extract user from + * @return user email from token + */ + @Override + public String getUserIdFromToken(String token) { + // TODO: implement token parsing to extract subject/email + return ""; + } +} diff --git a/src/main/java/org/pkwmtt/security/token/utils/JwtUtils.java b/src/main/java/org/pkwmtt/security/token/utils/JwtUtils.java new file mode 100644 index 0000000..21ee1bb --- /dev/null +++ b/src/main/java/org/pkwmtt/security/token/utils/JwtUtils.java @@ -0,0 +1,20 @@ +package org.pkwmtt.security.token.utils; + +import lombok.Getter; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +@Getter +@Component +public class JwtUtils { + // Secret key used for signing JWTs. If the environment variable JWT_SECRET_KEY + // is not set, a default value "TEST_SECRET" is used. This allows the application + // to start without a real secret, e.g., for local development or tests. + private final String secret; + private final long expirationMs = 1000L * 60 * 60 * 24 * 30 * 6; + + public JwtUtils(Environment environment) { + // Get the secret key from environment variables, or fallback to "TEST_SECRET" + this.secret = environment.getProperty("JWT_SECRET_KEY", "TEST_SECRET"); + } +} From 9904a3ba078d3552975ac4015325d630a66e1447 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Florczak?= <84631301+florczaq@users.noreply.github.com> Date: Thu, 4 Sep 2025 21:09:59 +0200 Subject: [PATCH 095/116] Fix: exception condition --- src/main/java/org/pkwmtt/otp/OTPService.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/pkwmtt/otp/OTPService.java b/src/main/java/org/pkwmtt/otp/OTPService.java index 4ca962b..15f7f09 100644 --- a/src/main/java/org/pkwmtt/otp/OTPService.java +++ b/src/main/java/org/pkwmtt/otp/OTPService.java @@ -58,27 +58,27 @@ public void sendOTPCodes (List requests) requests.forEach(request -> { var code = generateNewCode(); var mail = createMail(request, code); + var groupName = request.getGeneralGroupName(); + var groupNameLength = groupName.length(); - if (request.getGeneralGroupName().length() > 3) { + if (groupNameLength > 3 && Character.isDigit(groupName.charAt(groupNameLength - 1))) { throw new WrongArgumentException( "Wrong general group provided. Make sure you are not providing subgroup. (f.e 12K1 -> wrong, 12K -> good)"); } - if (!generalGroupExists(request.getGeneralGroupName())) { + if (!generalGroupExists(groupName)) { throw new SpecifiedGeneralGroupDoesntExistsException(); } try { emailService.send(mail); } catch (MessagingException e) { - throw new MailCouldNotBeSendException("Couldn't send mail for group: " + request.getGeneralGroupName()); + throw new MailCouldNotBeSendException("Couldn't send mail for group: " + groupName); } - - var generalGroup = generalGroupRepository.findByName(request.getGeneralGroupName()); - + var generalGroup = generalGroupRepository.findByName(groupName); if (generalGroup.isEmpty()) { - generalGroup = Optional.of(generalGroupRepository.save(new GeneralGroup(null, request.getGeneralGroupName()))); + generalGroup = Optional.of(generalGroupRepository.save(new GeneralGroup(null, groupName))); } var user = User From 7814013bdc4d647b9fbfdfadf2b03f99910cfd9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Florczak?= <84631301+florczaq@users.noreply.github.com> Date: Fri, 5 Sep 2025 17:37:37 +0200 Subject: [PATCH 096/116] Code optimization --- .gitignore | 1 + .../timetable/TimetableCacheService.java | 17 ++++------- src/main/resources/logback.xml | 2 +- src/test/java/org/pkwmtt/TESTS.md | 29 ------------------- .../timetable/TimetableCacheServiceTest.java | 21 +++----------- .../timetable/TimetableControllerTest.java | 5 +--- 6 files changed, 12 insertions(+), 63 deletions(-) delete mode 100644 src/test/java/org/pkwmtt/TESTS.md diff --git a/.gitignore b/.gitignore index 5868be2..ac8ccd0 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ target/ .mvn/wrapper/maven-wrapper.jar !**/src/main/**/target/ !**/src/test/**/target/ +logs/app.log ### STS ### .apt_generated diff --git a/src/main/java/org/pkwmtt/timetable/TimetableCacheService.java b/src/main/java/org/pkwmtt/timetable/TimetableCacheService.java index 7964ecf..c36307d 100644 --- a/src/main/java/org/pkwmtt/timetable/TimetableCacheService.java +++ b/src/main/java/org/pkwmtt/timetable/TimetableCacheService.java @@ -23,7 +23,6 @@ public class TimetableCacheService { private final ObjectMapper mapper; private final Cache cache; - @Value("${main.url:https://podzial.mech.pk.edu.pl/stacjonarne/html/}") private String mainUrl; @@ -42,13 +41,13 @@ public TimetableCacheService (TimetableParserService parser, ObjectMapper mapper */ public TimetableDTO getGeneralGroupSchedule (String generalGroupName) throws WebPageContentNotAvailableException, SpecifiedGeneralGroupDoesntExistsException { - var generalGroupList = getGeneralGroupsMap(); + var generalGroupMap = getGeneralGroupsMap(); - if (!generalGroupList.containsKey(generalGroupName)) { + if (!generalGroupMap.containsKey(generalGroupName)) { throw new SpecifiedGeneralGroupDoesntExistsException(generalGroupName); } - String groupUrl = generalGroupList.get(generalGroupName); + String groupUrl = generalGroupMap.get(generalGroupName); String url = mainUrl + groupUrl; String cacheKey = "timetable_" + generalGroupName; var html = fetchData(url); @@ -74,10 +73,7 @@ public TimetableDTO getGeneralGroupSchedule (String generalGroupName) public Map getGeneralGroupsMap () throws WebPageContentNotAvailableException { var url = mainUrl + "lista.html"; var html = fetchData(url); - String json = cache.get( - "generalGroupMap", - () -> mapper.writeValueAsString(parser.parseGeneralGroups(html)) - ); + String json = cache.get("generalGroupMap", () -> mapper.writeValueAsString(parser.parseGeneralGroups(html))); return getMappedValue( json, "generalGroupList", cache, new TypeReference<>() { @@ -93,10 +89,7 @@ public Map getGeneralGroupsMap () throws WebPageContentNotAvaila */ public List getListOfHours () throws WebPageContentNotAvailableException { String url = mainUrl + "plany/o25.html"; - String json = cache.get( - "hourList", - () -> mapper.writeValueAsString(parser.parseHours(fetchData(url))) - ); + String json = cache.get("hourList", () -> mapper.writeValueAsString(parser.parseHours(fetchData(url)))); List result = getMappedValue( json, "hourList", cache, new TypeReference<>() { diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index dcbbeca..49859dc 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -11,7 +11,7 @@ - true + false %d{HH:mm:ss.SSS} %highlight(%-5level) [%thread] %cyan(%logger{36}) - %msg%n diff --git a/src/test/java/org/pkwmtt/TESTS.md b/src/test/java/org/pkwmtt/TESTS.md deleted file mode 100644 index 388fc8e..0000000 --- a/src/test/java/org/pkwmtt/TESTS.md +++ /dev/null @@ -1,29 +0,0 @@ -# MockController Test Suite - -This repository contains a unit test for the `MockController` in a Spring Boot application using `@WebMvcTest`. It focuses on verifying the `/api/v1/hello` endpoint and includes Spring Security bypass configuration for testing purposes. - -## 📄 Overview - -- **Test Type**: Unit test (controller layer only) -- **Frameworks**: Spring Boot, JUnit 5, MockMvc -- **Security**: Bypassed using `@WithMockUser` and custom `SecurityConfig` -- **Target Endpoint**: `GET /api/v1/hello` - -## 🧪 How It Works - -The test class uses: -- `@WebMvcTest` to load only the web layer -- `MockMvc` to simulate HTTP requests -- `@WithMockUser` to mock an authenticated user -- `@Import(SecurityConfig.class)` to override security filters for testing - -## ✅ Example Test Case - -```java -@WithMockUser -@Test -public void getHello() throws Exception { - mockMvc.perform(get("/api/v1/hello")) - .andExpect(status().isOk()) - .andExpect(content().string("Hello")); -} diff --git a/src/test/java/org/pkwmtt/timetable/TimetableCacheServiceTest.java b/src/test/java/org/pkwmtt/timetable/TimetableCacheServiceTest.java index 09e0014..bef3add 100644 --- a/src/test/java/org/pkwmtt/timetable/TimetableCacheServiceTest.java +++ b/src/test/java/org/pkwmtt/timetable/TimetableCacheServiceTest.java @@ -2,22 +2,17 @@ import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.pkwmtt.ValuesForTest; import org.pkwmtt.cache.CacheInspector; import org.pkwmtt.timetable.dto.TimetableDTO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; import test.TestConfig; -import java.util.List; import java.util.Map; -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.get; -import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching; +import static com.github.tomakehurst.wiremock.client.WireMock.*; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; @@ -27,9 +22,6 @@ class TimetableCacheServiceTest extends TestConfig { @Autowired TimetableCacheService cachedService; - @Autowired - TimetableService service; - @Autowired CacheInspector cacheInspector; @@ -111,11 +103,9 @@ public void shouldGeneralGroupMapBePresentInCache () { } @Test - @Disabled("Test shouldn't be random") - public void shouldReturnRandomGeneralGroupSchedule () { + public void shouldReturn12K1Schedule () { //given - List generalGroupList = service.getGeneralGroupList(); - var generalGroupName = generalGroupList.get((int) (Math.random() * generalGroupList.size())); // get random general group + var generalGroupName = "12K1"; // get random general group //when var result = cachedService.getGeneralGroupSchedule(generalGroupName); @@ -126,12 +116,9 @@ public void shouldReturnRandomGeneralGroupSchedule () { } @Test - @Disabled("Test shouldn't be random") public void shouldRandomGeneralGroupScheduleBePresentInCache () { //given - List generalGroupList = service.getGeneralGroupList(); - - String generalGroupName = generalGroupList.get((int) (Math.random() * generalGroupList.size())); // get random general group + String generalGroupName = "12K1"; // get random general group String key = "timetable_" + generalGroupName; cachedService.getGeneralGroupSchedule(generalGroupName); // call method to save data in cache diff --git a/src/test/java/org/pkwmtt/timetable/TimetableControllerTest.java b/src/test/java/org/pkwmtt/timetable/TimetableControllerTest.java index e9cf1a0..d053637 100644 --- a/src/test/java/org/pkwmtt/timetable/TimetableControllerTest.java +++ b/src/test/java/org/pkwmtt/timetable/TimetableControllerTest.java @@ -13,10 +13,8 @@ import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.test.context.ActiveProfiles; import test.TestConfig; -import java.time.LocalDateTime; import java.util.Arrays; import java.util.List; import java.util.regex.Matcher; @@ -69,6 +67,7 @@ public void testGetGeneralGroupScheduleFiltered_withOptionalParams () { assertNotNull(responseBody); }, () -> { + assertNotNull(response.getBody()); var responseData = response.getBody().getData(); assertEquals(5, responseData.size()); assertEquals(12, responseData.getFirst().getOdd().size()); @@ -145,7 +144,6 @@ public void shouldReturn_BadRequest_SpecifiedGeneralGroupDoesntExistsException ( //then assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); assertNotNull(response.getBody()); - assertThat(response.getBody().getTimestamp()).isBefore(LocalDateTime.now()); } @Test @@ -165,7 +163,6 @@ public void shouldReturn_BadRequest_SpecifiedSubGroupDoesntExistsException () { //then assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); assertNotNull(response.getBody()); - assertThat(response.getBody().getTimestamp()).isBefore(LocalDateTime.now()); } @Test From 773d61918be591734d6233f498d059e142a57055 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Fri, 5 Sep 2025 18:21:13 +0200 Subject: [PATCH 097/116] code optimization --- pom.xml | 30 +++++++------------ .../org/pkwmtt/config/GlobalCorsConfig.java | 3 +- .../pkwmtt/examCalendar/ExamController.java | 1 - .../org/pkwmtt/examCalendar/entity/Exam.java | 3 +- .../examCalendar/entity/GeneralGroup.java | 2 -- .../examCalendar/entity/StudentGroup.java | 4 --- .../examCalendar/mapper/ExamDtoMapper.java | 7 ++--- .../exceptions/NoGroupsProvidedException.java | 7 ----- .../security/config/SpringSecurity.java | 1 - .../security/cors/SecurityCorsConfig.java | 3 +- .../pkwmtt/examCalendar/ExamServiceTest.java | 5 ++-- 11 files changed, 18 insertions(+), 48 deletions(-) delete mode 100644 src/main/java/org/pkwmtt/exceptions/NoGroupsProvidedException.java diff --git a/pom.xml b/pom.xml index 3d7611d..125b4ab 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 3.5.3 + 3.5.5 org.pkwmtt @@ -28,6 +28,12 @@ PKWM Mobile App Team https://github.com/PKTTTeam + + Patryk Mazurek + https://github.com/PatMaz999 + PKWM Mobile App Team + https://github.com/PKTTTeam + @@ -69,15 +75,6 @@ lombok true - - - - - - - - - org.springframework.boot @@ -106,24 +103,17 @@ runtime - junit junit 4.13.1 - - - - - - org.mockito mockito-core 5.18.0 - + com.h2database h2 @@ -152,7 +142,7 @@ org.springdoc springdoc-openapi-starter-webmvc-ui - 2.8.9 + 2.8.12 @@ -178,7 +168,7 @@ org.wiremock.integrations wiremock-spring-boot - 3.10.0 + 3.10.6 diff --git a/src/main/java/org/pkwmtt/config/GlobalCorsConfig.java b/src/main/java/org/pkwmtt/config/GlobalCorsConfig.java index 78a2e01..3942302 100644 --- a/src/main/java/org/pkwmtt/config/GlobalCorsConfig.java +++ b/src/main/java/org/pkwmtt/config/GlobalCorsConfig.java @@ -23,8 +23,7 @@ public WebMvcConfigurer corsConfigurer() { @Override public void addCorsMappings(@NonNull CorsRegistry registry) { registry.addMapping("/pkmwtt/api/**") -// TODO: change host - .allowedOrigins("http://localhost:5173") + .allowedOrigins("https://pkwmapp.pl") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") // required for authorization and cookies, default false diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamController.java b/src/main/java/org/pkwmtt/examCalendar/ExamController.java index 028c08e..9674952 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamController.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamController.java @@ -1,6 +1,5 @@ package org.pkwmtt.examCalendar; -import com.fasterxml.jackson.core.JsonProcessingException; import jakarta.validation.Valid; import jakarta.validation.constraints.Positive; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java b/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java index 90588cc..31d115a 100644 --- a/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java +++ b/src/main/java/org/pkwmtt/examCalendar/entity/Exam.java @@ -5,7 +5,6 @@ import lombok.Builder; import lombok.Getter; import lombok.RequiredArgsConstructor; -import org.pkwmtt.exceptions.InvalidGroupIdentifierException; import org.pkwmtt.exceptions.UnsupportedCountOfArgumentsException; import java.time.LocalDateTime; @@ -16,7 +15,7 @@ @Entity @Getter -@Builder(builderClassName = "Builder", buildMethodName = "build") +@Builder(builderClassName = "Builder") @RequiredArgsConstructor @Table(name = "exams") @AllArgsConstructor diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/GeneralGroup.java b/src/main/java/org/pkwmtt/examCalendar/entity/GeneralGroup.java index 12e328f..c32e545 100644 --- a/src/main/java/org/pkwmtt/examCalendar/entity/GeneralGroup.java +++ b/src/main/java/org/pkwmtt/examCalendar/entity/GeneralGroup.java @@ -6,8 +6,6 @@ import lombok.Getter; import lombok.NoArgsConstructor; -import java.util.Set; - @Entity @Getter @Builder diff --git a/src/main/java/org/pkwmtt/examCalendar/entity/StudentGroup.java b/src/main/java/org/pkwmtt/examCalendar/entity/StudentGroup.java index 3332ef2..158cce6 100644 --- a/src/main/java/org/pkwmtt/examCalendar/entity/StudentGroup.java +++ b/src/main/java/org/pkwmtt/examCalendar/entity/StudentGroup.java @@ -18,8 +18,4 @@ public class StudentGroup { @Column(nullable = false, unique = true) private String name; - -// FIXME: remove? -// @ManyToMany(mappedBy = "groups") -// private Set exams = new HashSet<>(); } diff --git a/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoMapper.java b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoMapper.java index ea79230..9b243b0 100644 --- a/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoMapper.java +++ b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoMapper.java @@ -1,6 +1,5 @@ package org.pkwmtt.examCalendar.mapper; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.pkwmtt.examCalendar.dto.ExamDto; import org.pkwmtt.examCalendar.entity.Exam; @@ -11,16 +10,16 @@ import java.util.List; import java.util.Set; import java.util.stream.Collectors; -import java.util.stream.Stream; /** * maps ExamDto to Exam entity. Couldn't be utility class, because needs ExamTypeRepository to validate exam types */ @Component -@RequiredArgsConstructor @Slf4j public class ExamDtoMapper { - private ExamDtoMapper examDtoMapper; + private ExamDtoMapper() { + throw new IllegalStateException("Utility class"); + } /** * @param examDto examDto object received from request diff --git a/src/main/java/org/pkwmtt/exceptions/NoGroupsProvidedException.java b/src/main/java/org/pkwmtt/exceptions/NoGroupsProvidedException.java deleted file mode 100644 index b99f44b..0000000 --- a/src/main/java/org/pkwmtt/exceptions/NoGroupsProvidedException.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.pkwmtt.exceptions; - -public class NoGroupsProvidedException extends RuntimeException { - public NoGroupsProvidedException() { - super("groups set is empty"); - } -} diff --git a/src/main/java/org/pkwmtt/security/config/SpringSecurity.java b/src/main/java/org/pkwmtt/security/config/SpringSecurity.java index c4dc591..0b11b2a 100644 --- a/src/main/java/org/pkwmtt/security/config/SpringSecurity.java +++ b/src/main/java/org/pkwmtt/security/config/SpringSecurity.java @@ -4,7 +4,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.web.SecurityFilterChain; diff --git a/src/main/java/org/pkwmtt/security/cors/SecurityCorsConfig.java b/src/main/java/org/pkwmtt/security/cors/SecurityCorsConfig.java index 48aca29..5107d21 100644 --- a/src/main/java/org/pkwmtt/security/cors/SecurityCorsConfig.java +++ b/src/main/java/org/pkwmtt/security/cors/SecurityCorsConfig.java @@ -23,8 +23,7 @@ public class SecurityCorsConfig { @Bean CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); -// TODO: change host - config.setAllowedOrigins(List.of("http://localhost:5173")); + config.setAllowedOrigins(List.of("https://pkwmapp.pl")); config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS")); config.setAllowedHeaders(List.of("*")); config.setAllowCredentials(true); //??? diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java index 2dbcc5a..fd4edf7 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java @@ -219,8 +219,6 @@ void addExamForEmptyGeneralGroup() { // -// TODO: test blank generalGroups in controller - // /** @@ -382,6 +380,7 @@ void addExamForSingleGeneralGroupAndSubgroupsWithRepositoryContainingGroups() th when(timetableService.getGeneralGroupList()).thenReturn(new ArrayList<>(generalGroups)); when(timetableService.getAvailableSubGroups("12K2")).thenReturn(List.of("K04", "P04", "L04", "K05")); + //noinspection unchecked when(groupRepository.findAllByNameIn(any(Set.class))).thenReturn(new HashSet<>(studentGroups.subList(0, 3))); when(groupRepository.saveAll(any())).thenReturn(studentGroups.subList(3, 5)); when(examRepository.save(any(Exam.class))).thenReturn(exam); @@ -532,6 +531,7 @@ void addExamWhenServiceIsUnavailableAndRepositoryContainsGroups() throws JsonPro when(timetableService.getGeneralGroupList()).thenThrow(new WebPageContentNotAvailableException()); when(timetableService.getAvailableSubGroups("12K2")).thenThrow(new JsonParseException("parsing subgroups failed")); + //noinspection unchecked when(groupRepository.findAllByNameIn(any(Set.class))).thenReturn(new HashSet<>(studentGroups)); when(groupRepository.saveAll(anyList())).thenReturn(List.of()); when(examRepository.save(any(Exam.class))).thenReturn(exam); @@ -729,7 +729,6 @@ private static List buildExampleStudentGroupList(Set group ).collect(Collectors.toList()); } - private static Exam buildExamWithIdAndGroups(int id, List groups) { return Exam.builder() .examId(id) From 21e9dce685c4a364410f12fa3e8c04d22dd22a58 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Fri, 5 Sep 2025 18:27:53 +0200 Subject: [PATCH 098/116] fix: examMapper --- src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoMapper.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoMapper.java b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoMapper.java index 9b243b0..cf3c3aa 100644 --- a/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoMapper.java +++ b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoMapper.java @@ -5,7 +5,6 @@ import org.pkwmtt.examCalendar.entity.Exam; import org.pkwmtt.examCalendar.entity.ExamType; import org.pkwmtt.examCalendar.entity.StudentGroup; -import org.springframework.stereotype.Component; import java.util.List; import java.util.Set; @@ -14,7 +13,6 @@ /** * maps ExamDto to Exam entity. Couldn't be utility class, because needs ExamTypeRepository to validate exam types */ -@Component @Slf4j public class ExamDtoMapper { private ExamDtoMapper() { From fd279a46628414e829979cf2c1948f7d16fe5824 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Florczak?= <84631301+florczaq@users.noreply.github.com> Date: Fri, 5 Sep 2025 20:24:50 +0200 Subject: [PATCH 099/116] Created OTP exceptions tests --- .gitignore | 2 +- logs/app.log | 122 ++++++++++++++++++ pom.xml | 57 ++++---- .../java/org/pkwmtt/mail/EmailService.java | 5 - .../org/pkwmtt/mail/config/MailConfig.java | 11 -- .../java/org/pkwmtt/otp/OTPController.java | 2 +- src/main/java/org/pkwmtt/otp/OTPService.java | 27 ++-- .../java/org/pkwmtt/otp/dto/OTPRequest.java | 2 + .../otp/repository/OTPCodeRepository.java | 7 +- .../java/org/pkwmtt/otp/OTPServiceTest.java | 48 +++++++ .../resources/application-database.properties | 8 ++ 11 files changed, 232 insertions(+), 59 deletions(-) create mode 100644 src/test/java/org/pkwmtt/otp/OTPServiceTest.java diff --git a/.gitignore b/.gitignore index ac8ccd0..c6a22f5 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ target/ .mvn/wrapper/maven-wrapper.jar !**/src/main/**/target/ !**/src/test/**/target/ -logs/app.log +logs/* ### STS ### .apt_generated diff --git a/logs/app.log b/logs/app.log index e69de29..72bfde5 100644 --- a/logs/app.log +++ b/logs/app.log @@ -0,0 +1,122 @@ +2025-09-05 18:54:59 ERROR o.s.boot.SpringApplication - Application run failed +org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Failed to initialize dependency 'dataSourceScriptDatabaseInitializer' of LoadTimeWeaverAware bean 'entityManagerFactory': Error creating bean with name 'dataSourceScriptDatabaseInitializer' defined in class path resource [org/springframework/boot/autoconfigure/sql/init/DataSourceInitializationConfiguration.class]: Failed to execute database script + at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:328) + at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:207) + at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:970) + at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:627) + at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752) + at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439) + at org.springframework.boot.SpringApplication.run(SpringApplication.java:318) + at org.springframework.boot.test.context.SpringBootContextLoader.lambda$loadContext$3(SpringBootContextLoader.java:144) + at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:58) + at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:46) + at org.springframework.boot.SpringApplication.withHook(SpringApplication.java:1461) + at org.springframework.boot.test.context.SpringBootContextLoader$ContextLoaderHook.run(SpringBootContextLoader.java:563) + at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:144) + at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:110) + at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:225) + at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:152) + at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:130) + at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:200) + at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:139) + at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:260) + at org.springframework.test.context.junit.jupiter.SpringExtension.postProcessTestInstance(SpringExtension.java:159) + at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$10(ClassBasedTestDescriptor.java:383) + at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.executeAndMaskThrowable(ClassBasedTestDescriptor.java:388) + at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$11(ClassBasedTestDescriptor.java:382) + at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) + at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) + at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179) + at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) + at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1708) + at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) + at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) + at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) + at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) + at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) + at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596) + at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeTestInstancePostProcessors(ClassBasedTestDescriptor.java:382) + at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$instantiateAndPostProcessTestInstance$6(ClassBasedTestDescriptor.java:293) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.instantiateAndPostProcessTestInstance(ClassBasedTestDescriptor.java:292) + at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$4(ClassBasedTestDescriptor.java:281) + at java.base/java.util.Optional.orElseGet(Optional.java:364) + at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$5(ClassBasedTestDescriptor.java:280) + at org.junit.jupiter.engine.execution.TestInstancesProvider.getTestInstances(TestInstancesProvider.java:27) + at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$before$2(ClassBasedTestDescriptor.java:205) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.before(ClassBasedTestDescriptor.java:204) + at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.before(ClassBasedTestDescriptor.java:84) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:153) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:146) + at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:144) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:143) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:100) + at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) + at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:160) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:146) + at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:144) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:143) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:100) + at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35) + at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57) + at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54) + at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:201) + at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:170) + at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:94) + at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:59) + at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:142) + at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:58) + at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:103) + at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:85) + at org.junit.platform.launcher.core.DelegatingLauncher.execute(DelegatingLauncher.java:47) + at org.junit.platform.launcher.core.InterceptingLauncher.lambda$execute$1(InterceptingLauncher.java:39) + at org.junit.platform.launcher.core.ClasspathAlignmentCheckingLauncherInterceptor.intercept(ClasspathAlignmentCheckingLauncherInterceptor.java:25) + at org.junit.platform.launcher.core.InterceptingLauncher.execute(InterceptingLauncher.java:38) + at org.junit.platform.launcher.core.DelegatingLauncher.execute(DelegatingLauncher.java:47) + at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:63) + at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:57) + at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38) + at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11) + at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35) + at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:231) + at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55) +Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSourceScriptDatabaseInitializer' defined in class path resource [org/springframework/boot/autoconfigure/sql/init/DataSourceInitializationConfiguration.class]: Failed to execute database script + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1826) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:607) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529) + at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) + at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373) + at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) + at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) + at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315) + ... 87 common frames omitted +Caused by: org.springframework.jdbc.datasource.init.UncategorizedScriptException: Failed to execute database script + at org.springframework.jdbc.datasource.init.DatabasePopulatorUtils.execute(DatabasePopulatorUtils.java:67) + at org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer.runScripts(DataSourceScriptDatabaseInitializer.java:87) + at org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer.runScripts(AbstractScriptDatabaseInitializer.java:146) + at org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer.applyScripts(AbstractScriptDatabaseInitializer.java:108) + at org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer.applySchemaScripts(AbstractScriptDatabaseInitializer.java:98) + at org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer.initializeDatabase(AbstractScriptDatabaseInitializer.java:76) + at org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer.afterPropertiesSet(AbstractScriptDatabaseInitializer.java:66) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1873) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1822) + ... 94 common frames omitted +Caused by: java.lang.RuntimeException: Driver org.h2.Driver claims to not accept jdbcUrl, jdbc:mysql://localhost:3306/pktt?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC + at com.zaxxer.hikari.util.DriverDataSource.(DriverDataSource.java:111) + at com.zaxxer.hikari.pool.PoolBase.initializeDataSource(PoolBase.java:331) + at com.zaxxer.hikari.pool.PoolBase.(PoolBase.java:116) + at com.zaxxer.hikari.pool.HikariPool.(HikariPool.java:94) + at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:111) + at org.springframework.jdbc.datasource.DataSourceUtils.fetchConnection(DataSourceUtils.java:160) + at org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils.java:118) + at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:81) + at org.springframework.jdbc.datasource.init.DatabasePopulatorUtils.execute(DatabasePopulatorUtils.java:52) + ... 102 common frames omitted diff --git a/pom.xml b/pom.xml index 3d7611d..c87f61c 100644 --- a/pom.xml +++ b/pom.xml @@ -70,24 +70,24 @@ true - - - - - - - + + + + + + + - - - org.springframework.boot - spring-boot-starter-security - - - org.springframework.security - spring-security-test - test - + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.security + spring-security-test + test + io.jsonwebtoken jjwt-api @@ -105,24 +105,24 @@ 0.12.7 runtime - + - - junit - junit - 4.13.1 - + + junit + junit + 4.13.1 + - - org.mockito - mockito-core - 5.18.0 - + + org.mockito + mockito-core + 5.18.0 + com.h2database @@ -191,6 +191,7 @@ dotenv-java 3.0.0 + diff --git a/src/main/java/org/pkwmtt/mail/EmailService.java b/src/main/java/org/pkwmtt/mail/EmailService.java index 1174ec0..95f043a 100644 --- a/src/main/java/org/pkwmtt/mail/EmailService.java +++ b/src/main/java/org/pkwmtt/mail/EmailService.java @@ -5,7 +5,6 @@ import jakarta.mail.internet.MimeMessage; import lombok.RequiredArgsConstructor; import org.pkwmtt.exceptions.MailServiceNotAvailableException; -import org.pkwmtt.mail.config.MailConfig; import org.pkwmtt.mail.dto.MailDTO; import org.springframework.core.env.Environment; import org.springframework.mail.javamail.JavaMailSender; @@ -27,10 +26,6 @@ private void assignProperties () { } public void send (MailDTO mail) throws MessagingException, MailServiceNotAvailableException { - if (!MailConfig.isEnabled()) { - throw new MailServiceNotAvailableException(); - } - MimeMessage message = mailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8"); diff --git a/src/main/java/org/pkwmtt/mail/config/MailConfig.java b/src/main/java/org/pkwmtt/mail/config/MailConfig.java index 595d744..6112c41 100644 --- a/src/main/java/org/pkwmtt/mail/config/MailConfig.java +++ b/src/main/java/org/pkwmtt/mail/config/MailConfig.java @@ -1,7 +1,6 @@ package org.pkwmtt.mail.config; import jakarta.annotation.PostConstruct; -import lombok.Getter; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -15,9 +14,6 @@ @RequiredArgsConstructor public class MailConfig { - @Getter - private static boolean enabled = true; - private final Environment environment; private String username; @@ -28,19 +24,12 @@ private void assignAndValidateProperties () { username = environment.getProperty("spring.mail.username"); password = environment.getProperty("spring.mail.password"); - if (username == null || password == null || username.isEmpty() || password.isEmpty()) { - enabled = false; - } } @Bean public JavaMailSender javaMailSender () { JavaMailSenderImpl mailSender = new JavaMailSenderImpl(); - if (!enabled) { - return mailSender; - } - mailSender.setHost("smtp.gmail.com"); mailSender.setPort(587); mailSender.setUsername(username); diff --git a/src/main/java/org/pkwmtt/otp/OTPController.java b/src/main/java/org/pkwmtt/otp/OTPController.java index 8cd6731..a872191 100644 --- a/src/main/java/org/pkwmtt/otp/OTPController.java +++ b/src/main/java/org/pkwmtt/otp/OTPController.java @@ -25,7 +25,7 @@ public ResponseEntity authenticate (@RequestParam(name = "c") String cod @PostMapping("/codes/generate") public ResponseEntity generateCodes (@RequestBody List request) throws MailCouldNotBeSendException, WrongArgumentException, SpecifiedGeneralGroupDoesntExistsException { - service.sendOTPCodes(request); + service.sendOTPCodesForManyGroups(request); return ResponseEntity.ok().build(); } diff --git a/src/main/java/org/pkwmtt/otp/OTPService.java b/src/main/java/org/pkwmtt/otp/OTPService.java index 15f7f09..24d55c0 100644 --- a/src/main/java/org/pkwmtt/otp/OTPService.java +++ b/src/main/java/org/pkwmtt/otp/OTPService.java @@ -30,7 +30,7 @@ @Service @RequiredArgsConstructor public class OTPService { - private final OTPCodeRepository repository; + private final OTPCodeRepository otpRepository; private final UserRepository userRepository; private final GeneralGroupRepository generalGroupRepository; private final EmailService emailService; @@ -49,11 +49,11 @@ public String generateTokenForRepresentative (String code) .setEmail(userEmail) .setRole(Role.REPRESENTATIVE) .setGroup(generalGroup.getName())); - repository.deleteByCode(code); + otpRepository.deleteByCode(code); return token; } - public void sendOTPCodes (List requests) + public void sendOTPCodesForManyGroups (List requests) throws MailCouldNotBeSendException, WrongArgumentException, SpecifiedSubGroupDoesntExistsException { requests.forEach(request -> { var code = generateNewCode(); @@ -70,17 +70,22 @@ public void sendOTPCodes (List requests) throw new SpecifiedGeneralGroupDoesntExistsException(); } + var generalGroup = generalGroupRepository.findByName(groupName); + + if (generalGroup.isPresent()) { + if (otpRepository.existsOTPCodeByGeneralGroup(generalGroup.get())) { + throw new RuntimeException(""); + } + } else { + generalGroup = Optional.of(generalGroupRepository.save(new GeneralGroup(null, groupName))); + } + try { emailService.send(mail); } catch (MessagingException e) { throw new MailCouldNotBeSendException("Couldn't send mail for group: " + groupName); } - var generalGroup = generalGroupRepository.findByName(groupName); - if (generalGroup.isEmpty()) { - generalGroup = Optional.of(generalGroupRepository.save(new GeneralGroup(null, groupName))); - } - var user = User .builder() .email(request.getEmail()) @@ -91,14 +96,14 @@ public void sendOTPCodes (List requests) userRepository.save(user); - repository.save(new OTPCode(code, generalGroup.get())); + otpRepository.save(new OTPCode(code, generalGroup.get())); }); } private GeneralGroup getGeneralGroupAssignedToCode (String code) throws OTPCodeNotFoundException, WrongOTPFormatException { this.validateCode(code); - Optional result = repository.findByCode(code); + Optional result = otpRepository.findByCode(code); if (result.isEmpty()) { throw new OTPCodeNotFoundException(); @@ -139,7 +144,7 @@ private String generateNewCode () { for (int i = 0; i < 6; i++) { code.append(availableCharacters.charAt(random.nextInt(availableCharacters.length()))); } - } while (repository.findByCode(code.toString()).isPresent()); + } while (otpRepository.findByCode(code.toString()).isPresent()); return code.toString(); } diff --git a/src/main/java/org/pkwmtt/otp/dto/OTPRequest.java b/src/main/java/org/pkwmtt/otp/dto/OTPRequest.java index 3c04e03..af310f5 100644 --- a/src/main/java/org/pkwmtt/otp/dto/OTPRequest.java +++ b/src/main/java/org/pkwmtt/otp/dto/OTPRequest.java @@ -1,8 +1,10 @@ package org.pkwmtt.otp.dto; +import lombok.AllArgsConstructor; import lombok.Data; @Data +@AllArgsConstructor public class OTPRequest { private String email; private String generalGroupName; diff --git a/src/main/java/org/pkwmtt/otp/repository/OTPCodeRepository.java b/src/main/java/org/pkwmtt/otp/repository/OTPCodeRepository.java index fd15d0b..707c906 100644 --- a/src/main/java/org/pkwmtt/otp/repository/OTPCodeRepository.java +++ b/src/main/java/org/pkwmtt/otp/repository/OTPCodeRepository.java @@ -1,15 +1,18 @@ package org.pkwmtt.otp.repository; import jakarta.transaction.Transactional; +import org.pkwmtt.examCalendar.entity.GeneralGroup; import org.pkwmtt.examCalendar.entity.OTPCode; import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; -public interface OTPCodeRepository - extends JpaRepository { +public interface OTPCodeRepository extends JpaRepository { Optional findByCode (String code); @Transactional void deleteByCode (String code); + + boolean existsOTPCodeByGeneralGroup (GeneralGroup generalGroup); + } \ No newline at end of file diff --git a/src/test/java/org/pkwmtt/otp/OTPServiceTest.java b/src/test/java/org/pkwmtt/otp/OTPServiceTest.java new file mode 100644 index 0000000..9a0d6f6 --- /dev/null +++ b/src/test/java/org/pkwmtt/otp/OTPServiceTest.java @@ -0,0 +1,48 @@ +package org.pkwmtt.otp; + +import com.mysql.cj.exceptions.WrongArgumentException; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.pkwmtt.exceptions.SpecifiedGeneralGroupDoesntExistsException; +import org.pkwmtt.otp.dto.OTPRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.jdbc.EmbeddedDatabaseConnection; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +@Slf4j +@TestInstance(TestInstance.Lifecycle.PER_METHOD) +@ActiveProfiles("database") +@SpringBootTest +@AutoConfigureTestDatabase(connection = EmbeddedDatabaseConnection.H2) +class OTPServiceTest { + + @Autowired + private OTPService otpService; + + @Test + void shouldThrow_WrongArgumentException () { + //given + List requests = List.of(new OTPRequest("test@localhost", "12K1")); + //when + //then + assertThrows(WrongArgumentException.class, () -> otpService.sendOTPCodesForManyGroups(requests)); + } + + @Test + void shouldThrow_SpecifiedGeneralGroupDoesntExistsException () { + //given + List requests = List.of(new OTPRequest("test@localhost", "XXXX")); + //when + //then + assertThrows(SpecifiedGeneralGroupDoesntExistsException.class, () -> otpService.sendOTPCodesForManyGroups(requests)); + } + + +} \ No newline at end of file diff --git a/src/test/resources/application-database.properties b/src/test/resources/application-database.properties index 46c88d1..937d998 100644 --- a/src/test/resources/application-database.properties +++ b/src/test/resources/application-database.properties @@ -12,4 +12,12 @@ spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false #spring.jpa.hibernate.ddl-auto=create-drop spring.datasource.hikari.initialization-fail-timeout=0 +spring.mail.host=localhost +spring.mail.port=3025 +spring.mail.protocol=smtp +spring.mail.username=test@localhost +spring.mail.password=password123 +spring.mail.properties.mail.smtp.auth=true +spring.mail.properties.mail.smtp.starttls.enable=false + From 2d519f24bdca887ee76717b3e12769c584bf18b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Florczak?= <84631301+florczaq@users.noreply.github.com> Date: Sat, 6 Sep 2025 12:06:25 +0200 Subject: [PATCH 100/116] Applied apiPrefix vairable to Request mapping --- .../pkwmtt/examCalendar/ExamController.java | 3 +- .../org/pkwmtt/mail/EmailTempController.java | 35 ------------------- .../java/org/pkwmtt/otp/OTPController.java | 2 +- .../provider/OTPAuthenticationProvider.java | 5 ++- .../security/config/SpringSecurity.java | 1 - .../pkwmtt/timetable/TimetableController.java | 2 +- src/main/resources/application.properties | 2 ++ .../PkwmttBackendApplicationTests.java | 16 --------- .../timetable/TimetableControllerTest.java | 16 ++++----- 9 files changed, 15 insertions(+), 67 deletions(-) delete mode 100644 src/main/java/org/pkwmtt/mail/EmailTempController.java delete mode 100644 src/test/java/org/pkwmtt/pkwmttbackend/PkwmttBackendApplicationTests.java diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamController.java b/src/main/java/org/pkwmtt/examCalendar/ExamController.java index 028c08e..8e7ebd0 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamController.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamController.java @@ -1,6 +1,5 @@ package org.pkwmtt.examCalendar; -import com.fasterxml.jackson.core.JsonProcessingException; import jakarta.validation.Valid; import jakarta.validation.constraints.Positive; import lombok.RequiredArgsConstructor; @@ -19,7 +18,7 @@ @Validated @RequiredArgsConstructor -@RequestMapping("/pkwmtt/api/v1/exams") +@RequestMapping("${apiPrefix}/exams") @RestController public class ExamController { diff --git a/src/main/java/org/pkwmtt/mail/EmailTempController.java b/src/main/java/org/pkwmtt/mail/EmailTempController.java deleted file mode 100644 index 1048a19..0000000 --- a/src/main/java/org/pkwmtt/mail/EmailTempController.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.pkwmtt.mail; - -import jakarta.mail.MessagingException; -import lombok.RequiredArgsConstructor; -import org.pkwmtt.exceptions.MailServiceNotAvailableException; -import org.pkwmtt.exceptions.dto.ErrorResponseDTO; -import org.pkwmtt.mail.dto.MailDTO; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/mail") -public class EmailTempController { - - private final EmailService service; - - @PostMapping - public void sendMail (@RequestParam(name = "r") String recipientEmailAddress) - throws MessagingException, MailServiceNotAvailableException { - service.send(new MailDTO() - .setRecipient(recipientEmailAddress) - .setDescription("TEST") - .setTitle("TEST")); - } - - @ExceptionHandler(MailServiceNotAvailableException.class) - public ResponseEntity handle (Exception e) { - return new ResponseEntity<>( - new ErrorResponseDTO(e.getMessage()), - HttpStatus.SERVICE_UNAVAILABLE - ); - } -} diff --git a/src/main/java/org/pkwmtt/otp/OTPController.java b/src/main/java/org/pkwmtt/otp/OTPController.java index 8cd6731..ef01fcc 100644 --- a/src/main/java/org/pkwmtt/otp/OTPController.java +++ b/src/main/java/org/pkwmtt/otp/OTPController.java @@ -11,7 +11,7 @@ import java.util.List; @RestController -@RequestMapping("/pkwmtt/api/v1/representatives") +@RequestMapping("${apiPrefix}/representatives") @RequiredArgsConstructor public class OTPController { private final OTPService service; diff --git a/src/main/java/org/pkwmtt/security/auth/provider/OTPAuthenticationProvider.java b/src/main/java/org/pkwmtt/security/auth/provider/OTPAuthenticationProvider.java index 845de0e..0d404a8 100644 --- a/src/main/java/org/pkwmtt/security/auth/provider/OTPAuthenticationProvider.java +++ b/src/main/java/org/pkwmtt/security/auth/provider/OTPAuthenticationProvider.java @@ -1,9 +1,8 @@ package org.pkwmtt.security.auth.provider; import lombok.RequiredArgsConstructor; -import org.pkwmtt.examCalendar.entity.*; +import org.pkwmtt.examCalendar.entity.User; import org.pkwmtt.examCalendar.repository.UserRepository; -import org.pkwmtt.examCalendar.repository.*; import org.pkwmtt.security.token.dto.UserDTO; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.BadCredentialsException; @@ -15,7 +14,7 @@ import java.util.List; import java.util.stream.Stream; - +//TODO delete @Component @RequiredArgsConstructor public class OTPAuthenticationProvider implements AuthenticationProvider { diff --git a/src/main/java/org/pkwmtt/security/config/SpringSecurity.java b/src/main/java/org/pkwmtt/security/config/SpringSecurity.java index c4dc591..0b11b2a 100644 --- a/src/main/java/org/pkwmtt/security/config/SpringSecurity.java +++ b/src/main/java/org/pkwmtt/security/config/SpringSecurity.java @@ -4,7 +4,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.web.SecurityFilterChain; diff --git a/src/main/java/org/pkwmtt/timetable/TimetableController.java b/src/main/java/org/pkwmtt/timetable/TimetableController.java index 7110810..6543654 100644 --- a/src/main/java/org/pkwmtt/timetable/TimetableController.java +++ b/src/main/java/org/pkwmtt/timetable/TimetableController.java @@ -14,7 +14,7 @@ import static java.util.Objects.isNull; @RestController -@RequestMapping("/pkmwtt/api/v1/timetables") +@RequestMapping("${apiPrefix}/timetables") @RequiredArgsConstructor public class TimetableController { private final TimetableService service; diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index a227ab6..f7e0844 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -27,3 +27,5 @@ spring.mail.username=${EMAIL_USERNAME:} spring.mail.password=${EMAIL_PASSWORD:} spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=true + +apiPrefix=/pkwmtt/api/v1 \ No newline at end of file diff --git a/src/test/java/org/pkwmtt/pkwmttbackend/PkwmttBackendApplicationTests.java b/src/test/java/org/pkwmtt/pkwmttbackend/PkwmttBackendApplicationTests.java deleted file mode 100644 index 2ae95fd..0000000 --- a/src/test/java/org/pkwmtt/pkwmttbackend/PkwmttBackendApplicationTests.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.pkwmtt.pkwmttbackend; - -import org.junit.jupiter.api.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Suite; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@SpringBootTest -class PkwmttBackendApplicationTests { - - @Test - void contextLoads() { - } - -} diff --git a/src/test/java/org/pkwmtt/timetable/TimetableControllerTest.java b/src/test/java/org/pkwmtt/timetable/TimetableControllerTest.java index e9cf1a0..551d764 100644 --- a/src/test/java/org/pkwmtt/timetable/TimetableControllerTest.java +++ b/src/test/java/org/pkwmtt/timetable/TimetableControllerTest.java @@ -13,7 +13,6 @@ import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.test.context.ActiveProfiles; import test.TestConfig; import java.time.LocalDateTime; @@ -54,7 +53,7 @@ public void initWireMock() { @Test public void testGetGeneralGroupScheduleFiltered_withOptionalParams () { //given - var url = String.format("http://localhost:%s/pkmwtt/api/v1/timetables/12K1?sub=K01&sub=L01&sub=P01", + var url = String.format("http://localhost:%s/pkwmtt/api/v1/timetables/12K1?sub=K01&sub=L01&sub=P01", port ); @@ -69,6 +68,7 @@ public void testGetGeneralGroupScheduleFiltered_withOptionalParams () { assertNotNull(responseBody); }, () -> { + assertNotNull(response.getBody()); var responseData = response.getBody().getData(); assertEquals(5, responseData.size()); assertEquals(12, responseData.getFirst().getOdd().size()); @@ -80,7 +80,7 @@ public void testGetGeneralGroupScheduleFiltered_withOptionalParams () { @Test public void testGetGeneralGroupScheduleFiltered_withoutParams () { //given - var url = String.format("http://localhost:%s/pkmwtt/api/v1/timetables/12K1", port); + var url = String.format("http://localhost:%s/pkwmtt/api/v1/timetables/12K1", port); //when ResponseEntity response = restTemplate.getForEntity(url, TimetableDTO.class); @@ -96,7 +96,7 @@ public void testGetGeneralGroupScheduleFiltered_withoutParams () { public void shouldReturnListOfGeneralGroups () { //given String url = String.format( - "http://localhost:%s/pkmwtt/api/v1/timetables/groups/general", + "http://localhost:%s/pkwmtt/api/v1/timetables/groups/general", port ); @@ -116,7 +116,7 @@ public void shouldReturnListOfGeneralGroups () { public void shouldReturnListOfSubgroupsForGeneralGroup () { //given String url = String.format( - "http://localhost:%s/pkmwtt/api/v1/timetables/groups/12K1", + "http://localhost:%s/pkwmtt/api/v1/timetables/groups/12K1", port ); @@ -132,7 +132,7 @@ public void shouldReturnListOfSubgroupsForGeneralGroup () { public void shouldReturn_BadRequest_SpecifiedGeneralGroupDoesntExistsException () { //given String url = String.format( - "http://localhost:%s/pkmwtt/api/v1/timetables/groups/XXXX", + "http://localhost:%s/pkwmtt/api/v1/timetables/groups/XXXX", port ); @@ -152,7 +152,7 @@ public void shouldReturn_BadRequest_SpecifiedGeneralGroupDoesntExistsException ( public void shouldReturn_BadRequest_SpecifiedSubGroupDoesntExistsException () { //given String url = String.format( - "http://localhost:%s/pkmwtt/api/v1/timetables/12K1?sub=XXX", + "http://localhost:%s/pkwmtt/api/v1/timetables/12K1?sub=XXX", port ); @@ -171,7 +171,7 @@ public void shouldReturn_BadRequest_SpecifiedSubGroupDoesntExistsException () { @Test public void shouldReturn_ListOfHours () { //given - String url = String.format("http://localhost:%s/pkmwtt/api/v1/timetables/hours", port); + String url = String.format("http://localhost:%s/pkwmtt/api/v1/timetables/hours", port); //when ResponseEntity response = restTemplate.getForEntity(url, String[].class); From 6302636507328042177d2f2e2f52ddc62e6be30b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Florczak?= <84631301+florczaq@users.noreply.github.com> Date: Sat, 6 Sep 2025 14:48:55 +0200 Subject: [PATCH 101/116] Crete mail tests with Green mail --- logs/app.log | 115 ++++++++++++++++++ pom.xml | 54 ++++---- .../java/org/pkwmtt/mail/EmailService.java | 1 - .../org/pkwmtt/mail/config/MailConfig.java | 10 +- .../otp/repository/OTPCodeRepository.java | 1 + .../java/org/pkwmtt/otp/OTPServiceTest.java | 110 ++++++++++++++++- src/test/java/test/GreenMailConfig.java | 19 +++ .../resources/application-database.properties | 2 +- 8 files changed, 284 insertions(+), 28 deletions(-) create mode 100644 src/test/java/test/GreenMailConfig.java diff --git a/logs/app.log b/logs/app.log index 72bfde5..29c45c4 100644 --- a/logs/app.log +++ b/logs/app.log @@ -120,3 +120,118 @@ Caused by: java.lang.RuntimeException: Driver org.h2.Driver claims to not accept at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:81) at org.springframework.jdbc.datasource.init.DatabasePopulatorUtils.execute(DatabasePopulatorUtils.java:52) ... 102 common frames omitted +2025-09-06 13:31:35 ERROR o.s.boot.SpringApplication - Application run failed +org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'mailConfig': Invocation of init method failed + at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:222) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:429) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1818) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:607) + at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529) + at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) + at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373) + at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) + at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) + at org.springframework.beans.factory.support.DefaultListableBeanFactory.instantiateSingleton(DefaultListableBeanFactory.java:1222) + at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingleton(DefaultListableBeanFactory.java:1188) + at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:1123) + at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:987) + at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:627) + at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752) + at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439) + at org.springframework.boot.SpringApplication.run(SpringApplication.java:318) + at org.springframework.boot.test.context.SpringBootContextLoader.lambda$loadContext$3(SpringBootContextLoader.java:144) + at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:58) + at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:46) + at org.springframework.boot.SpringApplication.withHook(SpringApplication.java:1461) + at org.springframework.boot.test.context.SpringBootContextLoader$ContextLoaderHook.run(SpringBootContextLoader.java:563) + at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:144) + at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:110) + at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:225) + at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:152) + at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:130) + at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:200) + at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:139) + at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:260) + at org.springframework.test.context.junit.jupiter.SpringExtension.postProcessTestInstance(SpringExtension.java:159) + at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$10(ClassBasedTestDescriptor.java:383) + at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.executeAndMaskThrowable(ClassBasedTestDescriptor.java:388) + at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$11(ClassBasedTestDescriptor.java:382) + at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) + at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) + at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179) + at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) + at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1708) + at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) + at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) + at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) + at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) + at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) + at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596) + at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeTestInstancePostProcessors(ClassBasedTestDescriptor.java:382) + at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$instantiateAndPostProcessTestInstance$6(ClassBasedTestDescriptor.java:293) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.instantiateAndPostProcessTestInstance(ClassBasedTestDescriptor.java:292) + at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$4(ClassBasedTestDescriptor.java:281) + at java.base/java.util.Optional.orElseGet(Optional.java:364) + at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$5(ClassBasedTestDescriptor.java:280) + at org.junit.jupiter.engine.execution.TestInstancesProvider.getTestInstances(TestInstancesProvider.java:27) + at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$prepare$0(TestMethodTestDescriptor.java:112) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:111) + at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:69) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$prepare$2(NodeTestTask.java:128) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.prepare(NodeTestTask.java:128) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) + at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) + at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:160) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:146) + at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:144) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:143) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:100) + at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) + at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:160) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:146) + at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:144) + at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:143) + at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:100) + at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35) + at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57) + at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54) + at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:201) + at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:170) + at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:94) + at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:59) + at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:142) + at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:58) + at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:103) + at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:85) + at org.junit.platform.launcher.core.DelegatingLauncher.execute(DelegatingLauncher.java:47) + at org.junit.platform.launcher.core.InterceptingLauncher.lambda$execute$1(InterceptingLauncher.java:39) + at org.junit.platform.launcher.core.ClasspathAlignmentCheckingLauncherInterceptor.intercept(ClasspathAlignmentCheckingLauncherInterceptor.java:25) + at org.junit.platform.launcher.core.InterceptingLauncher.execute(InterceptingLauncher.java:38) + at org.junit.platform.launcher.core.DelegatingLauncher.execute(DelegatingLauncher.java:47) + at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:63) + at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:57) + at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38) + at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11) + at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35) + at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:231) + at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55) +Caused by: java.lang.NullPointerException: null + at java.base/java.util.Objects.requireNonNull(Objects.java:233) + at org.pkwmtt.mail.config.MailConfig.assignAndValidateProperties(MailConfig.java:31) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMethod.invoke(InitDestroyAnnotationBeanPostProcessor.java:457) + at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:401) + at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:219) + ... 103 common frames omitted diff --git a/pom.xml b/pom.xml index 1a99a39..169e06e 100644 --- a/pom.xml +++ b/pom.xml @@ -75,16 +75,16 @@ lombok true - - - org.springframework.boot - spring-boot-starter-security - - - org.springframework.security - spring-security-test - test - + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.security + spring-security-test + test + io.jsonwebtoken jjwt-api @@ -102,17 +102,17 @@ 0.12.7 runtime - - - junit - junit - 4.13.1 - - - org.mockito - mockito-core - 5.18.0 - + + + junit + junit + 4.13.1 + + + org.mockito + mockito-core + 5.18.0 + com.h2database @@ -181,7 +181,17 @@ dotenv-java 3.0.0 - + + + com.icegreen + greenmail-junit5 + 2.0.0 + test + + + org.eclipse.angus + angus-activation + diff --git a/src/main/java/org/pkwmtt/mail/EmailService.java b/src/main/java/org/pkwmtt/mail/EmailService.java index 95f043a..bdc3821 100644 --- a/src/main/java/org/pkwmtt/mail/EmailService.java +++ b/src/main/java/org/pkwmtt/mail/EmailService.java @@ -34,7 +34,6 @@ public void send (MailDTO mail) throws MessagingException, MailServiceNotAvailab helper.setTo(mail.getRecipient()); helper.setText(mail.getDescription(), true); helper.setSubject(mail.getTitle()); - mailSender.send(message); } diff --git a/src/main/java/org/pkwmtt/mail/config/MailConfig.java b/src/main/java/org/pkwmtt/mail/config/MailConfig.java index 6112c41..15a4680 100644 --- a/src/main/java/org/pkwmtt/mail/config/MailConfig.java +++ b/src/main/java/org/pkwmtt/mail/config/MailConfig.java @@ -8,6 +8,7 @@ import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.JavaMailSenderImpl; +import java.util.Objects; import java.util.Properties; @Configuration @@ -18,20 +19,23 @@ public class MailConfig { private String username; private String password; + private String host; + private int port; @PostConstruct private void assignAndValidateProperties () { username = environment.getProperty("spring.mail.username"); password = environment.getProperty("spring.mail.password"); - + host = environment.getProperty("spring.mail.host"); + port = Integer.parseInt(Objects.requireNonNull(environment.getProperty("spring.mail.port"))); } @Bean public JavaMailSender javaMailSender () { JavaMailSenderImpl mailSender = new JavaMailSenderImpl(); - mailSender.setHost("smtp.gmail.com"); - mailSender.setPort(587); + mailSender.setHost(host); + mailSender.setPort(port); mailSender.setUsername(username); mailSender.setPassword(password); diff --git a/src/main/java/org/pkwmtt/otp/repository/OTPCodeRepository.java b/src/main/java/org/pkwmtt/otp/repository/OTPCodeRepository.java index 707c906..6fca6a6 100644 --- a/src/main/java/org/pkwmtt/otp/repository/OTPCodeRepository.java +++ b/src/main/java/org/pkwmtt/otp/repository/OTPCodeRepository.java @@ -15,4 +15,5 @@ public interface OTPCodeRepository extends JpaRepository { boolean existsOTPCodeByGeneralGroup (GeneralGroup generalGroup); + boolean existsOTPCodeByCode (String code); } \ No newline at end of file diff --git a/src/test/java/org/pkwmtt/otp/OTPServiceTest.java b/src/test/java/org/pkwmtt/otp/OTPServiceTest.java index 9a0d6f6..9fa98fc 100644 --- a/src/test/java/org/pkwmtt/otp/OTPServiceTest.java +++ b/src/test/java/org/pkwmtt/otp/OTPServiceTest.java @@ -1,11 +1,21 @@ package org.pkwmtt.otp; +import com.icegreen.greenmail.configuration.GreenMailConfiguration; +import com.icegreen.greenmail.junit5.GreenMailExtension; +import com.icegreen.greenmail.util.ServerSetupTest; import com.mysql.cj.exceptions.WrongArgumentException; +import jakarta.mail.Multipart; +import jakarta.mail.Part; +import jakarta.mail.internet.MimeMessage; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.pkwmtt.exceptions.OTPCodeNotFoundException; import org.pkwmtt.exceptions.SpecifiedGeneralGroupDoesntExistsException; +import org.pkwmtt.exceptions.WrongOTPFormatException; import org.pkwmtt.otp.dto.OTPRequest; +import org.pkwmtt.otp.repository.OTPCodeRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.jdbc.EmbeddedDatabaseConnection; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; @@ -13,8 +23,11 @@ import org.springframework.test.context.ActiveProfiles; import java.util.List; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.*; @Slf4j @TestInstance(TestInstance.Lifecycle.PER_METHOD) @@ -26,6 +39,37 @@ class OTPServiceTest { @Autowired private OTPService otpService; + @Autowired + private OTPCodeRepository otpCodeRepository; + + @RegisterExtension + static GreenMailExtension greenMail = new GreenMailExtension(ServerSetupTest.SMTP) + .withConfiguration(GreenMailConfiguration.aConfig().withUser("test@localhost", "test")) + .withPerMethodLifecycle(true); + + @Test + void shouldSendCorrectMailWithRepresentativePayload () { + //given + List requests = List.of(new OTPRequest("test@localhost", "12K")); + Pattern pattern = Pattern.compile("[A-Z0-9]{6}"); + //when + otpService.sendOTPCodesForManyGroups(requests); + + //then + assertAll(() -> { + assertTrue(greenMail.waitForIncomingEmail(1)); + + MimeMessage receivedMessage = greenMail.getReceivedMessages()[0]; + assertEquals("Kod Starosty 12K", receivedMessage.getSubject()); + assertEquals("test@localhost", receivedMessage.getAllRecipients()[0].toString()); + + Matcher matcher = pattern.matcher(Objects.requireNonNull(extractBody(receivedMessage))); + assertTrue(matcher.find()); + System.out.println(matcher.group(0)); + assertTrue(otpCodeRepository.existsOTPCodeByCode(matcher.group(0))); + }); + } + @Test void shouldThrow_WrongArgumentException () { //given @@ -44,5 +88,69 @@ void shouldThrow_SpecifiedGeneralGroupDoesntExistsException () { assertThrows(SpecifiedGeneralGroupDoesntExistsException.class, () -> otpService.sendOTPCodesForManyGroups(requests)); } + @Test + void shouldGenerateTokenForRepresentative () throws Exception { + //given + List requests = List.of(new OTPRequest("test@localhost", "12K")); + Pattern otpPattern = Pattern.compile("[A-Z0-9]{6}"); + Pattern tokenPattern = Pattern.compile("[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+"); + + //when + otpService.sendOTPCodesForManyGroups(requests); //generate mail with code + greenMail.waitForIncomingEmail(1); // fetch mail + + MimeMessage receivedMessage = greenMail.getReceivedMessages()[0]; + Matcher otpMatcher = otpPattern.matcher(Objects.requireNonNull(extractBody(receivedMessage))); //get content + + final String code; + if (otpMatcher.find()) { + code = otpMatcher.group(); + } else { + code = ""; + fail("Code not found"); + } + + String token = otpService.generateTokenForRepresentative(code); //generate token + + //then + assertAll(() -> { + assertNotNull(token); + + Matcher tokenMatcher = tokenPattern.matcher(token); + assertTrue(tokenMatcher.find()); + assertFalse(otpCodeRepository.existsOTPCodeByCode(code)); + }); + } + + @Test + void shouldThrow_WrongOTPFormatException_wrongCharacters () { + assertThrows(WrongOTPFormatException.class, () -> otpService.generateTokenForRepresentative("XXXXX#")); + } + + @Test + void shouldThrow_WrongOTPFormatException_tooLongCode () { + assertThrows(WrongOTPFormatException.class, () -> otpService.generateTokenForRepresentative("X".repeat(7))); + } + + @Test + void shouldThrow_OTPCodeNotFoundException () { + assertThrows(OTPCodeNotFoundException.class, () -> otpService.generateTokenForRepresentative("X".repeat(6))); + } + + private String extractBody (Part part) throws Exception { + if (part.isMimeType("text/plain") || part.isMimeType("text/html")) { + return (String) part.getContent(); + } + if (part.isMimeType("multipart/*")) { + Multipart mp = (Multipart) part.getContent(); + for (int i = 0; i < mp.getCount(); i++) { + String result = extractBody(mp.getBodyPart(i)); + if (result != null) { + return result; + } + } + } + return null; + } } \ No newline at end of file diff --git a/src/test/java/test/GreenMailConfig.java b/src/test/java/test/GreenMailConfig.java new file mode 100644 index 0000000..6f40a12 --- /dev/null +++ b/src/test/java/test/GreenMailConfig.java @@ -0,0 +1,19 @@ +package test; + +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.JavaMailSenderImpl; + +@TestConfiguration +public class GreenMailConfig { + @Bean + public JavaMailSender javaMailSender() { + JavaMailSenderImpl sender = new JavaMailSenderImpl(); + sender.setHost("localhost"); + sender.setPort(3025); + sender.setUsername("test@localhost"); + sender.setPassword("test"); + return sender; + } +} diff --git a/src/test/resources/application-database.properties b/src/test/resources/application-database.properties index 937d998..7f6d0ce 100644 --- a/src/test/resources/application-database.properties +++ b/src/test/resources/application-database.properties @@ -16,7 +16,7 @@ spring.mail.host=localhost spring.mail.port=3025 spring.mail.protocol=smtp spring.mail.username=test@localhost -spring.mail.password=password123 +spring.mail.password=test spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=false From a0691530dab6de7f3dbfd5a8feb3d6c73b29ab55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Florczak?= <84631301+florczaq@users.noreply.github.com> Date: Sat, 6 Sep 2025 14:56:47 +0200 Subject: [PATCH 102/116] Update docker-image.yml --- .github/workflows/docker-image.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index ea94ec7..58cd7db 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -42,6 +42,7 @@ jobs: SPRING_PROFILES_ACTIVE: ${{ secrets.SPRING_PROFILES_ACTIVE }} EMAIL_USERNAME: ${{ secrets.EMAIL_USERNAME }} EMAIL_PASSWORD: ${{ secrets.EMAIL_PASSWORD }} + JWT_SECRET_KEY: ${{secrets.JWT_SECRET_KEY}} steps: - name: Checkout code @@ -112,4 +113,4 @@ jobs: cache-to: type=gha,mode=max build-args: | EMAIL_USERNAME=${{ secrets.EMAIL_USERNAME }} - EMAIL_PASSWORD=${{ secrets.EMAIL_PASSWORD }} \ No newline at end of file + EMAIL_PASSWORD=${{ secrets.EMAIL_PASSWORD }} From b3764d535660f0f41bd51002837381e7da56d4ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Florczak?= <84631301+florczaq@users.noreply.github.com> Date: Sat, 6 Sep 2025 21:38:23 +0200 Subject: [PATCH 103/116] API Key logic --- logs/app.log | 122 ------------------ .../exceptions/IncorrectApiKeyValue.java | 9 ++ .../exceptions/MissingHeaderException.java | 7 + .../pkwmtt/global/GlobalExceptionHandler.java | 23 ++++ .../org/pkwmtt/global/RequestInterceptor.java | 45 +++++++ .../pkwmtt/global/admin/AdminController.java | 25 ++++ .../{ => global}/config/GlobalCorsConfig.java | 2 +- .../HighlightingCompositeLogConverter.java | 2 +- .../org/pkwmtt/global/config/WebConfig.java | 25 ++++ .../pkwmtt/security/apiKey/ApiKeyService.java | 34 +++++ .../pkwmtt/security/apiKey/entity/ApiKey.java | 26 ++++ .../apiKey/repository/ApiKeyRepository.java | 8 ++ .../security/config/SpringSecurity.java | 19 ++- src/main/resources/application.properties | 6 +- src/main/resources/logback.xml | 2 +- 15 files changed, 219 insertions(+), 136 deletions(-) create mode 100644 src/main/java/org/pkwmtt/exceptions/IncorrectApiKeyValue.java create mode 100644 src/main/java/org/pkwmtt/exceptions/MissingHeaderException.java create mode 100644 src/main/java/org/pkwmtt/global/GlobalExceptionHandler.java create mode 100644 src/main/java/org/pkwmtt/global/RequestInterceptor.java create mode 100644 src/main/java/org/pkwmtt/global/admin/AdminController.java rename src/main/java/org/pkwmtt/{ => global}/config/GlobalCorsConfig.java (97%) rename src/main/java/org/pkwmtt/{ => global}/config/HighlightingCompositeLogConverter.java (95%) create mode 100644 src/main/java/org/pkwmtt/global/config/WebConfig.java create mode 100644 src/main/java/org/pkwmtt/security/apiKey/ApiKeyService.java create mode 100644 src/main/java/org/pkwmtt/security/apiKey/entity/ApiKey.java create mode 100644 src/main/java/org/pkwmtt/security/apiKey/repository/ApiKeyRepository.java diff --git a/logs/app.log b/logs/app.log index 72bfde5..e69de29 100644 --- a/logs/app.log +++ b/logs/app.log @@ -1,122 +0,0 @@ -2025-09-05 18:54:59 ERROR o.s.boot.SpringApplication - Application run failed -org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Failed to initialize dependency 'dataSourceScriptDatabaseInitializer' of LoadTimeWeaverAware bean 'entityManagerFactory': Error creating bean with name 'dataSourceScriptDatabaseInitializer' defined in class path resource [org/springframework/boot/autoconfigure/sql/init/DataSourceInitializationConfiguration.class]: Failed to execute database script - at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:328) - at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:207) - at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:970) - at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:627) - at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752) - at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439) - at org.springframework.boot.SpringApplication.run(SpringApplication.java:318) - at org.springframework.boot.test.context.SpringBootContextLoader.lambda$loadContext$3(SpringBootContextLoader.java:144) - at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:58) - at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:46) - at org.springframework.boot.SpringApplication.withHook(SpringApplication.java:1461) - at org.springframework.boot.test.context.SpringBootContextLoader$ContextLoaderHook.run(SpringBootContextLoader.java:563) - at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:144) - at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:110) - at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:225) - at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:152) - at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:130) - at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:200) - at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:139) - at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:260) - at org.springframework.test.context.junit.jupiter.SpringExtension.postProcessTestInstance(SpringExtension.java:159) - at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$10(ClassBasedTestDescriptor.java:383) - at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.executeAndMaskThrowable(ClassBasedTestDescriptor.java:388) - at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$11(ClassBasedTestDescriptor.java:382) - at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) - at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) - at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179) - at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) - at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1708) - at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) - at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) - at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) - at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) - at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) - at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596) - at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeTestInstancePostProcessors(ClassBasedTestDescriptor.java:382) - at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$instantiateAndPostProcessTestInstance$6(ClassBasedTestDescriptor.java:293) - at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) - at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.instantiateAndPostProcessTestInstance(ClassBasedTestDescriptor.java:292) - at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$4(ClassBasedTestDescriptor.java:281) - at java.base/java.util.Optional.orElseGet(Optional.java:364) - at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$5(ClassBasedTestDescriptor.java:280) - at org.junit.jupiter.engine.execution.TestInstancesProvider.getTestInstances(TestInstancesProvider.java:27) - at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$before$2(ClassBasedTestDescriptor.java:205) - at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) - at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.before(ClassBasedTestDescriptor.java:204) - at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.before(ClassBasedTestDescriptor.java:84) - at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:153) - at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) - at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:146) - at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) - at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:144) - at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) - at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:143) - at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:100) - at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) - at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) - at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:160) - at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) - at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:146) - at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) - at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:144) - at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) - at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:143) - at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:100) - at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35) - at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57) - at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54) - at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:201) - at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:170) - at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:94) - at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:59) - at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:142) - at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:58) - at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:103) - at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:85) - at org.junit.platform.launcher.core.DelegatingLauncher.execute(DelegatingLauncher.java:47) - at org.junit.platform.launcher.core.InterceptingLauncher.lambda$execute$1(InterceptingLauncher.java:39) - at org.junit.platform.launcher.core.ClasspathAlignmentCheckingLauncherInterceptor.intercept(ClasspathAlignmentCheckingLauncherInterceptor.java:25) - at org.junit.platform.launcher.core.InterceptingLauncher.execute(InterceptingLauncher.java:38) - at org.junit.platform.launcher.core.DelegatingLauncher.execute(DelegatingLauncher.java:47) - at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:63) - at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:57) - at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38) - at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11) - at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35) - at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:231) - at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55) -Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSourceScriptDatabaseInitializer' defined in class path resource [org/springframework/boot/autoconfigure/sql/init/DataSourceInitializationConfiguration.class]: Failed to execute database script - at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1826) - at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:607) - at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529) - at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) - at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373) - at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) - at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) - at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315) - ... 87 common frames omitted -Caused by: org.springframework.jdbc.datasource.init.UncategorizedScriptException: Failed to execute database script - at org.springframework.jdbc.datasource.init.DatabasePopulatorUtils.execute(DatabasePopulatorUtils.java:67) - at org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer.runScripts(DataSourceScriptDatabaseInitializer.java:87) - at org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer.runScripts(AbstractScriptDatabaseInitializer.java:146) - at org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer.applyScripts(AbstractScriptDatabaseInitializer.java:108) - at org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer.applySchemaScripts(AbstractScriptDatabaseInitializer.java:98) - at org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer.initializeDatabase(AbstractScriptDatabaseInitializer.java:76) - at org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer.afterPropertiesSet(AbstractScriptDatabaseInitializer.java:66) - at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1873) - at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1822) - ... 94 common frames omitted -Caused by: java.lang.RuntimeException: Driver org.h2.Driver claims to not accept jdbcUrl, jdbc:mysql://localhost:3306/pktt?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC - at com.zaxxer.hikari.util.DriverDataSource.(DriverDataSource.java:111) - at com.zaxxer.hikari.pool.PoolBase.initializeDataSource(PoolBase.java:331) - at com.zaxxer.hikari.pool.PoolBase.(PoolBase.java:116) - at com.zaxxer.hikari.pool.HikariPool.(HikariPool.java:94) - at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:111) - at org.springframework.jdbc.datasource.DataSourceUtils.fetchConnection(DataSourceUtils.java:160) - at org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils.java:118) - at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:81) - at org.springframework.jdbc.datasource.init.DatabasePopulatorUtils.execute(DatabasePopulatorUtils.java:52) - ... 102 common frames omitted diff --git a/src/main/java/org/pkwmtt/exceptions/IncorrectApiKeyValue.java b/src/main/java/org/pkwmtt/exceptions/IncorrectApiKeyValue.java new file mode 100644 index 0000000..873c00a --- /dev/null +++ b/src/main/java/org/pkwmtt/exceptions/IncorrectApiKeyValue.java @@ -0,0 +1,9 @@ +package org.pkwmtt.exceptions; + +import com.mysql.cj.exceptions.WrongArgumentException; + +public class IncorrectApiKeyValue extends WrongArgumentException { + public IncorrectApiKeyValue () { + super("API Key authentication unsuccessful"); + } +} diff --git a/src/main/java/org/pkwmtt/exceptions/MissingHeaderException.java b/src/main/java/org/pkwmtt/exceptions/MissingHeaderException.java new file mode 100644 index 0000000..500cd0a --- /dev/null +++ b/src/main/java/org/pkwmtt/exceptions/MissingHeaderException.java @@ -0,0 +1,7 @@ +package org.pkwmtt.exceptions; + +public class MissingHeaderException extends Exception { + public MissingHeaderException (String headerName) { + super(String.format("Missing header: [%s]", headerName)); + } +} diff --git a/src/main/java/org/pkwmtt/global/GlobalExceptionHandler.java b/src/main/java/org/pkwmtt/global/GlobalExceptionHandler.java new file mode 100644 index 0000000..5438a5e --- /dev/null +++ b/src/main/java/org/pkwmtt/global/GlobalExceptionHandler.java @@ -0,0 +1,23 @@ +package org.pkwmtt.global; + +import org.apache.logging.log4j.util.InternalException; +import org.pkwmtt.exceptions.IncorrectApiKeyValue; +import org.pkwmtt.exceptions.dto.ErrorResponseDTO; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +@ControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(IncorrectApiKeyValue.class) + public ResponseEntity handleIncorrectApiKeyValue (Exception e) { + return new ResponseEntity<>(new ErrorResponseDTO(e.getMessage()), HttpStatus.UNAUTHORIZED); + } + + @ExceptionHandler(InternalException.class) + public ResponseEntity handleInternalException (Exception e) { + return new ResponseEntity<>(new ErrorResponseDTO(e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR); + } +} diff --git a/src/main/java/org/pkwmtt/global/RequestInterceptor.java b/src/main/java/org/pkwmtt/global/RequestInterceptor.java new file mode 100644 index 0000000..7bb2356 --- /dev/null +++ b/src/main/java/org/pkwmtt/global/RequestInterceptor.java @@ -0,0 +1,45 @@ +package org.pkwmtt.global; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.logging.log4j.util.InternalException; +import org.pkwmtt.exceptions.IncorrectApiKeyValue; +import org.pkwmtt.exceptions.MissingHeaderException; +import org.pkwmtt.security.apiKey.ApiKeyService; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +@Slf4j +@Component +@RequiredArgsConstructor +@Profile("!test & !database") //Skip on tests +public class RequestInterceptor implements HandlerInterceptor { + + private final ApiKeyService apiKeyService; + + @Override + public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) { + + + String headerName = "x-api-key"; + try { + String providedApiKey = request.getHeader(headerName); + + if (providedApiKey == null || providedApiKey.isBlank()) { + throw new MissingHeaderException(headerName); + } + + apiKeyService.validateApiKey(providedApiKey); + } catch (IncorrectApiKeyValue | MissingHeaderException e) { + throw new IncorrectApiKeyValue(); + } catch (Exception e) { + log.error(e.getMessage()); + throw new InternalException("Internal server error with validating API key."); + } + + return true; + } +} diff --git a/src/main/java/org/pkwmtt/global/admin/AdminController.java b/src/main/java/org/pkwmtt/global/admin/AdminController.java new file mode 100644 index 0000000..6025252 --- /dev/null +++ b/src/main/java/org/pkwmtt/global/admin/AdminController.java @@ -0,0 +1,25 @@ +package org.pkwmtt.global.admin; + +import lombok.RequiredArgsConstructor; +import org.pkwmtt.security.apiKey.ApiKeyService; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/admin") +public class AdminController { + private final ApiKeyService service; + + @GetMapping("") + public String adminPanel(){ + return "ADMIN"; + } + + @GetMapping("/api/key/generate") + public String generateApiKey (@RequestParam(name = "d") String description) { + return service.generateApiKey(description); + } +} diff --git a/src/main/java/org/pkwmtt/config/GlobalCorsConfig.java b/src/main/java/org/pkwmtt/global/config/GlobalCorsConfig.java similarity index 97% rename from src/main/java/org/pkwmtt/config/GlobalCorsConfig.java rename to src/main/java/org/pkwmtt/global/config/GlobalCorsConfig.java index 3942302..d05da52 100644 --- a/src/main/java/org/pkwmtt/config/GlobalCorsConfig.java +++ b/src/main/java/org/pkwmtt/global/config/GlobalCorsConfig.java @@ -1,4 +1,4 @@ -package org.pkwmtt.config; +package org.pkwmtt.global.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/src/main/java/org/pkwmtt/config/HighlightingCompositeLogConverter.java b/src/main/java/org/pkwmtt/global/config/HighlightingCompositeLogConverter.java similarity index 95% rename from src/main/java/org/pkwmtt/config/HighlightingCompositeLogConverter.java rename to src/main/java/org/pkwmtt/global/config/HighlightingCompositeLogConverter.java index dc2440b..24bf38f 100644 --- a/src/main/java/org/pkwmtt/config/HighlightingCompositeLogConverter.java +++ b/src/main/java/org/pkwmtt/global/config/HighlightingCompositeLogConverter.java @@ -1,4 +1,4 @@ -package org.pkwmtt.config; +package org.pkwmtt.global.config; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.spi.ILoggingEvent; diff --git a/src/main/java/org/pkwmtt/global/config/WebConfig.java b/src/main/java/org/pkwmtt/global/config/WebConfig.java new file mode 100644 index 0000000..268b28b --- /dev/null +++ b/src/main/java/org/pkwmtt/global/config/WebConfig.java @@ -0,0 +1,25 @@ +package org.pkwmtt.global.config; + +import lombok.RequiredArgsConstructor; +import org.pkwmtt.global.RequestInterceptor; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.util.Optional; + +@Configuration +@RequiredArgsConstructor +public class WebConfig implements WebMvcConfigurer { + + //During tests RequestInterceptor isn't required + private final Optional requestInterceptor; + private final Environment environment; + + @Override + public void addInterceptors (InterceptorRegistry registry) { + String apiPrefix = environment.getProperty("apiPrefix", ""); + requestInterceptor.ifPresent(interceptor -> registry.addInterceptor(interceptor).addPathPatterns(apiPrefix + "/**")); + } +} \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/security/apiKey/ApiKeyService.java b/src/main/java/org/pkwmtt/security/apiKey/ApiKeyService.java new file mode 100644 index 0000000..2715b34 --- /dev/null +++ b/src/main/java/org/pkwmtt/security/apiKey/ApiKeyService.java @@ -0,0 +1,34 @@ +package org.pkwmtt.security.apiKey; + +import lombok.RequiredArgsConstructor; +import org.pkwmtt.exceptions.IncorrectApiKeyValue; +import org.pkwmtt.security.apiKey.entity.ApiKey; +import org.pkwmtt.security.apiKey.repository.ApiKeyRepository; +import org.springframework.stereotype.Service; + +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class ApiKeyService { + + private final ApiKeyRepository repository; + + public String generateApiKey (String description) { + String value = UUID.randomUUID().toString(); + repository.save(new ApiKey(value, description)); + return value; + } + + public void validateApiKey (String value) throws IncorrectApiKeyValue { + try { + UUID.fromString(value); + } catch (IllegalArgumentException e) { + throw new IncorrectApiKeyValue(); + } + + if (!repository.existsApiKeyByValue(value)) { + throw new IncorrectApiKeyValue(); + } + } +} diff --git a/src/main/java/org/pkwmtt/security/apiKey/entity/ApiKey.java b/src/main/java/org/pkwmtt/security/apiKey/entity/ApiKey.java new file mode 100644 index 0000000..287f384 --- /dev/null +++ b/src/main/java/org/pkwmtt/security/apiKey/entity/ApiKey.java @@ -0,0 +1,26 @@ +package org.pkwmtt.security.apiKey.entity; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Table(name = "api_keys") +@NoArgsConstructor +@Getter +public class ApiKey { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer key_id; + + @Column(nullable = false) + private String value; + + @Column(nullable = false) + private String description; + + public ApiKey (String value, String description) { + this.value = value; + this.description = description; + } +} diff --git a/src/main/java/org/pkwmtt/security/apiKey/repository/ApiKeyRepository.java b/src/main/java/org/pkwmtt/security/apiKey/repository/ApiKeyRepository.java new file mode 100644 index 0000000..c33a6ee --- /dev/null +++ b/src/main/java/org/pkwmtt/security/apiKey/repository/ApiKeyRepository.java @@ -0,0 +1,8 @@ +package org.pkwmtt.security.apiKey.repository; + +import org.pkwmtt.security.apiKey.entity.ApiKey; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ApiKeyRepository extends JpaRepository { + boolean existsApiKeyByValue (String value); +} diff --git a/src/main/java/org/pkwmtt/security/config/SpringSecurity.java b/src/main/java/org/pkwmtt/security/config/SpringSecurity.java index 0b11b2a..93c430b 100644 --- a/src/main/java/org/pkwmtt/security/config/SpringSecurity.java +++ b/src/main/java/org/pkwmtt/security/config/SpringSecurity.java @@ -3,29 +3,28 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.web.SecurityFilterChain; import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS; -//@EnableWebSecurity +@EnableWebSecurity @Slf4j @Configuration public class SpringSecurity { - + @Bean - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + public SecurityFilterChain filterChain (HttpSecurity http) throws Exception { log.info("Configuring Security Filter Chain..."); http - .cors(withDefaults()) - .csrf(AbstractHttpConfigurer::disable) - .authorizeHttpRequests(auth -> auth - .requestMatchers("/**").permitAll() - .anyRequest().authenticated() - ) - .sessionManagement(session -> session.sessionCreationPolicy(STATELESS)); + .cors(withDefaults()) + .csrf(AbstractHttpConfigurer::disable) + .authorizeHttpRequests(auth -> auth.requestMatchers("/**").permitAll().anyRequest().authenticated()) + .sessionManagement(session -> session.sessionCreationPolicy(STATELESS)); log.info("Configuring Success..."); return http.build(); } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index f7e0844..ed73b56 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -28,4 +28,8 @@ spring.mail.password=${EMAIL_PASSWORD:} spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=true -apiPrefix=/pkwmtt/api/v1 \ No newline at end of file +apiPrefix=/pkwmtt/api/v1 + +spring.security.user.name=admin +spring.security.user.password==${ADMIN_PASSWORD} + diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 49859dc..b860289 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -6,7 +6,7 @@ + converterClass="org.pkwmtt.global.config.HighlightingCompositeLogConverter"/> From d531b0da87e7949e77e3049ce32845529128bf73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Florczak?= <84631301+florczaq@users.noreply.github.com> Date: Sat, 6 Sep 2025 21:39:42 +0200 Subject: [PATCH 104/116] API Key logic --- src/main/java/org/pkwmtt/global/RequestInterceptor.java | 3 ++- src/main/java/org/pkwmtt/global/config/WebConfig.java | 3 ++- src/main/java/org/pkwmtt/security/config/SpringSecurity.java | 1 - 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/pkwmtt/global/RequestInterceptor.java b/src/main/java/org/pkwmtt/global/RequestInterceptor.java index 7bb2356..ec49c8c 100644 --- a/src/main/java/org/pkwmtt/global/RequestInterceptor.java +++ b/src/main/java/org/pkwmtt/global/RequestInterceptor.java @@ -2,6 +2,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.logging.log4j.util.InternalException; @@ -21,7 +22,7 @@ public class RequestInterceptor implements HandlerInterceptor { private final ApiKeyService apiKeyService; @Override - public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) { + public boolean preHandle (@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) { String headerName = "x-api-key"; diff --git a/src/main/java/org/pkwmtt/global/config/WebConfig.java b/src/main/java/org/pkwmtt/global/config/WebConfig.java index 268b28b..02f5e39 100644 --- a/src/main/java/org/pkwmtt/global/config/WebConfig.java +++ b/src/main/java/org/pkwmtt/global/config/WebConfig.java @@ -1,5 +1,6 @@ package org.pkwmtt.global.config; +import lombok.NonNull; import lombok.RequiredArgsConstructor; import org.pkwmtt.global.RequestInterceptor; import org.springframework.context.annotation.Configuration; @@ -18,7 +19,7 @@ public class WebConfig implements WebMvcConfigurer { private final Environment environment; @Override - public void addInterceptors (InterceptorRegistry registry) { + public void addInterceptors (@NonNull InterceptorRegistry registry) { String apiPrefix = environment.getProperty("apiPrefix", ""); requestInterceptor.ifPresent(interceptor -> registry.addInterceptor(interceptor).addPathPatterns(apiPrefix + "/**")); } diff --git a/src/main/java/org/pkwmtt/security/config/SpringSecurity.java b/src/main/java/org/pkwmtt/security/config/SpringSecurity.java index 93c430b..e6e94fc 100644 --- a/src/main/java/org/pkwmtt/security/config/SpringSecurity.java +++ b/src/main/java/org/pkwmtt/security/config/SpringSecurity.java @@ -3,7 +3,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; From a4e3e0a34fc490d21d0113ae492894b00684d0ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Florczak?= <84631301+florczaq@users.noreply.github.com> Date: Sat, 6 Sep 2025 22:32:45 +0200 Subject: [PATCH 105/116] Admin key validation --- .../org/pkwmtt/global/RequestInterceptor.java | 2 +- .../pkwmtt/global/admin/AdminController.java | 7 +- .../global/admin/AdminRequestInterceptor.java | 40 ++++++++++ .../config/SwaggerEndpointConfiguration.java | 74 +++++++++++++++++++ .../org/pkwmtt/global/config/WebConfig.java | 7 +- 5 files changed, 127 insertions(+), 3 deletions(-) create mode 100644 src/main/java/org/pkwmtt/global/admin/AdminRequestInterceptor.java create mode 100644 src/main/java/org/pkwmtt/global/config/SwaggerEndpointConfiguration.java diff --git a/src/main/java/org/pkwmtt/global/RequestInterceptor.java b/src/main/java/org/pkwmtt/global/RequestInterceptor.java index ec49c8c..112501b 100644 --- a/src/main/java/org/pkwmtt/global/RequestInterceptor.java +++ b/src/main/java/org/pkwmtt/global/RequestInterceptor.java @@ -25,7 +25,7 @@ public class RequestInterceptor implements HandlerInterceptor { public boolean preHandle (@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) { - String headerName = "x-api-key"; + String headerName = "X-API-KEY"; try { String providedApiKey = request.getHeader(headerName); diff --git a/src/main/java/org/pkwmtt/global/admin/AdminController.java b/src/main/java/org/pkwmtt/global/admin/AdminController.java index 6025252..75a9712 100644 --- a/src/main/java/org/pkwmtt/global/admin/AdminController.java +++ b/src/main/java/org/pkwmtt/global/admin/AdminController.java @@ -1,5 +1,8 @@ package org.pkwmtt.global.admin; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; import lombok.RequiredArgsConstructor; import org.pkwmtt.security.apiKey.ApiKeyService; import org.springframework.web.bind.annotation.GetMapping; @@ -14,11 +17,13 @@ public class AdminController { private final ApiKeyService service; @GetMapping("") - public String adminPanel(){ + @Operation(summary = "Admin endpoint", parameters = {@Parameter(name = "X-ADMIN-KEY", in = ParameterIn.HEADER, required = true, description = "Admin access key")}) + public String adminPanel () { return "ADMIN"; } @GetMapping("/api/key/generate") + @Operation(summary = "Admin endpoint", parameters = {@Parameter(name = "X-ADMIN-KEY", in = ParameterIn.HEADER, required = true, description = "Admin access key")}) public String generateApiKey (@RequestParam(name = "d") String description) { return service.generateApiKey(description); } diff --git a/src/main/java/org/pkwmtt/global/admin/AdminRequestInterceptor.java b/src/main/java/org/pkwmtt/global/admin/AdminRequestInterceptor.java new file mode 100644 index 0000000..1295681 --- /dev/null +++ b/src/main/java/org/pkwmtt/global/admin/AdminRequestInterceptor.java @@ -0,0 +1,40 @@ +package org.pkwmtt.global.admin; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.apache.logging.log4j.util.InternalException; +import org.pkwmtt.exceptions.IncorrectApiKeyValue; +import org.pkwmtt.exceptions.MissingHeaderException; +import org.pkwmtt.security.apiKey.ApiKeyService; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +@RequiredArgsConstructor +@Component +public class AdminRequestInterceptor implements HandlerInterceptor { + private final ApiKeyService apiKeyService; + + //TODO seperate api key storage for admin keys + @Override + public boolean preHandle (@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) { + String headerName = "X-ADMIN-KEY"; + try { + String providedApiKey = request.getHeader(headerName); + + if (providedApiKey == null || providedApiKey.isBlank()) { + throw new MissingHeaderException(headerName); + } + + apiKeyService.validateApiKey(providedApiKey); + } catch (IncorrectApiKeyValue | MissingHeaderException e) { + throw new IncorrectApiKeyValue(); + } catch (Exception e) { + throw new InternalException("Internal server error with validating API key."); + } + + return true; + } + +} diff --git a/src/main/java/org/pkwmtt/global/config/SwaggerEndpointConfiguration.java b/src/main/java/org/pkwmtt/global/config/SwaggerEndpointConfiguration.java new file mode 100644 index 0000000..ea7228a --- /dev/null +++ b/src/main/java/org/pkwmtt/global/config/SwaggerEndpointConfiguration.java @@ -0,0 +1,74 @@ +package org.pkwmtt.global.config; + + +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.Paths; +import io.swagger.v3.oas.models.media.StringSchema; +import io.swagger.v3.oas.models.parameters.Parameter; +import lombok.RequiredArgsConstructor; +import org.springdoc.core.models.GroupedOpenApi; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; + +@Configuration +@RequiredArgsConstructor +public class SwaggerEndpointConfiguration { + + private final Environment environment; + + //Add text field for api key to every request that need authentication with it + @Bean + public GroupedOpenApi publicEndpointCustomizer () { + String apiPrefix = environment.getProperty("apiPrefix", ""); + + return GroupedOpenApi.builder().group("all") // single group + .pathsToMatch(apiPrefix + "/**", "/admin/**").addOpenApiCustomizer(openApi -> { + Paths paths = openApi.getPaths(); + + paths.forEach((path, pathItem) -> pathItem.readOperations().forEach(operation -> { + if (path.startsWith("/admin")) { + addHeaderIfMissing( + operation, + "X-ADMIN-KEY", + "Admin API key", + "Admin-only endpoint", + "Requires X-ADMIN-KEY header", + "admin" + ); + } else if (path.startsWith(apiPrefix)) { + addHeaderIfMissing( + operation, + "X-API-KEY", + "Your API key", + "Public API endpoint", + "Requires X-API-KEY header", + "public" + ); + } + })); + }).build(); + } + + private void addHeaderIfMissing (Operation operation, String headerName, String headerDescription, String summary, String description, String tag) { + + boolean alreadyExists = operation.getParameters() != null && operation + .getParameters() + .stream() + .anyMatch(p -> p.getName().equalsIgnoreCase(headerName) && "header".equalsIgnoreCase(p.getIn())); + + if (!alreadyExists) { + operation.setSummary(summary); + operation.setDescription(description); + operation.addTagsItem(tag); + operation.addParametersItem(new Parameter() + .name(headerName) + .in("header") + .required(true) + .description(headerDescription) + .schema(new StringSchema())); + } + } + + +} diff --git a/src/main/java/org/pkwmtt/global/config/WebConfig.java b/src/main/java/org/pkwmtt/global/config/WebConfig.java index 02f5e39..c61c1bc 100644 --- a/src/main/java/org/pkwmtt/global/config/WebConfig.java +++ b/src/main/java/org/pkwmtt/global/config/WebConfig.java @@ -3,6 +3,7 @@ import lombok.NonNull; import lombok.RequiredArgsConstructor; import org.pkwmtt.global.RequestInterceptor; +import org.pkwmtt.global.admin.AdminRequestInterceptor; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; @@ -16,11 +17,15 @@ public class WebConfig implements WebMvcConfigurer { //During tests RequestInterceptor isn't required private final Optional requestInterceptor; + private final AdminRequestInterceptor adminRequestInterceptor; private final Environment environment; @Override public void addInterceptors (@NonNull InterceptorRegistry registry) { String apiPrefix = environment.getProperty("apiPrefix", ""); - requestInterceptor.ifPresent(interceptor -> registry.addInterceptor(interceptor).addPathPatterns(apiPrefix + "/**")); + requestInterceptor.ifPresent(interceptor -> registry + .addInterceptor(interceptor) + .addPathPatterns(apiPrefix + "/**")); + registry.addInterceptor(adminRequestInterceptor).addPathPatterns("/admin"); } } \ No newline at end of file From d25991f7a6a64236264a7b63dfe12f5c38347e71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Florczak?= <84631301+florczaq@users.noreply.github.com> Date: Sat, 6 Sep 2025 23:18:19 +0200 Subject: [PATCH 106/116] Admin Key logic --- init.sql | 231 +++++++----------- .../org/pkwmtt/global/RequestInterceptor.java | 3 +- .../pkwmtt/global/admin/AdminController.java | 30 --- .../config/SwaggerEndpointConfiguration.java | 26 +- .../org/pkwmtt/global/config/WebConfig.java | 2 +- .../security/admin/AdminController.java | 32 +++ .../admin/AdminRequestInterceptor.java | 6 +- .../security/admin/entity/AdminKey.java | 19 ++ .../admin/repository/AdminKeyRepository.java | 8 + .../pkwmtt/security/apiKey/ApiKeyService.java | 46 +++- .../pkwmtt/security/apiKey/entity/ApiKey.java | 19 +- .../apiKey/entity/BaseApiKeyEntity.java | 25 ++ 12 files changed, 239 insertions(+), 208 deletions(-) delete mode 100644 src/main/java/org/pkwmtt/global/admin/AdminController.java create mode 100644 src/main/java/org/pkwmtt/security/admin/AdminController.java rename src/main/java/org/pkwmtt/{global => security}/admin/AdminRequestInterceptor.java (90%) create mode 100644 src/main/java/org/pkwmtt/security/admin/entity/AdminKey.java create mode 100644 src/main/java/org/pkwmtt/security/admin/repository/AdminKeyRepository.java create mode 100644 src/main/java/org/pkwmtt/security/apiKey/entity/BaseApiKeyEntity.java diff --git a/init.sql b/init.sql index 5703969..5d8ed30 100644 --- a/init.sql +++ b/init.sql @@ -1,11 +1,11 @@ -- phpMyAdmin SQL Dump --- version 5.2.2 +-- version 5.2.0 -- https://www.phpmyadmin.net/ -- --- Host: db --- Generation Time: Aug 27, 2025 at 06:52 PM --- Wersja serwera: 9.3.0 --- Wersja PHP: 8.2.27 +-- Host: 127.0.0.1 +-- Czas generowania: 06 Wrz 2025, 23:16 +-- Wersja serwera: 10.4.27-MariaDB +-- Wersja PHP: 8.0.25 SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; START TRANSACTION; @@ -20,23 +20,69 @@ SET time_zone = "+00:00"; -- -- Baza danych: `pktt` -- -CREATE DATABASE IF NOT EXISTS `pktt` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci; +CREATE DATABASE IF NOT EXISTS `pktt` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE `pktt`; -- -------------------------------------------------------- +-- +-- Struktura tabeli dla tabeli `admin_keys` +-- + +DROP TABLE IF EXISTS `admin_keys`; +CREATE TABLE IF NOT EXISTS `admin_keys` ( + `key_id` int(11) NOT NULL AUTO_INCREMENT, + `value` varchar(255) NOT NULL, + `description` varchar(255) NOT NULL, + PRIMARY KEY (`key_id`), + UNIQUE KEY `unique_value` (`value`) +) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- +-- Zrzut danych tabeli `admin_keys` +-- + +INSERT INTO `admin_keys` (`key_id`, `value`, `description`) VALUES +(3, '0923cd6f-cd33-4883-87e4-ae3b50b80a3f', 'mikolaj'); + +-- -------------------------------------------------------- + +-- +-- Struktura tabeli dla tabeli `api_keys` +-- + +DROP TABLE IF EXISTS `api_keys`; +CREATE TABLE IF NOT EXISTS `api_keys` ( + `key_id` int(11) NOT NULL AUTO_INCREMENT, + `value` varchar(255) NOT NULL, + `description` varchar(255) NOT NULL, + PRIMARY KEY (`key_id`), + UNIQUE KEY `unique_value` (`value`) +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- +-- Zrzut danych tabeli `api_keys` +-- + +INSERT INTO `api_keys` (`key_id`, `value`, `description`) VALUES +(1, 'ca3bdabb-b559-41ca-9e96-2c27d6199017', 'test'); + +-- -------------------------------------------------------- + -- -- Struktura tabeli dla tabeli `exams` -- DROP TABLE IF EXISTS `exams`; -CREATE TABLE `exams` ( - `exam_id` int NOT NULL, +CREATE TABLE IF NOT EXISTS `exams` ( + `exam_id` int(11) NOT NULL AUTO_INCREMENT, `title` varchar(255) NOT NULL, `description` varchar(255) DEFAULT NULL, `exam_date` datetime NOT NULL, - `exam_type_id` int NOT NULL -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; + `exam_type_id` int(11) NOT NULL, + PRIMARY KEY (`exam_id`), + KEY `exam_type_id_idx` (`exam_type_id`) +) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; -- -- Zrzut danych tabeli `exams` @@ -57,11 +103,14 @@ INSERT INTO `exams` (`exam_id`, `title`, `description`, `exam_date`, `exam_type_ -- DROP TABLE IF EXISTS `exams_groups`; -CREATE TABLE `exams_groups` ( - `exam_group_id` int NOT NULL, - `exam_id` int NOT NULL, - `group_id` int NOT NULL -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +CREATE TABLE IF NOT EXISTS `exams_groups` ( + `exam_group_id` int(11) NOT NULL AUTO_INCREMENT, + `exam_id` int(11) NOT NULL, + `group_id` int(11) NOT NULL, + PRIMARY KEY (`exam_group_id`), + KEY `exam_id_idx` (`exam_id`), + KEY `group_id_idx` (`group_id`) +) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; -- -- Zrzut danych tabeli `exams_groups` @@ -90,10 +139,11 @@ INSERT INTO `exams_groups` (`exam_group_id`, `exam_id`, `group_id`) VALUES -- DROP TABLE IF EXISTS `exam_type`; -CREATE TABLE `exam_type` ( - `exam_type_id` int NOT NULL, - `name` varchar(255) NOT NULL -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +CREATE TABLE IF NOT EXISTS `exam_type` ( + `exam_type_id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL, + PRIMARY KEY (`exam_type_id`) +) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; -- -- Zrzut danych tabeli `exam_type` @@ -111,10 +161,11 @@ INSERT INTO `exam_type` (`exam_type_id`, `name`) VALUES -- DROP TABLE IF EXISTS `general_group`; -CREATE TABLE `general_group` ( - `general_group_id` int NOT NULL, - `name` varchar(255) NOT NULL -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +CREATE TABLE IF NOT EXISTS `general_group` ( + `general_group_id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL, + PRIMARY KEY (`general_group_id`) +) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; -- -- Zrzut danych tabeli `general_group` @@ -133,12 +184,14 @@ INSERT INTO `general_group` (`general_group_id`, `name`) VALUES -- DROP TABLE IF EXISTS `otp_codes`; -CREATE TABLE `otp_codes` ( - `otp_code_id` int NOT NULL, +CREATE TABLE IF NOT EXISTS `otp_codes` ( + `otp_code_id` int(11) NOT NULL AUTO_INCREMENT, `code` varchar(255) NOT NULL, - `expire` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - `general_group_id` int NOT NULL -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; + `expire` timestamp NOT NULL DEFAULT current_timestamp(), + `general_group_id` int(11) NOT NULL, + PRIMARY KEY (`otp_code_id`), + KEY `general_group_id_idx` (`general_group_id`) +) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; -- -- Zrzut danych tabeli `otp_codes` @@ -157,10 +210,12 @@ INSERT INTO `otp_codes` (`otp_code_id`, `code`, `expire`, `general_group_id`) VA -- DROP TABLE IF EXISTS `student_groups`; -CREATE TABLE `student_groups` ( - `group_id` int NOT NULL, - `name` varchar(255) NOT NULL -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +CREATE TABLE IF NOT EXISTS `student_groups` ( + `group_id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL, + PRIMARY KEY (`group_id`), + UNIQUE KEY `name` (`name`) +) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; -- -- Zrzut danych tabeli `student_groups` @@ -184,13 +239,15 @@ INSERT INTO `student_groups` (`group_id`, `name`) VALUES -- DROP TABLE IF EXISTS `users`; -CREATE TABLE `users` ( - `user_id` int NOT NULL, - `general_group_id` int NOT NULL, +CREATE TABLE IF NOT EXISTS `users` ( + `user_id` int(11) NOT NULL AUTO_INCREMENT, + `general_group_id` int(11) NOT NULL, `email` varchar(255) NOT NULL, - `is_active` tinyint(1) NOT NULL DEFAULT '1', - `role` enum('ADMIN','REPRESENTATIVE') NOT NULL DEFAULT 'REPRESENTATIVE' -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; + `is_active` tinyint(1) NOT NULL DEFAULT 1, + `role` enum('ADMIN','REPRESENTATIVE') NOT NULL DEFAULT 'REPRESENTATIVE', + PRIMARY KEY (`user_id`), + KEY `general_group_id_idx` (`general_group_id`) +) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; -- -- Zrzut danych tabeli `users` @@ -202,104 +259,6 @@ INSERT INTO `users` (`user_id`, `general_group_id`, `email`, `is_active`, `role` (3, 19, 'user13k@example.com', 1, 'REPRESENTATIVE'), (4, 20, 'user14m@example.com', 1, 'ADMIN'); --- --- Indeksy dla zrzutów tabel --- - --- --- Indeksy dla tabeli `exams` --- -ALTER TABLE `exams` - ADD PRIMARY KEY (`exam_id`), - ADD KEY `exam_type_id_idx` (`exam_type_id`); - --- --- Indeksy dla tabeli `exams_groups` --- -ALTER TABLE `exams_groups` - ADD PRIMARY KEY (`exam_group_id`), - ADD KEY `exam_id_idx` (`exam_id`), - ADD KEY `group_id_idx` (`group_id`); - --- --- Indeksy dla tabeli `exam_type` --- -ALTER TABLE `exam_type` - ADD PRIMARY KEY (`exam_type_id`); - --- --- Indeksy dla tabeli `general_group` --- -ALTER TABLE `general_group` - ADD PRIMARY KEY (`general_group_id`); - --- --- Indeksy dla tabeli `otp_codes` --- -ALTER TABLE `otp_codes` - ADD PRIMARY KEY (`otp_code_id`), - ADD KEY `general_group_id_idx` (`general_group_id`); - --- --- Indeksy dla tabeli `student_groups` --- -ALTER TABLE `student_groups` - ADD PRIMARY KEY (`group_id`), - ADD UNIQUE KEY `name` (`name`); - --- --- Indeksy dla tabeli `users` --- -ALTER TABLE `users` - ADD PRIMARY KEY (`user_id`), - ADD KEY `general_group_id_idx` (`general_group_id`); - --- --- AUTO_INCREMENT dla zrzuconych tabel --- - --- --- AUTO_INCREMENT dla tabeli `exams` --- -ALTER TABLE `exams` - MODIFY `exam_id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=7; - --- --- AUTO_INCREMENT dla tabeli `exams_groups` --- -ALTER TABLE `exams_groups` - MODIFY `exam_group_id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=21; - --- --- AUTO_INCREMENT dla tabeli `exam_type` --- -ALTER TABLE `exam_type` - MODIFY `exam_type_id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4; - --- --- AUTO_INCREMENT dla tabeli `general_group` --- -ALTER TABLE `general_group` - MODIFY `general_group_id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=21; - --- --- AUTO_INCREMENT dla tabeli `otp_codes` --- -ALTER TABLE `otp_codes` - MODIFY `otp_code_id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=5; - --- --- AUTO_INCREMENT dla tabeli `student_groups` --- -ALTER TABLE `student_groups` - MODIFY `group_id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=21; - --- --- AUTO_INCREMENT dla tabeli `users` --- -ALTER TABLE `users` - MODIFY `user_id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=5; - -- -- Ograniczenia dla zrzutów tabel -- diff --git a/src/main/java/org/pkwmtt/global/RequestInterceptor.java b/src/main/java/org/pkwmtt/global/RequestInterceptor.java index 112501b..45de179 100644 --- a/src/main/java/org/pkwmtt/global/RequestInterceptor.java +++ b/src/main/java/org/pkwmtt/global/RequestInterceptor.java @@ -6,6 +6,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.logging.log4j.util.InternalException; +import org.pkwmtt.examCalendar.enums.Role; import org.pkwmtt.exceptions.IncorrectApiKeyValue; import org.pkwmtt.exceptions.MissingHeaderException; import org.pkwmtt.security.apiKey.ApiKeyService; @@ -33,7 +34,7 @@ public boolean preHandle (@NonNull HttpServletRequest request, @NonNull HttpServ throw new MissingHeaderException(headerName); } - apiKeyService.validateApiKey(providedApiKey); + apiKeyService.validateApiKey(providedApiKey, Role.REPRESENTATIVE); } catch (IncorrectApiKeyValue | MissingHeaderException e) { throw new IncorrectApiKeyValue(); } catch (Exception e) { diff --git a/src/main/java/org/pkwmtt/global/admin/AdminController.java b/src/main/java/org/pkwmtt/global/admin/AdminController.java deleted file mode 100644 index 75a9712..0000000 --- a/src/main/java/org/pkwmtt/global/admin/AdminController.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.pkwmtt.global.admin; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.enums.ParameterIn; -import lombok.RequiredArgsConstructor; -import org.pkwmtt.security.apiKey.ApiKeyService; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/admin") -public class AdminController { - private final ApiKeyService service; - - @GetMapping("") - @Operation(summary = "Admin endpoint", parameters = {@Parameter(name = "X-ADMIN-KEY", in = ParameterIn.HEADER, required = true, description = "Admin access key")}) - public String adminPanel () { - return "ADMIN"; - } - - @GetMapping("/api/key/generate") - @Operation(summary = "Admin endpoint", parameters = {@Parameter(name = "X-ADMIN-KEY", in = ParameterIn.HEADER, required = true, description = "Admin access key")}) - public String generateApiKey (@RequestParam(name = "d") String description) { - return service.generateApiKey(description); - } -} diff --git a/src/main/java/org/pkwmtt/global/config/SwaggerEndpointConfiguration.java b/src/main/java/org/pkwmtt/global/config/SwaggerEndpointConfiguration.java index ea7228a..9263a2a 100644 --- a/src/main/java/org/pkwmtt/global/config/SwaggerEndpointConfiguration.java +++ b/src/main/java/org/pkwmtt/global/config/SwaggerEndpointConfiguration.java @@ -51,23 +51,15 @@ public GroupedOpenApi publicEndpointCustomizer () { } private void addHeaderIfMissing (Operation operation, String headerName, String headerDescription, String summary, String description, String tag) { - - boolean alreadyExists = operation.getParameters() != null && operation - .getParameters() - .stream() - .anyMatch(p -> p.getName().equalsIgnoreCase(headerName) && "header".equalsIgnoreCase(p.getIn())); - - if (!alreadyExists) { - operation.setSummary(summary); - operation.setDescription(description); - operation.addTagsItem(tag); - operation.addParametersItem(new Parameter() - .name(headerName) - .in("header") - .required(true) - .description(headerDescription) - .schema(new StringSchema())); - } + operation.setSummary(summary); + operation.setDescription(description); + operation.addTagsItem(tag); + operation.addParametersItem(new Parameter() + .name(headerName) + .in("header") + .required(true) + .description(headerDescription) + .schema(new StringSchema())); } diff --git a/src/main/java/org/pkwmtt/global/config/WebConfig.java b/src/main/java/org/pkwmtt/global/config/WebConfig.java index c61c1bc..e900c37 100644 --- a/src/main/java/org/pkwmtt/global/config/WebConfig.java +++ b/src/main/java/org/pkwmtt/global/config/WebConfig.java @@ -3,7 +3,7 @@ import lombok.NonNull; import lombok.RequiredArgsConstructor; import org.pkwmtt.global.RequestInterceptor; -import org.pkwmtt.global.admin.AdminRequestInterceptor; +import org.pkwmtt.security.admin.AdminRequestInterceptor; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; diff --git a/src/main/java/org/pkwmtt/security/admin/AdminController.java b/src/main/java/org/pkwmtt/security/admin/AdminController.java new file mode 100644 index 0000000..b0bd2af --- /dev/null +++ b/src/main/java/org/pkwmtt/security/admin/AdminController.java @@ -0,0 +1,32 @@ +package org.pkwmtt.security.admin; + +import lombok.RequiredArgsConstructor; +import org.pkwmtt.examCalendar.enums.Role; +import org.pkwmtt.security.apiKey.ApiKeyService; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/admin") +public class AdminController { + private final ApiKeyService service; + + @GetMapping("") + public String adminPanel () { + return "ADMIN"; + } + + @PostMapping("/api/keys/generate") + public String generateApiKey (@RequestParam(name = "d") String description, @RequestParam(name = "r") Role role) { + return service.generateApiKey(description, role); + } + + @GetMapping("/api/keys") + public Map getMapOfPublicApiKeys () { + return service.getMapOfPublicApiKeys(); + } + + +} diff --git a/src/main/java/org/pkwmtt/global/admin/AdminRequestInterceptor.java b/src/main/java/org/pkwmtt/security/admin/AdminRequestInterceptor.java similarity index 90% rename from src/main/java/org/pkwmtt/global/admin/AdminRequestInterceptor.java rename to src/main/java/org/pkwmtt/security/admin/AdminRequestInterceptor.java index 1295681..5683b46 100644 --- a/src/main/java/org/pkwmtt/global/admin/AdminRequestInterceptor.java +++ b/src/main/java/org/pkwmtt/security/admin/AdminRequestInterceptor.java @@ -1,10 +1,11 @@ -package org.pkwmtt.global.admin; +package org.pkwmtt.security.admin; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.NonNull; import lombok.RequiredArgsConstructor; import org.apache.logging.log4j.util.InternalException; +import org.pkwmtt.examCalendar.enums.Role; import org.pkwmtt.exceptions.IncorrectApiKeyValue; import org.pkwmtt.exceptions.MissingHeaderException; import org.pkwmtt.security.apiKey.ApiKeyService; @@ -16,7 +17,6 @@ public class AdminRequestInterceptor implements HandlerInterceptor { private final ApiKeyService apiKeyService; - //TODO seperate api key storage for admin keys @Override public boolean preHandle (@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) { String headerName = "X-ADMIN-KEY"; @@ -27,7 +27,7 @@ public boolean preHandle (@NonNull HttpServletRequest request, @NonNull HttpServ throw new MissingHeaderException(headerName); } - apiKeyService.validateApiKey(providedApiKey); + apiKeyService.validateApiKey(providedApiKey, Role.ADMIN); } catch (IncorrectApiKeyValue | MissingHeaderException e) { throw new IncorrectApiKeyValue(); } catch (Exception e) { diff --git a/src/main/java/org/pkwmtt/security/admin/entity/AdminKey.java b/src/main/java/org/pkwmtt/security/admin/entity/AdminKey.java new file mode 100644 index 0000000..3d6031b --- /dev/null +++ b/src/main/java/org/pkwmtt/security/admin/entity/AdminKey.java @@ -0,0 +1,19 @@ +package org.pkwmtt.security.admin.entity; + +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.pkwmtt.security.apiKey.entity.BaseApiKeyEntity; + +@Entity +@Table(name = "admin_keys") +@NoArgsConstructor +@Getter +public class AdminKey extends BaseApiKeyEntity { + + public AdminKey (String value, String description) { + super(value, description); + } + +} diff --git a/src/main/java/org/pkwmtt/security/admin/repository/AdminKeyRepository.java b/src/main/java/org/pkwmtt/security/admin/repository/AdminKeyRepository.java new file mode 100644 index 0000000..a6d8744 --- /dev/null +++ b/src/main/java/org/pkwmtt/security/admin/repository/AdminKeyRepository.java @@ -0,0 +1,8 @@ +package org.pkwmtt.security.admin.repository; + +import org.pkwmtt.security.admin.entity.AdminKey; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface AdminKeyRepository extends JpaRepository { + boolean existsApiKeyByValue (String value); +} diff --git a/src/main/java/org/pkwmtt/security/apiKey/ApiKeyService.java b/src/main/java/org/pkwmtt/security/apiKey/ApiKeyService.java index 2715b34..53b79da 100644 --- a/src/main/java/org/pkwmtt/security/apiKey/ApiKeyService.java +++ b/src/main/java/org/pkwmtt/security/apiKey/ApiKeyService.java @@ -1,34 +1,68 @@ package org.pkwmtt.security.apiKey; import lombok.RequiredArgsConstructor; +import org.pkwmtt.examCalendar.enums.Role; import org.pkwmtt.exceptions.IncorrectApiKeyValue; +import org.pkwmtt.security.admin.entity.AdminKey; +import org.pkwmtt.security.admin.repository.AdminKeyRepository; import org.pkwmtt.security.apiKey.entity.ApiKey; import org.pkwmtt.security.apiKey.repository.ApiKeyRepository; import org.springframework.stereotype.Service; +import java.util.HashMap; +import java.util.Map; import java.util.UUID; @Service @RequiredArgsConstructor public class ApiKeyService { - private final ApiKeyRepository repository; + private final ApiKeyRepository apiKeyRepository; + private final AdminKeyRepository adminKeyRepository; - public String generateApiKey (String description) { + public String generateApiKey (String description, Role role) { String value = UUID.randomUUID().toString(); - repository.save(new ApiKey(value, description)); + if (role == Role.REPRESENTATIVE) { + apiKeyRepository.save(new ApiKey(value, description)); + } else if (role == Role.ADMIN) { + adminKeyRepository.save(new AdminKey(value, description)); + } return value; } - public void validateApiKey (String value) throws IncorrectApiKeyValue { + public void validateApiKey (String value, Role role) throws IncorrectApiKeyValue { try { UUID.fromString(value); } catch (IllegalArgumentException e) { throw new IncorrectApiKeyValue(); } - if (!repository.existsApiKeyByValue(value)) { - throw new IncorrectApiKeyValue(); + + if (existsInAdminKeyBase(value)) { // Admin can access all endpoint + return; + } + + if (role != Role.ADMIN && existsInPublicKeyBase(value)) { //Normal user access + return; } + + throw new IncorrectApiKeyValue(); + } + + public boolean existsInPublicKeyBase (String value) { + return apiKeyRepository.existsApiKeyByValue(value); } + + public boolean existsInAdminKeyBase (String value) { + return adminKeyRepository.existsApiKeyByValue(value); + } + + public Map getMapOfPublicApiKeys () { + Map objectMap = new HashMap<>(); + + apiKeyRepository.findAll().forEach(item -> objectMap.put(item.getValue(), item.getDescription())); + + return objectMap; + } + } diff --git a/src/main/java/org/pkwmtt/security/apiKey/entity/ApiKey.java b/src/main/java/org/pkwmtt/security/apiKey/entity/ApiKey.java index 287f384..ec5f2ae 100644 --- a/src/main/java/org/pkwmtt/security/apiKey/entity/ApiKey.java +++ b/src/main/java/org/pkwmtt/security/apiKey/entity/ApiKey.java @@ -6,21 +6,12 @@ @Entity @Table(name = "api_keys") -@NoArgsConstructor @Getter -public class ApiKey { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Integer key_id; - - @Column(nullable = false) - private String value; - - @Column(nullable = false) - private String description; +@NoArgsConstructor +public class ApiKey extends BaseApiKeyEntity { public ApiKey (String value, String description) { - this.value = value; - this.description = description; + super(value, description); } -} + +} \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/security/apiKey/entity/BaseApiKeyEntity.java b/src/main/java/org/pkwmtt/security/apiKey/entity/BaseApiKeyEntity.java new file mode 100644 index 0000000..58343ee --- /dev/null +++ b/src/main/java/org/pkwmtt/security/apiKey/entity/BaseApiKeyEntity.java @@ -0,0 +1,25 @@ +package org.pkwmtt.security.apiKey.entity; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@MappedSuperclass +@NoArgsConstructor +@Getter +public abstract class BaseApiKeyEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + protected Integer key_id; + + @Column(nullable = false) + protected String value; + + @Column(nullable = false) + protected String description; + + public BaseApiKeyEntity (String value, String description) { + this.value = value; + this.description = description; + } +} From 22ad7401e3afef7e5048a067f8b819e5766433d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Florczak?= <84631301+florczaq@users.noreply.github.com> Date: Sat, 6 Sep 2025 23:30:11 +0200 Subject: [PATCH 107/116] Fix: missing env variable --- src/main/resources/application.properties | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index ed73b56..8c45a56 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -30,6 +30,4 @@ spring.mail.properties.mail.smtp.starttls.enable=true apiPrefix=/pkwmtt/api/v1 -spring.security.user.name=admin -spring.security.user.password==${ADMIN_PASSWORD} From 3751132311da3c08323154a63b7d65cb7c1668e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Florczak?= <84631301+florczaq@users.noreply.github.com> Date: Sat, 6 Sep 2025 23:46:36 +0200 Subject: [PATCH 108/116] Delete logs directory --- logs/app.log | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 logs/app.log diff --git a/logs/app.log b/logs/app.log deleted file mode 100644 index e69de29..0000000 From f1f0814990c4471a699df8087f4405dead6f97ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Florczak?= <84631301+florczaq@users.noreply.github.com> Date: Sun, 7 Sep 2025 14:30:12 +0200 Subject: [PATCH 109/116] Add endpoint: list of subjects --- .../pkwmtt/timetable/TimetableController.java | 38 +++++++++---------- .../pkwmtt/timetable/TimetableService.java | 35 ++++++++++------- 2 files changed, 39 insertions(+), 34 deletions(-) diff --git a/src/main/java/org/pkwmtt/timetable/TimetableController.java b/src/main/java/org/pkwmtt/timetable/TimetableController.java index 6543654..f7490d6 100644 --- a/src/main/java/org/pkwmtt/timetable/TimetableController.java +++ b/src/main/java/org/pkwmtt/timetable/TimetableController.java @@ -19,7 +19,7 @@ public class TimetableController { private final TimetableService service; private final TimetableCacheService cachedService; - + /** * Provide schedule of specified group and filters if all provided * @@ -29,20 +29,15 @@ public class TimetableController { * @throws WebPageContentNotAvailableException . */ @GetMapping("/{generalGroupName}") - public ResponseEntity getGeneralGroupSchedule ( - @PathVariable String generalGroupName, - @RequestParam(required = false, name = "sub") List subgroups) - throws WebPageContentNotAvailableException, SpecifiedGeneralGroupDoesntExistsException, - SpecifiedSubGroupDoesntExistsException, JsonProcessingException { - + public ResponseEntity getGeneralGroupSchedule (@PathVariable String generalGroupName, @RequestParam(required = false, name = "sub") List subgroups) + throws WebPageContentNotAvailableException, SpecifiedGeneralGroupDoesntExistsException, SpecifiedSubGroupDoesntExistsException, JsonProcessingException { + if (isNull(subgroups) || subgroups.isEmpty()) { return ResponseEntity.ok(cachedService.getGeneralGroupSchedule(generalGroupName)); } - return ResponseEntity.ok(service.getFilteredGeneralGroupSchedule( - generalGroupName, subgroups - )); + return ResponseEntity.ok(service.getFilteredGeneralGroupSchedule(generalGroupName, subgroups)); } - + /** * Provides list of schedule hours * @@ -50,22 +45,20 @@ public ResponseEntity getGeneralGroupSchedule ( * @throws WebPageContentNotAvailableException . */ @GetMapping("/hours") - public ResponseEntity> getListOfHours () - throws WebPageContentNotAvailableException { + public ResponseEntity> getListOfHours () throws WebPageContentNotAvailableException { return ResponseEntity.ok(cachedService.getListOfHours()); } - + /** * Provides list of general groups * * @return list of general groups */ @GetMapping("/groups/general") - public ResponseEntity> getListOfGeneralGroups () - throws WebPageContentNotAvailableException { + public ResponseEntity> getListOfGeneralGroups () throws WebPageContentNotAvailableException { return ResponseEntity.ok(service.getGeneralGroupList()); } - + /** * Provides list of available subgroups for specified general group * @@ -75,10 +68,13 @@ public ResponseEntity> getListOfGeneralGroups () */ @GetMapping("/groups/{generalGroupName}") public ResponseEntity> getListOfAvailableGroups (@PathVariable String generalGroupName) - throws JsonProcessingException, SpecifiedGeneralGroupDoesntExistsException, - WebPageContentNotAvailableException { + throws JsonProcessingException, SpecifiedGeneralGroupDoesntExistsException, WebPageContentNotAvailableException { return ResponseEntity.ok(service.getAvailableSubGroups(generalGroupName)); } - - + + @GetMapping("/{generalGroupName}/list") + public ResponseEntity> getListOfSubjects (@PathVariable String generalGroupName) { + return ResponseEntity.ok(service.getListOfSubjects(generalGroupName)); + } + } diff --git a/src/main/java/org/pkwmtt/timetable/TimetableService.java b/src/main/java/org/pkwmtt/timetable/TimetableService.java index 903b220..55d0ce5 100644 --- a/src/main/java/org/pkwmtt/timetable/TimetableService.java +++ b/src/main/java/org/pkwmtt/timetable/TimetableService.java @@ -7,6 +7,7 @@ import org.pkwmtt.exceptions.SpecifiedSubGroupDoesntExistsException; import org.pkwmtt.exceptions.WebPageContentNotAvailableException; import org.pkwmtt.timetable.dto.DayOfWeekDTO; +import org.pkwmtt.timetable.dto.SubjectDTO; import org.pkwmtt.timetable.dto.TimetableDTO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -36,8 +37,7 @@ public class TimetableService { * @throws JsonProcessingException if timetable conversion to JSON fails */ public List getAvailableSubGroups (String generalGroupName) - throws JsonProcessingException, SpecifiedGeneralGroupDoesntExistsException, - WebPageContentNotAvailableException { + throws JsonProcessingException, SpecifiedGeneralGroupDoesntExistsException, WebPageContentNotAvailableException { generalGroupName = generalGroupName.toUpperCase(); TimetableDTO timetable = cachedService.getGeneralGroupSchedule(generalGroupName); @@ -76,8 +76,7 @@ public List getAvailableSubGroups (String generalGroupName) * @throws WebPageContentNotAvailableException if source data can't be retrieved */ public TimetableDTO getFilteredGeneralGroupSchedule (String generalGroupName, List sub) - throws WebPageContentNotAvailableException, SpecifiedGeneralGroupDoesntExistsException, - JsonProcessingException { + throws WebPageContentNotAvailableException, SpecifiedGeneralGroupDoesntExistsException, JsonProcessingException { generalGroupName = generalGroupName.toUpperCase(); @@ -89,9 +88,7 @@ public TimetableDTO getFilteredGeneralGroupSchedule (String generalGroupName, Li } } - List schedule = cachedService - .getGeneralGroupSchedule(generalGroupName) - .getData(); + List schedule = cachedService.getGeneralGroupSchedule(generalGroupName).getData(); for (var day : schedule) { @@ -107,12 +104,24 @@ public TimetableDTO getFilteredGeneralGroupSchedule (String generalGroupName, Li * @return List of general group's names */ public List getGeneralGroupList () throws WebPageContentNotAvailableException { - return cachedService - .getGeneralGroupsMap() - .keySet() - .stream() - .sorted() - .collect(Collectors.toList()); + return cachedService.getGeneralGroupsMap().keySet().stream().sorted().collect(Collectors.toList()); + } + + public List getListOfSubjects (String generalGroupName) { + var subjectSet = new HashSet(); + var schedule = cachedService.getGeneralGroupSchedule(generalGroupName); + + schedule.getData().forEach(day -> { + day.getEven().forEach(subject -> addToSet(subjectSet, subject)); + day.getOdd().forEach(subject -> addToSet(subjectSet, subject)); + }); + + return subjectSet.stream().toList(); + } + + private void addToSet (Set subjectSet, SubjectDTO subject) { + subject.deleteTypeAndUnnecessaryCharactersFromName(); + subjectSet.add(subject.getName()); } } From 5098ab3aeacc19fdac85ba0b87d13c1e51b29628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Piotrkowski?= Date: Mon, 8 Sep 2025 01:26:37 +0200 Subject: [PATCH 110/116] feat: added filter for validating token --- .../security/token/filter/JwtFilter.java | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 src/main/java/org/pkwmtt/security/token/filter/JwtFilter.java diff --git a/src/main/java/org/pkwmtt/security/token/filter/JwtFilter.java b/src/main/java/org/pkwmtt/security/token/filter/JwtFilter.java new file mode 100644 index 0000000..a6bb6be --- /dev/null +++ b/src/main/java/org/pkwmtt/security/token/filter/JwtFilter.java @@ -0,0 +1,69 @@ +package org.pkwmtt.security.token.filter; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.pkwmtt.examCalendar.entity.User; +import org.pkwmtt.examCalendar.repository.UserRepository; +import org.pkwmtt.security.token.JwtService; +import org.pkwmtt.security.token.dto.UserDTO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; +import java.util.List; + +@Component +public class JwtFilter extends OncePerRequestFilter { + + @Autowired + private JwtService jwtService; + + @Autowired + private UserRepository userRepository; + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + + String authHeader = request.getHeader("Authorization"); + String token = null; + String email = null; + + if (authHeader != null && authHeader.startsWith("Bearer ")) { + token = authHeader.substring(7); + email = jwtService.getUserEmailFromToken(token); + } + + if (email != null && SecurityContextHolder.getContext().getAuthentication() == null) { + User user = userRepository.findByEmail(email).orElseThrow(); + + if (jwtService.validateToken(token, user)) { + List authorities = List.of( + new SimpleGrantedAuthority("ROLE_" + user.getRole()) + ); + + UsernamePasswordAuthenticationToken authToken = + new UsernamePasswordAuthenticationToken( + user.getEmail(), + null, + authorities + ); + + authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authToken); + } + } + + filterChain.doFilter(request, response); + } +} From b2ffd23f06d5e08391d8acc6c27fbbfcdb807e7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Piotrkowski?= Date: Mon, 8 Sep 2025 01:37:13 +0200 Subject: [PATCH 111/116] feat: add token validation logic and comments --- .../org/pkwmtt/security/token/JwtService.java | 7 +- .../pkwmtt/security/token/JwtServiceImpl.java | 68 ++++++++++++++++--- .../security/token/filter/JwtFilter.java | 18 ++++- 3 files changed, 80 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/pkwmtt/security/token/JwtService.java b/src/main/java/org/pkwmtt/security/token/JwtService.java index e248f86..e62bc7b 100644 --- a/src/main/java/org/pkwmtt/security/token/JwtService.java +++ b/src/main/java/org/pkwmtt/security/token/JwtService.java @@ -1,9 +1,12 @@ package org.pkwmtt.security.token; +import org.pkwmtt.examCalendar.entity.User; import org.pkwmtt.security.token.dto.UserDTO; +import java.util.Optional; + public interface JwtService { String generateToken(UserDTO user); - Boolean validateToken(String token); - String getUserIdFromToken(String token); + Boolean validateToken(String token, User user); + String getUserEmailFromToken(String token); } diff --git a/src/main/java/org/pkwmtt/security/token/JwtServiceImpl.java b/src/main/java/org/pkwmtt/security/token/JwtServiceImpl.java index f94079e..20133dd 100644 --- a/src/main/java/org/pkwmtt/security/token/JwtServiceImpl.java +++ b/src/main/java/org/pkwmtt/security/token/JwtServiceImpl.java @@ -1,9 +1,11 @@ package org.pkwmtt.security.token; +import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtException; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.security.Keys; import lombok.RequiredArgsConstructor; +import org.pkwmtt.examCalendar.entity.User; import org.pkwmtt.security.token.dto.UserDTO; import org.pkwmtt.security.token.utils.JwtUtils; import org.springframework.stereotype.Service; @@ -11,13 +13,13 @@ import javax.crypto.SecretKey; import java.util.Base64; import java.util.Date; +import java.util.function.Function; @Service @RequiredArgsConstructor public class JwtServiceImpl implements JwtService { private final JwtUtils jwtUtils; - /** * Generates a JWT token for a given user. * The token contains user's email, group, and role as claims, @@ -38,7 +40,6 @@ public String generateToken(UserDTO user) { .compact(); } - /** * Decode a secret key for signing JWT. * The key is decoded from Base64 stored in JwtUtils configuration. @@ -58,10 +59,12 @@ private SecretKey decodeSecretKey(){ * @return true if the token is valid, false otherwise */ @Override - public Boolean validateToken(String token) { + public Boolean validateToken(String token, User user) { try { - // TODO: add logic to validate the token - return true; + final String userEmail = getUserEmailFromToken(token); + return userEmail != null + && userEmail.equals(user.getEmail()) + && !isTokenExpired(token); } catch (JwtException | IllegalArgumentException e) { return false; } @@ -74,8 +77,57 @@ public Boolean validateToken(String token) { * @return user email from token */ @Override - public String getUserIdFromToken(String token) { - // TODO: implement token parsing to extract subject/email - return ""; + public String getUserEmailFromToken(String token) { + return extractClaim(token, Claims::getSubject); + } + + /** + * Extracts the expiration date from a JWT token. + * + * @param token JWT token string + * @return expiration date of the token + */ + private Date getExpirationDateFromToken(String token) { + return extractClaim(token, Claims::getExpiration); + } + + /** + * Checks whether a JWT token has expired. + * + * @param token JWT token string + * @return true if the token is expired, false otherwise + */ + private boolean isTokenExpired(String token){ + return getExpirationDateFromToken(token).before(new Date()); + } + + /** + * Extracts a specific claim from a JWT token using a claim resolver function. + * + * @param type of the claim + * @param token JWT token string + * @param claimResolver function to extract the desired claim from Claims + * @return the extracted claim of type T + */ + private T extractClaim(String token, Function claimResolver) { + final Claims claims = extractAllClaims(token); + return claimResolver.apply(claims); + } + + /** + * Parses the JWT token and returns all claims contained in its payload. + *

+ * The method verifies the token signature using the secret key. + * + * @param token JWT token string + * @return Claims object containing all claims from the token payload + * @throws JwtException if the token is invalid or the signature does not match + */ + private Claims extractAllClaims(String token) { + return Jwts.parser() + .verifyWith(decodeSecretKey()) + .build() + .parseSignedClaims(token) + .getPayload(); } } diff --git a/src/main/java/org/pkwmtt/security/token/filter/JwtFilter.java b/src/main/java/org/pkwmtt/security/token/filter/JwtFilter.java index a6bb6be..8b43e4f 100644 --- a/src/main/java/org/pkwmtt/security/token/filter/JwtFilter.java +++ b/src/main/java/org/pkwmtt/security/token/filter/JwtFilter.java @@ -7,13 +7,10 @@ import org.pkwmtt.examCalendar.entity.User; import org.pkwmtt.examCalendar.repository.UserRepository; import org.pkwmtt.security.token.JwtService; -import org.pkwmtt.security.token.dto.UserDTO; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; @@ -30,6 +27,21 @@ public class JwtFilter extends OncePerRequestFilter { @Autowired private UserRepository userRepository; + /** + * Filters incoming HTTP requests to validate JWT tokens. + * + *

This filter: + * - Extracts the JWT token from the Authorization header. + * - Validates the token using JwtService. + * - Loads the user from UserRepository. + * - Sets the Spring Security Authentication in the SecurityContext. + * + * @param request the HttpServletRequest + * @param response the HttpServletResponse + * @param filterChain the FilterChain + * @throws ServletException if a servlet error occurs + * @throws IOException if an I/O error occurs + */ @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, From a1a3944a6a6a9223a3d14f1e3e88c26c1d03cb86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Piotrkowski?= Date: Mon, 8 Sep 2025 02:00:43 +0200 Subject: [PATCH 112/116] feat: add tests --- .../pkwmtt/security/token/JwtServiceImpl.java | 4 +- .../security/token/filter/JwtFilter.java | 4 +- .../security/token/JwtServiceImplTest.java | 139 ++++++++++++++++++ .../security/token/filter/JwtFilterTest.java | 58 ++++++++ 4 files changed, 201 insertions(+), 4 deletions(-) create mode 100644 src/test/java/org/pkwmtt/security/token/JwtServiceImplTest.java create mode 100644 src/test/java/org/pkwmtt/security/token/filter/JwtFilterTest.java diff --git a/src/main/java/org/pkwmtt/security/token/JwtServiceImpl.java b/src/main/java/org/pkwmtt/security/token/JwtServiceImpl.java index 20133dd..0e2858d 100644 --- a/src/main/java/org/pkwmtt/security/token/JwtServiceImpl.java +++ b/src/main/java/org/pkwmtt/security/token/JwtServiceImpl.java @@ -46,7 +46,7 @@ public String generateToken(UserDTO user) { * * @return secret key for JWT signing */ - private SecretKey decodeSecretKey(){ + SecretKey decodeSecretKey(){ byte[] decodedKey = Base64.getDecoder().decode(jwtUtils.getSecret()); return Keys.hmacShaKeyFor(decodedKey); } @@ -109,7 +109,7 @@ private boolean isTokenExpired(String token){ * @param claimResolver function to extract the desired claim from Claims * @return the extracted claim of type T */ - private T extractClaim(String token, Function claimResolver) { + T extractClaim(String token, Function claimResolver) { final Claims claims = extractAllClaims(token); return claimResolver.apply(claims); } diff --git a/src/main/java/org/pkwmtt/security/token/filter/JwtFilter.java b/src/main/java/org/pkwmtt/security/token/filter/JwtFilter.java index 8b43e4f..f5d6749 100644 --- a/src/main/java/org/pkwmtt/security/token/filter/JwtFilter.java +++ b/src/main/java/org/pkwmtt/security/token/filter/JwtFilter.java @@ -22,10 +22,10 @@ public class JwtFilter extends OncePerRequestFilter { @Autowired - private JwtService jwtService; + JwtService jwtService; @Autowired - private UserRepository userRepository; + UserRepository userRepository; /** * Filters incoming HTTP requests to validate JWT tokens. diff --git a/src/test/java/org/pkwmtt/security/token/JwtServiceImplTest.java b/src/test/java/org/pkwmtt/security/token/JwtServiceImplTest.java new file mode 100644 index 0000000..e3fb9a0 --- /dev/null +++ b/src/test/java/org/pkwmtt/security/token/JwtServiceImplTest.java @@ -0,0 +1,139 @@ +package org.pkwmtt.security.token; + +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.pkwmtt.examCalendar.entity.User; +import org.pkwmtt.examCalendar.enums.Role; +import org.pkwmtt.security.token.dto.UserDTO; +import org.pkwmtt.security.token.utils.JwtUtils; + +import java.util.Base64; +import java.util.Date; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class JwtServiceImplTest { + + private JwtServiceImpl jwtService; + + @BeforeEach + void setUp() { + JwtUtils jwtUtils = mock(JwtUtils.class); + + byte[] keyBytes = new byte[32]; + for (int i = 0; i < 32; i++) keyBytes[i] = (byte) i; + String secretBase64 = Base64.getEncoder().encodeToString(keyBytes); + + when(jwtUtils.getSecret()).thenReturn(secretBase64); + when(jwtUtils.getExpirationMs()).thenReturn(1000 * 60 * 60L); // 1h + + jwtService = new JwtServiceImpl(jwtUtils); + } + + @Test + void generateToken_shouldCreateNonEmptyToken() { + UserDTO user = new UserDTO() + .setEmail("user@example.com") + .setGroup("GROUP1") + .setRole(Role.ADMIN); + + String token = jwtService.generateToken(user); + assertNotNull(token); + assertFalse(token.isEmpty()); + } + + @Test + void getUserEmailFromToken_shouldReturnCorrectEmail() { + UserDTO user = new UserDTO() + .setEmail("user@example.com") + .setGroup("GROUP1") + .setRole(Role.ADMIN); + + String token = jwtService.generateToken(user); + String email = jwtService.getUserEmailFromToken(token); + assertEquals("user@example.com", email); + } + + @Test + void extractRoleFromToken_shouldReturnCorrectRole() { + UserDTO user = new UserDTO() + .setEmail("user@example.com") + .setGroup("GROUP1") + .setRole(Role.ADMIN); + + String token = jwtService.generateToken(user); + String roleClaim = jwtService.extractClaim(token, claims -> claims.get("role", String.class)); + assertEquals("ADMIN", roleClaim); + } + + @Test + void extractGroupFromToken_shouldReturnCorrectGroup() { + UserDTO user = new UserDTO() + .setEmail("user@example.com") + .setGroup("GROUP1") + .setRole(Role.ADMIN); + + String token = jwtService.generateToken(user); + String groupClaim = jwtService.extractClaim(token, claims -> claims.get("group", String.class)); + assertEquals("GROUP1", groupClaim); + } + + @Test + void validateToken_shouldReturnTrueForValidToken() { + UserDTO userDTO = new UserDTO() + .setEmail("user@example.com") + .setGroup("GROUP1") + .setRole(Role.ADMIN); + + String token = jwtService.generateToken(userDTO); + User mockUser = mock(User.class); + when(mockUser.getEmail()).thenReturn("user@example.com"); + assertTrue(jwtService.validateToken(token, mockUser)); + } + + @Test + void validateToken_shouldReturnFalseForInvalidEmail() { + UserDTO userDTO = new UserDTO() + .setEmail("user@example.com") + .setGroup("GROUP1") + .setRole(Role.ADMIN); + + String token = jwtService.generateToken(userDTO); + User mockUser = mock(User.class); + when(mockUser.getEmail()).thenReturn("other@example.com"); + assertFalse(jwtService.validateToken(token, mockUser)); + } + + @Test + void validateToken_shouldReturnFalseForExpiredToken() { + UserDTO user = new UserDTO() + .setEmail("user@example.com") + .setGroup("GROUP1") + .setRole(Role.ADMIN); + + long pastExpiration = System.currentTimeMillis() - 1000; + String expiredToken = Jwts.builder() + .subject(user.getEmail()) + .claim("group", user.getGroup()) + .claim("role", user.getRole()) + .issuedAt(new Date(System.currentTimeMillis() - 2000)) + .expiration(new Date(pastExpiration)) + .signWith(jwtService.decodeSecretKey()) + .compact(); + + User mockUser = mock(User.class); + when(mockUser.getEmail()).thenReturn("user@example.com"); + + assertFalse(jwtService.validateToken(expiredToken, mockUser)); + } + + @Test + void getUserEmailFromToken_shouldThrowExceptionForInvalidToken() { + String invalidToken = "invalid.token.value"; + assertThrows(JwtException.class, () -> jwtService.getUserEmailFromToken(invalidToken)); + } +} diff --git a/src/test/java/org/pkwmtt/security/token/filter/JwtFilterTest.java b/src/test/java/org/pkwmtt/security/token/filter/JwtFilterTest.java new file mode 100644 index 0000000..54123dc --- /dev/null +++ b/src/test/java/org/pkwmtt/security/token/filter/JwtFilterTest.java @@ -0,0 +1,58 @@ +package org.pkwmtt.security.token.filter; + +import jakarta.servlet.FilterChain; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.pkwmtt.examCalendar.entity.User; +import org.pkwmtt.examCalendar.enums.Role; +import org.pkwmtt.examCalendar.repository.UserRepository; +import org.pkwmtt.security.token.JwtService; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.core.context.SecurityContextHolder; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class JwtFilterTest { + + private JwtService jwtService; + private UserRepository userRepository; + private JwtFilter jwtFilter; + + @BeforeEach + void setUp() { + jwtService = mock(JwtService.class); + userRepository = mock(UserRepository.class); + jwtFilter = new JwtFilter(); + jwtFilter.jwtService = jwtService; + jwtFilter.userRepository = userRepository; + + SecurityContextHolder.clearContext(); + } + + @Test + void givenValidToken_whenDoFilter_thenAuthenticationSet() throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("Authorization", "Bearer validToken"); + MockHttpServletResponse response = new MockHttpServletResponse(); + FilterChain filterChain = mock(FilterChain.class); + + User mockUser = mock(User.class); + when(mockUser.getRole()).thenReturn(Role.valueOf("ADMIN")); + when(mockUser.getEmail()).thenReturn("user@example.com"); + + when(jwtService.getUserEmailFromToken("validToken")).thenReturn("user@example.com"); + when(jwtService.validateToken(eq("validToken"), any(User.class))).thenReturn(true); + when(userRepository.findByEmail("user@example.com")).thenReturn(Optional.of(mockUser)); + + jwtFilter.doFilterInternal(request, response, filterChain); + + assertNotNull(SecurityContextHolder.getContext().getAuthentication()); + } +} From 9b760b407516e2454dff0529396d4bb8a071afab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Piotrkowski?= Date: Mon, 8 Sep 2025 02:02:15 +0200 Subject: [PATCH 113/116] fix: tests --- src/test/java/org/pkwmtt/security/token/JwtServiceImplTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/pkwmtt/security/token/JwtServiceImplTest.java b/src/test/java/org/pkwmtt/security/token/JwtServiceImplTest.java index e3fb9a0..9c50238 100644 --- a/src/test/java/org/pkwmtt/security/token/JwtServiceImplTest.java +++ b/src/test/java/org/pkwmtt/security/token/JwtServiceImplTest.java @@ -29,7 +29,7 @@ void setUp() { String secretBase64 = Base64.getEncoder().encodeToString(keyBytes); when(jwtUtils.getSecret()).thenReturn(secretBase64); - when(jwtUtils.getExpirationMs()).thenReturn(1000 * 60 * 60L); // 1h + when(jwtUtils.getExpirationMs()).thenReturn(1000L * 60 * 60 * 24 * 30 * 6); jwtService = new JwtServiceImpl(jwtUtils); } From 7752be479fcfb68bfa5c3abfa2eea6fbe1bc576d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Piotrkowski?= Date: Mon, 8 Sep 2025 19:01:49 +0200 Subject: [PATCH 114/116] fix: changed docker compose to automatically build backend and run all containers, moved priv data to env --- Dockerfile | 1 + docker-compose.yml | 8 +++++ .../resources/application-prod.properties | 34 +++++++++++++------ src/main/resources/application.properties | 30 ++++++++-------- 4 files changed, 47 insertions(+), 26 deletions(-) diff --git a/Dockerfile b/Dockerfile index f2676ca..e16ea75 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,5 +6,6 @@ RUN mvn clean package -DskipTests FROM amazoncorretto:21 WORKDIR /app COPY --from=build /app/target/*.jar app.jar +COPY .env .env EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/docker-compose.yml b/docker-compose.yml index a722b7d..23a0f45 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,13 @@ version: "3.9" services: + app: + build: + context: . + dockerfile: Dockerfile + container_name: pkwmtt-backend + ports: + - "8080:8080" + restart: always db: image: mysql environment: diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties index cd609c7..bff5ace 100644 --- a/src/main/resources/application-prod.properties +++ b/src/main/resources/application-prod.properties @@ -1,17 +1,31 @@ -spring.datasource.url=jdbc:mysql://localhost:3306/pktt?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC -spring.datasource.username=pkttuser -spring.datasource.password=pkttpassword - -server.port=8080 -server.address=0.0.0.0 - -spring.jpa.show-sql=true +### Properties for deployment +#Import .env variables +spring.config.import=optional:file:.env[.properties] +#Database +spring.datasource.url=${SPRING_DATASOURCE_URL} +spring.datasource.username=${SPRING_DATASOURCE_USERNAME} +spring.datasource.password=${SPRING_DATASOURCE_PASSWORD} +spring.jpa.show-sql=false spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false spring.jpa.hibernate.ddl-auto=none spring.datasource.hikari.initialization-fail-timeout=0 - +#Server properties +server.port=8080 +server.address=0.0.0.0 +#Logging logging.file.name=logs/app.log logging.file.path=logs - +#Cache spring.cache.type=caffeine +#Test +logging.level.WireMock.my-mock=off +#Mail +spring.mail.host=smtp.gmail.com +spring.mail.port=587 +spring.mail.username=${EMAIL_USERNAME:} +spring.mail.password=${EMAIL_PASSWORD:} +spring.mail.properties.mail.smtp.auth=true +spring.mail.properties.mail.smtp.starttls.enable=true +#Path +apiPrefix=/pkwmtt/api/v1 \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8c45a56..876e8ad 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,33 +1,31 @@ +### Properties for deployment #Import .env variables spring.config.import=optional:file:.env[.properties] - -spring.datasource.url=jdbc:mysql://localhost:3306/pktt?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC -spring.datasource.username=pkttuser -spring.datasource.password=pkttpassword - -server.port=8080 -server.address=0.0.0.0 - +#Database +spring.datasource.url=${SPRING_DATASOURCE_URL} +spring.datasource.username=${SPRING_DATASOURCE_USERNAME} +spring.datasource.password=${SPRING_DATASOURCE_PASSWORD} spring.jpa.show-sql=true spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false spring.jpa.hibernate.ddl-auto=none spring.datasource.hikari.initialization-fail-timeout=0 - +#Server properties +server.port=8080 +server.address=0.0.0.0 +#Logging logging.file.name=logs/app.log logging.file.path=logs - +#Cache spring.cache.type=caffeine - +#Test logging.level.WireMock.my-mock=off - +#Mail spring.mail.host=smtp.gmail.com spring.mail.port=587 spring.mail.username=${EMAIL_USERNAME:} spring.mail.password=${EMAIL_PASSWORD:} spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=true - -apiPrefix=/pkwmtt/api/v1 - - +#Path +apiPrefix=/pkwmtt/api/v1 \ No newline at end of file From 53a5df204e06b0583746253e763e43ef9ace73c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Piotrkowski?= Date: Mon, 8 Sep 2025 19:12:11 +0200 Subject: [PATCH 115/116] fix: take profile from .env --- docker-compose.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 23a0f45..87636af 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,6 +8,8 @@ services: ports: - "8080:8080" restart: always + environment: + SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE} db: image: mysql environment: From 513250387a840da44e93f0975240c0b789b66155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Piotrkowski?= Date: Mon, 8 Sep 2025 19:34:15 +0200 Subject: [PATCH 116/116] fix: remove error with copy --- Dockerfile | 1 - 1 file changed, 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index e16ea75..f2676ca 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,6 +6,5 @@ RUN mvn clean package -DskipTests FROM amazoncorretto:21 WORKDIR /app COPY --from=build /app/target/*.jar app.jar -COPY .env .env EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"]