From 8e26fc77bed5e36a71c5ddb285619bc4db093c11 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Mon, 8 Sep 2025 17:54:14 +0200 Subject: [PATCH 1/7] prevent form duplicating exams --- .../examCalendar/ExamControllerAdvice.java | 5 ++ .../org/pkwmtt/examCalendar/ExamService.java | 10 ++- .../repository/ExamRepository.java | 2 + .../ResourceAlreadyExistsException.java | 7 ++ .../examCalendar/ExamControllerTest.java | 22 +++++- .../pkwmtt/examCalendar/ExamServiceTest.java | 79 ++++++++++++++++--- 6 files changed, 108 insertions(+), 17 deletions(-) create mode 100644 src/main/java/org/pkwmtt/exceptions/ResourceAlreadyExistsException.java diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java b/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java index 20a9e8b..ea77710 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamControllerAdvice.java @@ -45,4 +45,9 @@ public ResponseEntity handleConstraintViolationException(Const .collect(Collectors.joining(", ")); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorResponseDTO(message)); } + + @ExceptionHandler(ResourceAlreadyExistsException.class) + public ResponseEntity handleConflicts(RuntimeException e) { + return ResponseEntity.status(HttpStatus.CONFLICT).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 58621b0..8935ecf 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamService.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamService.java @@ -36,12 +36,15 @@ 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(); + Exam exam = ExamDtoMapper.mapToNewExam(examDto, groups, examType); + Set existingExam = examRepository.findAllByTitle(exam.getTitle()); + if(existingExam.contains(exam)) + throw new ResourceAlreadyExistsException("Exam already exists"); + return examRepository.save(exam).getExamId(); } /** @@ -49,12 +52,11 @@ 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)); Set groups = verifyAndUpdateExamGroups(examDto); -// check if exam type exists ExamType examType = examTypeRepository.findByName(examDto.getExamType()) .orElseThrow(() -> new ExamTypeNotExistsException(examDto.getExamType())); diff --git a/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java index c87b283..00ad156 100644 --- a/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java +++ b/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java @@ -10,6 +10,8 @@ public interface ExamRepository extends JpaRepository { + Set findAllByTitle(String title); + /** * @param groups set of generalGroups * @return list of exams for generalGroups diff --git a/src/main/java/org/pkwmtt/exceptions/ResourceAlreadyExistsException.java b/src/main/java/org/pkwmtt/exceptions/ResourceAlreadyExistsException.java new file mode 100644 index 0000000..d1de752 --- /dev/null +++ b/src/main/java/org/pkwmtt/exceptions/ResourceAlreadyExistsException.java @@ -0,0 +1,7 @@ +package org.pkwmtt.exceptions; + +public class ResourceAlreadyExistsException extends RuntimeException { + public ResourceAlreadyExistsException(String message) { + super(message); + } +} diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java index 44c1549..40565e3 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java @@ -128,6 +128,24 @@ void addExamWithCorrectData() throws Exception { assertEquals(examDtoRequest.getExamType(), examResponse.getExamType().getName()); } + @Test + @Transactional + void addExamTwice() throws Exception { +// given + createExampleExamType("Project"); + ExamDto examDtoRequest = createExampleExamDto("Project"); + + when(timetableService.getGeneralGroupList()).thenReturn(List.of("12K1","12K2","12K3")); + when(timetableService.getAvailableSubGroups("12K2")).thenReturn(List.of("K04","L04","P04")); + +// when + assertPostRequest(status().isCreated(), examDtoRequest); + MvcResult result = assertPostRequest(status().isConflict(), examDtoRequest); +// then + assertResponseMessage("Exam already exists", result); + assertEquals(1, examRepository.findAllByTitle(examDtoRequest.getTitle()).size()); + } + @Test void addExamWithBlankExamTitle() throws Exception { // given @@ -340,7 +358,7 @@ void addExamWithTooLongExamTitle() throws Exception { // given createExampleExamType("Project"); ExamDto requestData = ExamDto.builder() - .title("a".repeat(256)) // 256 znaków + .title("a".repeat(256)) .description("first exam") .date(LocalDateTime.now().plusDays(1)) .examType("Project") @@ -361,7 +379,7 @@ void addExamWithTooLongDescription() throws Exception { createExampleExamType("Project"); ExamDto requestData = ExamDto.builder() .title("Math exam") - .description("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") // 256 znaków + .description("a".repeat(256)) .date(LocalDateTime.now().plusDays(1)) .examType("Project") .generalGroups(Set.of("12K2")) diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java index fd4edf7..e175811 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java @@ -13,6 +13,7 @@ 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; @@ -217,6 +218,33 @@ void addExamForEmptyGeneralGroup() { assertEquals("Invalid group identifier: general group is missing", exception.getMessage()); } + @Test + void addExamThatAlreadyExists() throws JsonProcessingException { + // given + LocalDateTime date = LocalDateTime.now().plusDays(1); + ExamType examType = buildExampleExamType(); + ExamDto examDto = buildExampleExamDto(Set.of("12K2"), Set.of("L04"), date.plusSeconds(34)); + Set studentGroups = new HashSet<>(buildExampleStudentGroupList(Set.of("12K2", "L04"))); + Exam exam = ExamDtoMapper.mapToNewExam(examDto, studentGroups, examType); + + when(timetableService.getGeneralGroupList()).thenReturn(new ArrayList<>(List.of("12K1", "12K2", "12K3"))); + when(timetableService.getAvailableSubGroups("12K2")).thenReturn(new ArrayList<>(List.of("L04"))); + when(groupRepository.findAllByNameIn(any(Set.class))).thenReturn(studentGroups); +// + + when(examTypeRepository.findByName(examDto.getExamType())).thenReturn(Optional.of(examType)); + when(examRepository.findAllByTitle(examDto.getTitle())).thenReturn(Set.of(exam)); +// when + RuntimeException exception = assertThrows( + ResourceAlreadyExistsException.class, + () -> examService.addExam(examDto) + ); +// then + verify(examRepository, times(0)).save(exam); + assertEquals("Exam already exists", exception.getMessage()); + + } + // // @@ -355,6 +383,46 @@ void addExamForSingleGeneralGroupWithRepositoryContainingGroup() { assertExam(savedExam, date, savedId, generalGroups); } + @Test + void addExamWithNonUniqueTitle() { + // 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 newExam = buildExamWithIdAndGroups(1, studentGroups); + Exam existingExam = Exam.builder() + .title("title") + .description("description") + .examDate(date.plusHours(4)) + .examType(examType) + .groups(new HashSet<>(studentGroups)) + .build(); + + 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.findAllByTitle(any())).thenReturn(Set.of(existingExam)); + when(examRepository.save(any(Exam.class))).thenReturn(newExam); +// 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 * generalGroup - 1 item @@ -551,18 +619,7 @@ void addExamWhenServiceIsUnavailableAndRepositoryContainsGroups() throws JsonPro // - /************************************************************************************/ //modify exam - @Test - void shouldModifyExamWhenIdExists() { - - } - - @Test - void shouldThrowWhenExamIdNotExists() { - // given - - } /************************************************************************************/ //delete exam From cdcefd083bc5202a279ccc72f380feb39831ef18 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Mon, 8 Sep 2025 19:05:38 +0200 Subject: [PATCH 2/7] refactor addExam method --- .../org/pkwmtt/examCalendar/ExamService.java | 101 +++++++++--------- .../InvalidGroupIdentifierException.java | 9 ++ 2 files changed, 57 insertions(+), 53 deletions(-) diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamService.java b/src/main/java/org/pkwmtt/examCalendar/ExamService.java index 8935ecf..a852053 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamService.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamService.java @@ -39,10 +39,9 @@ public int addExam(ExamDto examDto) { ExamType examType = examTypeRepository.findByName(examDto.getExamType()) .orElseThrow(() -> new ExamTypeNotExistsException(examDto.getExamType())); -// save exam in repository and return id of created exam Exam exam = ExamDtoMapper.mapToNewExam(examDto, groups, examType); Set existingExam = examRepository.findAllByTitle(exam.getTitle()); - if(existingExam.contains(exam)) + if (existingExam.contains(exam)) throw new ResourceAlreadyExistsException("Exam already exists"); return examRepository.save(exam).getExamId(); } @@ -121,75 +120,71 @@ public List getExamTypes() { * @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 + + if (generalGroups == null || generalGroups.isEmpty()) + throw new InvalidGroupIdentifierException("general group is missing"); + + verifyGeneralGroups(generalGroups); + + if (subgroups == null || subgroups.isEmpty()) + return saveNewStudentGroups(generalGroups); + + if (generalGroups.size() > 1) + throw new InvalidGroupIdentifierException("ambiguous general groups for subgroups"); + + String superiorGroup = generalGroups.iterator().next(); + verifySubgroups(superiorGroup, subgroups); + + subgroups.add(trimLastDigit(superiorGroup)); + return saveNewStudentGroups(subgroups); + } + + private void verifyGeneralGroups(Set generalGroups) { try { - generalGroupsFromRepository = new HashSet<>(timetableService.getGeneralGroupList()); + Set existingGeneralGroups = new HashSet<>(timetableService.getGeneralGroupList()); + if (!existingGeneralGroups.containsAll(generalGroups)) + throw new InvalidGroupIdentifierException(existingGeneralGroups, generalGroups); } catch (WebPageContentNotAvailableException e) { - generalGroupsFromRepository = verifyGroupsUsingRepository(generalGroups); - } -// verify generalGroups using timetable service - if (!generalGroupsFromRepository.containsAll(generalGroups)) { - generalGroups.removeAll(generalGroupsFromRepository); - throw new InvalidGroupIdentifierException(generalGroups); + verifyGeneralGroupsUsingRepository(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 { + private void verifyGeneralGroupsUsingRepository(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 + if (!groupsFromRepository.containsAll(groups)) throw new ServiceNotAvailableException("Couldn't verify groups using repository"); } + private void verifySubgroups(String superiorGroup, Set subgroups) { + try { + Set subGroupsFromTimetable = new HashSet<>(timetableService.getAvailableSubGroups(superiorGroup)); + if (!subGroupsFromTimetable.containsAll(subgroups)) + throw new InvalidGroupIdentifierException(subGroupsFromTimetable, subgroups); + } 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 + } + } + + private static String trimLastDigit(String superiorGroup) { + char lastChar = superiorGroup.charAt(superiorGroup.length() - 1); + if (Character.isDigit(lastChar)) + superiorGroup = superiorGroup.substring(0, superiorGroup.length() - 1); + return superiorGroup; + } + /** * saves groups to groupRepository, existing group names are filtered out before saving * @@ -220,7 +215,7 @@ private static void verifyGeneralGroupsFormat(Set generalGroups) throws }); } - private static void verifySubgroupsFormat(Set subgroups) { + private static void verifySubgroupsFormat(Set subgroups) throws SpecifiedSubGroupDoesntExistsException { subgroups.forEach(group -> { if (!group.matches("^[A-Z].*")) throw new SpecifiedSubGroupDoesntExistsException(group); diff --git a/src/main/java/org/pkwmtt/exceptions/InvalidGroupIdentifierException.java b/src/main/java/org/pkwmtt/exceptions/InvalidGroupIdentifierException.java index a47bf28..a5746fc 100644 --- a/src/main/java/org/pkwmtt/exceptions/InvalidGroupIdentifierException.java +++ b/src/main/java/org/pkwmtt/exceptions/InvalidGroupIdentifierException.java @@ -10,4 +10,13 @@ public InvalidGroupIdentifierException(String groupIdentifier) { public InvalidGroupIdentifierException(Set groupIdentifiers) { super("Invalid group identifiers: " + groupIdentifiers.toString()); } + + public InvalidGroupIdentifierException(Set all, Set provided) { + super("Invalid group identifiers: " + extractInvalidGroups(all, provided)); + } + + private static String extractInvalidGroups(Set all, Set provided) { + provided.removeAll(all); + return provided.toString(); + } } From 177805954efa58102541071b3dd4296c17c2134d Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Mon, 8 Sep 2025 21:10:07 +0200 Subject: [PATCH 3/7] refactor getExamById method --- .../org/pkwmtt/examCalendar/ExamService.java | 40 ++++++++--------- .../repository/ExamRepository.java | 11 +++-- .../examCalendar/ExamControllerTest.java | 4 +- .../pkwmtt/examCalendar/ExamServiceTest.java | 22 +++++----- .../repository/ExamRepositoryTest.java | 43 +++++++++++++++---- 5 files changed, 72 insertions(+), 48 deletions(-) diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamService.java b/src/main/java/org/pkwmtt/examCalendar/ExamService.java index a852053..4ed0adf 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamService.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamService.java @@ -41,6 +41,7 @@ public int addExam(ExamDto examDto) { Exam exam = ExamDtoMapper.mapToNewExam(examDto, groups, examType); Set existingExam = examRepository.findAllByTitle(exam.getTitle()); + if (existingExam.contains(exam)) throw new ResourceAlreadyExistsException("Exam already exists"); return examRepository.save(exam).getExamId(); @@ -79,31 +80,15 @@ public Exam getExamById(int id) { } public Set getExamByGroups(Set generalGroups, Set subgroups) { -// verify generalGroups identifiers + + String superiorGroup = trimLastDigit(generalGroups); 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; + if(subgroups == null || subgroups.isEmpty()) + return examRepository.findAllByGroups_NameIn(generalGroups); + + verifySubgroupsFormat(subgroups); + return examRepository.findAllBySubgroupsOfSuperiorGroupAndGeneralGroup(superiorGroup, generalGroups, subgroups); } /** @@ -185,6 +170,15 @@ private static String trimLastDigit(String superiorGroup) { return superiorGroup; } + private static String trimLastDigit(Set superiorGroups) { + Set trimmedGroups = superiorGroups.stream() + .map(ExamService::trimLastDigit) + .collect(Collectors.toSet()); + if(trimmedGroups.size() > 1) + throw new InvalidGroupIdentifierException("ambiguous general groups for subgroups"); + return trimmedGroups.iterator().next(); + } + /** * saves groups to groupRepository, existing group names are filtered out before saving * diff --git a/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java index 00ad156..d1cd905 100644 --- a/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java +++ b/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java @@ -5,7 +5,6 @@ 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 { @@ -16,7 +15,7 @@ public interface ExamRepository extends JpaRepository { * @param groups set of generalGroups * @return list of exams for generalGroups */ - List findAllByGroups_NameIn(Set groups); + Set findAllByGroups_NameIn(Set groups); /** * @param generalGroup superior group of subgroups e.g. 12K @@ -27,7 +26,11 @@ public interface ExamRepository extends JpaRepository { SELECT DISTINCT e FROM Exam e JOIN e.groups g1 JOIN FETCH e.groups g2 - WHERE g1.name = :general AND g2.name IN :sub + WHERE (g1.name = :superior AND g2.name IN :sub) + OR g2.name IN :general """) - Set findAllBySubgroupsOfGeneralGroup(@Param("general") String generalGroup, @Param("sub") Set subgroup); + Set findAllBySubgroupsOfSuperiorGroupAndGeneralGroup( + @Param("superior") String superiorGroup, + @Param("general") Set 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 40565e3..e6bcf08 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java @@ -622,7 +622,7 @@ 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); + assertResponseMessage("Invalid group identifier: ambiguous general groups for subgroups",result); } @Test @@ -636,7 +636,7 @@ void getExamsWithSwappedGroupNames() throws Exception { @Test void getExamsWithInvalidSubgroup() throws Exception { // when - MvcResult result = assertGetByGroupsRequest(status().isBadRequest(), Set.of("12K1,", "12K2"), Set.of("11K2")); + MvcResult result = assertGetByGroupsRequest(status().isBadRequest(), Set.of("12K1", "12K2"), Set.of("11K2")); // then assertResponseMessage("Specified sub group [11K2] doesn't exists",result); } diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java index e175811..7657c49 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java @@ -687,20 +687,20 @@ void getExamsForNormalGroups() { // when examService.getExamByGroups(generalGroups, subgroups); // then - verify(examRepository, times(1)).findAllByGroups_NameIn(generalGroups); - verify(examRepository, times(1)).findAllBySubgroupsOfGeneralGroup("12K", subgroups); + verify(examRepository, never()).findAllByGroups_NameIn(any()); + verify(examRepository, times(1)).findAllBySubgroupsOfSuperiorGroupAndGeneralGroup("12K", Set.of("12K2"), subgroups); } @Test - void getExamsForGroupWithoutDigitAsFirstCharacter() { + void getExamsForGroupWithoutDigitAsLastCharacter() { // 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); + verify(examRepository, never()).findAllByGroups_NameIn(any()); + verify(examRepository, times(1)).findAllBySubgroupsOfSuperiorGroupAndGeneralGroup("1Er", generalGroups, subgroups); } @Test @@ -712,7 +712,7 @@ void getExamsWithEmptySubgroups() { examService.getExamByGroups(generalGroups, subgroups); // then verify(examRepository, times(1)).findAllByGroups_NameIn(generalGroups); - verify(examRepository, never()).findAllBySubgroupsOfGeneralGroup(any(), any()); + verify(examRepository, never()).findAllBySubgroupsOfSuperiorGroupAndGeneralGroup(any(), any(), any()); } @Test @@ -724,7 +724,7 @@ void getExamsWithBlankSubgroups() { examService.getExamByGroups(generalGroups, subgroups); // then verify(examRepository, times(1)).findAllByGroups_NameIn(generalGroups); - verify(examRepository, never()).findAllBySubgroupsOfGeneralGroup(any(), any()); + verify(examRepository, never()).findAllBySubgroupsOfSuperiorGroupAndGeneralGroup(any(), any(), any()); } @Test @@ -735,8 +735,8 @@ void shouldNotThrowWhenGroupsAreFromTheSameYearOfStudy() { // when examService.getExamByGroups(generalGroups, subgroups); // then - verify(examRepository, times(1)).findAllByGroups_NameIn(generalGroups); - verify(examRepository, times(1)).findAllBySubgroupsOfGeneralGroup("12K", subgroups); + verify(examRepository, never()).findAllByGroups_NameIn(any()); + verify(examRepository, times(1)).findAllBySubgroupsOfSuperiorGroupAndGeneralGroup("12K", generalGroups, subgroups); } @Test @@ -746,7 +746,7 @@ void shouldThrowWhenSubgroupsAreSwappedWithGeneralGroups() { Set subgroups = new HashSet<>( Set.of("12K1")); // when then assertThrows( - SpecifiedGeneralGroupDoesntExistsException.class, + InvalidGroupIdentifierException.class, () -> examService.getExamByGroups(generalGroups, subgroups) ); } @@ -771,7 +771,7 @@ void shouldThrowWhenGeneralGroupsAreFromDifferentYearOfStudy() { // when RuntimeException exception = assertThrows(InvalidGroupIdentifierException.class, () -> examService.getExamByGroups(generalGroups, subgroups)); // then - assertEquals("Invalid group identifier: ambiguous superior group identifier for subgroups", exception.getMessage()); + assertEquals("Invalid group identifier: ambiguous general groups for subgroups", 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 52490f4..eb0b198 100644 --- a/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java @@ -11,7 +11,6 @@ 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; @@ -104,7 +103,7 @@ void setUp() { .description("Linear Algebra") .examDate(LocalDateTime.now().plusDays(1)) .examType(examType) - .groups(Set.of(g12K1, g12K2, g12K3)) + .groups(Set.of(g12K1, g12K2)) .build(); Exam generalGroupExam2 = Exam.builder() @@ -133,7 +132,7 @@ void setUp() { @Test void shouldReturnExamsWhenNotAllSubgroupsFromRepositoryMatched() { - Set exams = examRepository.findAllBySubgroupsOfGeneralGroup("12K", Set.of("L04")); + Set exams = examRepository.findAllBySubgroupsOfSuperiorGroupAndGeneralGroup("12K", Set.of("12K3"), Set.of("L04")); assertEquals(2, exams.size()); List examTitles = exams.stream().map(Exam::getTitle).sorted().toList(); assertEquals("small Group Exam 1", examTitles.get(0)); @@ -142,7 +141,7 @@ void shouldReturnExamsWhenNotAllSubgroupsFromRepositoryMatched() { @Test void shouldReturnExamWhenNotAllSubgroupsFromArgumentsMatchedAndNotReturnExamsForWrongGeneralGroup() { - Set exams = examRepository.findAllBySubgroupsOfGeneralGroup("12K", Set.of("L05")); + Set exams = examRepository.findAllBySubgroupsOfSuperiorGroupAndGeneralGroup("12K", Set.of("12K3"), Set.of("L05")); assertEquals(1, exams.size()); List examTitles = exams.stream().map(Exam::getTitle).sorted().toList(); assertEquals("small Group Exam 2", examTitles.getFirst()); @@ -150,28 +149,56 @@ void shouldReturnExamWhenNotAllSubgroupsFromArgumentsMatchedAndNotReturnExamsFor @Test void shouldReturnExamsWhenMultipleArgumentsMatch() { - Set exams = examRepository.findAllBySubgroupsOfGeneralGroup("12K", Set.of("L04", "L05")); + Set exams = examRepository.findAllBySubgroupsOfSuperiorGroupAndGeneralGroup("12K", Set.of("12K3"), Set.of("L04", "L05")); assertEquals(2, exams.size()); 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 shouldReturnOnlyExamsForGeneralGroups() { + Set exams = examRepository.findAllBySubgroupsOfSuperiorGroupAndGeneralGroup("12K", Set.of("12K1"), Set.of("L01", "L08")); + assertEquals(2, exams.size()); + Set examTitles = exams.stream().map(Exam::getTitle).collect(Collectors.toSet()); + assertTrue(examTitles.contains("general Group Exam 1")); + assertTrue(examTitles.contains("general Group Exam 2")); + } + + @Test + void shouldReturnGeneralGroupExamsWhenSubgroupsIsEmpty() { + Set exams = examRepository.findAllBySubgroupsOfSuperiorGroupAndGeneralGroup("12K", Set.of("12K1"), Set.of()); + assertEquals(2, exams.size()); + Set examTitles = exams.stream().map(Exam::getTitle).collect(Collectors.toSet()); + assertTrue(examTitles.contains("general Group Exam 1")); + assertTrue(examTitles.contains("general Group Exam 2")); + } + + @Test + void shouldReturnExamsForGeneralAndSubgroups() { + Set exams = examRepository.findAllBySubgroupsOfSuperiorGroupAndGeneralGroup("12K", Set.of("12K2"), Set.of("L04", "L05")); + assertEquals(3, exams.size()); + Set examTitles = exams.stream().map(Exam::getTitle).collect(Collectors.toSet()); + assertTrue(examTitles.contains("small Group Exam 1")); + assertTrue(examTitles.contains("small Group Exam 2")); + assertTrue(examTitles.contains("general Group Exam 1")); + } + @Test void ShouldReturnEmptyListWhenSubgroupsSetIsEmpty() { - Set exams = examRepository.findAllBySubgroupsOfGeneralGroup("12K", Set.of()); + Set exams = examRepository.findAllBySubgroupsOfSuperiorGroupAndGeneralGroup("12K", Set.of(), Set.of()); assertTrue(exams.isEmpty()); } @Test void shouldReturnEmptyListWhenGeneralGroupIdentifierHasInvalidFormat() { - Set exams = examRepository.findAllBySubgroupsOfGeneralGroup("12K2", Set.of("L04")); + Set exams = examRepository.findAllBySubgroupsOfSuperiorGroupAndGeneralGroup("12K2", Set.of(), Set.of("L04")); assertTrue(exams.isEmpty()); } @Test void shouldReturnEmptyListWhenGeneralGroupIdentifierDontMatch() { - Set exams = examRepository.findAllBySubgroupsOfGeneralGroup("12B", Set.of("L04", "L05")); + Set exams = examRepository.findAllBySubgroupsOfSuperiorGroupAndGeneralGroup("12B", Set.of("12B1"), Set.of("L04", "L05")); assertTrue(exams.isEmpty()); } } \ No newline at end of file From 436a20707a5de4171106039c976de4cc02b6c0cd Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Wed, 10 Sep 2025 15:13:43 +0200 Subject: [PATCH 4/7] add verify subgroups by repository --- .../org/pkwmtt/examCalendar/ExamService.java | 20 ++++-- .../repository/ExamRepository.java | 9 +++ .../examCalendar/ExamControllerTest.java | 1 - .../pkwmtt/examCalendar/ExamServiceTest.java | 15 +++-- .../pkwmtt/examCalendar/dto/ExamDtoTest.java | 4 -- .../repository/ExamRepositoryTest.java | 65 +++++++++++++++++-- 6 files changed, 92 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamService.java b/src/main/java/org/pkwmtt/examCalendar/ExamService.java index 4ed0adf..50c2e3f 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamService.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamService.java @@ -140,14 +140,14 @@ private void verifyGeneralGroups(Set generalGroups) { * @param groups groups that would be verified using repository * @throws WebPageContentNotAvailableException when verification not succeeded */ - private void verifyGeneralGroupsUsingRepository(Set groups) throws WebPageContentNotAvailableException { + private void verifyGeneralGroupsUsingRepository(Set groups){ verifyGeneralGroupsFormat(groups); Set groupsFromRepository = groupRepository.findAllByNameIn(groups).stream() .map(StudentGroup::getName) .collect(Collectors.toSet() ); if (!groupsFromRepository.containsAll(groups)) - throw new ServiceNotAvailableException("Couldn't verify groups using repository"); + throw new ServiceNotAvailableException("Timetable service unavailable, couldn't verify groups using repository"); } private void verifySubgroups(String superiorGroup, Set subgroups) { @@ -158,11 +158,23 @@ private void verifySubgroups(String superiorGroup, Set subgroups) { } 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 + verifySubgroupsUsingRepository(superiorGroup, subgroups); } } + private void verifySubgroupsUsingRepository(String superiorGroup, Set groups){ + verifySubgroupsFormat(groups); + groups.add(trimLastDigit(superiorGroup)); + if(examRepository.findCommonExamIdsForGroups(groups, groups.size()).isEmpty()) + throw new ServiceNotAvailableException("Timetable service unavailable, couldn't verify groups using repository"); + Set groupsFromRepository = groupRepository.findAllByNameIn(groups).stream() + .map(StudentGroup::getName) + .collect(Collectors.toSet() + ); + if (!groupsFromRepository.containsAll(groups)) + throw new ServiceNotAvailableException("Timetable service unavailable, couldn't verify groups using repository"); + } + private static String trimLastDigit(String superiorGroup) { char lastChar = superiorGroup.charAt(superiorGroup.length() - 1); if (Character.isDigit(lastChar)) diff --git a/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java index d1cd905..45ff360 100644 --- a/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java +++ b/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java @@ -33,4 +33,13 @@ Set findAllBySubgroupsOfSuperiorGroupAndGeneralGroup( @Param("superior") String superiorGroup, @Param("general") Set generalGroup, @Param("sub") Set subgroup); + + @Query(value = """ + SELECT exam_id FROM exams_groups + INNER JOIN student_groups ON exams_groups.group_id = student_groups.group_id + WHERE student_groups.name IN (:groups) + GROUP BY exam_id + HAVING COUNT(DISTINCT exams_groups.group_id) = :expectedSize + """, nativeQuery = true) + Set findCommonExamIdsForGroups(@Param("groups") Set groups, @Param("expectedSize") int expectedSize); } \ 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 e6bcf08..63bd489 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamControllerTest.java @@ -19,7 +19,6 @@ 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.annotation.DirtiesContext; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java index 7657c49..65789de 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java @@ -2,7 +2,6 @@ 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; @@ -493,7 +492,7 @@ void unavailableServiceAndRepositoryDontMatch() { // when RuntimeException exception = assertThrows(ServiceNotAvailableException.class, () -> examService.addExam(examDto)); // then - assertEquals("Couldn't verify groups using repository", exception.getMessage()); + assertEquals("Timetable service unavailable, couldn't verify groups using repository", exception.getMessage()); verify(timetableService, times(1)).getGeneralGroupList(); verify(groupRepository, times(1)).findAllByNameIn(generalGroups); } @@ -524,7 +523,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("Timetable service unavailable, couldn't verify groups using repository", exception.getMessage()); verify(timetableService, times(1)).getGeneralGroupList(); verify(groupRepository, times(1)).findAllByNameIn(generalGroups); } @@ -582,12 +581,11 @@ void addExamWhenServiceIsUnavailableAndRepositoryContainsGeneralGroups() { * 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"); + Set combinedGroups = Set.of("12K", "12K2", "L04", "K04", "P04", "K05"); LocalDateTime date = LocalDateTime.now().plusDays(1); ExamDto examDto = buildExampleExamDto(generalGroups, subgroups, date); @@ -601,20 +599,23 @@ void addExamWhenServiceIsUnavailableAndRepositoryContainsGroups() throws JsonPro //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); + //noinspection unchecked + when(examRepository.findCommonExamIdsForGroups(any(Set.class), any(Integer.class))).thenReturn(Set.of(1)); // 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(3)).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); + assertExam(savedExam, date, savedId, combinedGroups); } // diff --git a/src/test/java/org/pkwmtt/examCalendar/dto/ExamDtoTest.java b/src/test/java/org/pkwmtt/examCalendar/dto/ExamDtoTest.java index a98fd0e..f278e3b 100644 --- a/src/test/java/org/pkwmtt/examCalendar/dto/ExamDtoTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/dto/ExamDtoTest.java @@ -3,13 +3,9 @@ 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; diff --git a/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java b/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java index eb0b198..e2dd528 100644 --- a/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/repository/ExamRepositoryTest.java @@ -37,6 +37,13 @@ class ExamRepositoryTest { @Autowired private GroupRepository groupRepository; + private Integer ex1Id; + private Integer ex2Id; + private Integer ex3Id; + private Integer ex4Id; + private Integer ex5Id; + private Integer ex6Id; + @BeforeAll void setUp() { ExamType examType = ExamType.builder() @@ -122,12 +129,12 @@ void setUp() { .groups(Set.of(g12A1, g12A2)) .build(); - examRepository.save(smallGroupExam1); - examRepository.save(smallGroupExam2); - examRepository.save(smallGroupExam3); - examRepository.save(generalGroupExam1); - examRepository.save(generalGroupExam2); - examRepository.save(generalGroupExam3); + ex1Id = examRepository.save(smallGroupExam1).getExamId(); + ex2Id = examRepository.save(smallGroupExam2).getExamId(); + ex3Id = examRepository.save(smallGroupExam3).getExamId(); + ex4Id = examRepository.save(generalGroupExam1).getExamId(); + ex5Id = examRepository.save(generalGroupExam2).getExamId(); + ex6Id = examRepository.save(generalGroupExam3).getExamId(); } @Test @@ -201,4 +208,50 @@ void shouldReturnEmptyListWhenGeneralGroupIdentifierDontMatch() { Set exams = examRepository.findAllBySubgroupsOfSuperiorGroupAndGeneralGroup("12B", Set.of("12B1"), Set.of("L04", "L05")); assertTrue(exams.isEmpty()); } + +// findCommonExamIdsForGroups + + @Test + void shouldReturnWhenThereAreMoreGroupsInRepositoryThanArguments() { +// when + Set ids = examRepository.findCommonExamIdsForGroups(Set.of("12K", "L04"), 2); +// then + assertEquals(2, ids.size()); + assertTrue(ids.contains(ex1Id)); + assertTrue(ids.contains(ex2Id)); + } + + @Test + void shouldReturnOnlyWhenAllGroupsMatch() { +// when + Set ids = examRepository.findCommonExamIdsForGroups(Set.of("12K", "L04", "L05"), 3); +// then + assertEquals(1, ids.size()); + assertTrue(ids.contains(ex2Id)); + } + + @Test + void shouldReturnEmptySetWhenArgumentListIsEmpty() { +// when + Set ids = examRepository.findCommonExamIdsForGroups(Set.of(), 0); +// then + assertTrue(ids.isEmpty()); + } + + @Test + void shouldReturnEmptySetWhenThereAreMoreArgumentsThanGroupsInRepository() { +// when + Set ids = examRepository.findCommonExamIdsForGroups(Set.of("12K", "L04", "L05","L06"), 4); +// then + assertTrue(ids.isEmpty()); + } + + @Test + void shouldReturnEmptySetWhenSubgroupNotMatchSuperiorGroup() { +// when + Set ids = examRepository.findCommonExamIdsForGroups(Set.of("L04","G12A"), 2); +// then + assertTrue(ids.isEmpty()); + } + } \ No newline at end of file From 4012aca50665ee63a47d6bfeff1adcdf663bfd71 Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Wed, 10 Sep 2025 16:04:42 +0200 Subject: [PATCH 5/7] javadoc --- .../org/pkwmtt/examCalendar/ExamService.java | 74 +++++++++++++++---- .../examCalendar/mapper/ExamDtoMapper.java | 10 ++- .../repository/ExamRepository.java | 19 ++++- 3 files changed, 82 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamService.java b/src/main/java/org/pkwmtt/examCalendar/ExamService.java index 50c2e3f..7178dec 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamService.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamService.java @@ -79,6 +79,13 @@ public Exam getExamById(int id) { return examRepository.findById(id).orElseThrow(() -> new NoSuchElementWithProvidedIdException(id)); } + /** + * @param generalGroups set of general groups from the same year of study + * e.g. 12K1, 12K2 are from 12K year of study, + * but 12A1 and 12B1 or 11A1 and 12A1 aren't from the same year + * @param subgroups subgroups that belong to provided general groups + * @return set of exams containing provided groups + */ public Set getExamByGroups(Set generalGroups, Set subgroups) { String superiorGroup = trimLastDigit(generalGroups); @@ -98,11 +105,13 @@ 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 - * + * verify if groups exists and updates database when it exists, but repository doesn't contain it. + * When timetable service is unavailable verifies groups using groupsRepository * @param examDto containing groups for verification + * @return single set of all kinds of provided groups as StudentGroup entities + * that are in database and could be safely attach to Exam entity */ private Set verifyAndUpdateExamGroups(ExamDto examDto) { Set generalGroups = examDto.getGeneralGroups(); @@ -126,6 +135,10 @@ private Set verifyAndUpdateExamGroups(ExamDto examDto) { return saveNewStudentGroups(subgroups); } + /** + * verifies provided generalGroups using timetable service or repository when service is unavailable + * @param generalGroups that would be verified + */ private void verifyGeneralGroups(Set generalGroups) { try { Set existingGeneralGroups = new HashSet<>(timetableService.getGeneralGroupList()); @@ -137,10 +150,10 @@ private void verifyGeneralGroups(Set generalGroups) { } /** - * @param groups groups that would be verified using repository - * @throws WebPageContentNotAvailableException when verification not succeeded + * @param groups that would be verified using repository + * @throws ServiceNotAvailableException when verification not succeeded */ - private void verifyGeneralGroupsUsingRepository(Set groups){ + private void verifyGeneralGroupsUsingRepository(Set groups) throws ServiceNotAvailableException { verifyGeneralGroupsFormat(groups); Set groupsFromRepository = groupRepository.findAllByNameIn(groups).stream() .map(StudentGroup::getName) @@ -150,7 +163,12 @@ private void verifyGeneralGroupsUsingRepository(Set groups){ throw new ServiceNotAvailableException("Timetable service unavailable, couldn't verify groups using repository"); } - private void verifySubgroups(String superiorGroup, Set subgroups) { + /** + * verifies provided subgroups using timetable service or repository when service is unavailable + * @param superiorGroup of provided subgroups + * @param subgroups that would be verified + */ + private void verifySubgroups(String superiorGroup, Set subgroups){ try { Set subGroupsFromTimetable = new HashSet<>(timetableService.getAvailableSubGroups(superiorGroup)); if (!subGroupsFromTimetable.containsAll(subgroups)) @@ -162,7 +180,13 @@ private void verifySubgroups(String superiorGroup, Set subgroups) { } } - private void verifySubgroupsUsingRepository(String superiorGroup, Set groups){ +// TODO: + /** + * @param superiorGroup of provided subgroups + * @param groups subgroups for verification + * @throws ServiceNotAvailableException when verification not succeeded + */ + private void verifySubgroupsUsingRepository(String superiorGroup, Set groups) throws ServiceNotAvailableException { verifySubgroupsFormat(groups); groups.add(trimLastDigit(superiorGroup)); if(examRepository.findCommonExamIdsForGroups(groups, groups.size()).isEmpty()) @@ -175,14 +199,25 @@ private void verifySubgroupsUsingRepository(String superiorGroup, Set gr throw new ServiceNotAvailableException("Timetable service unavailable, couldn't verify groups using repository"); } - private static String trimLastDigit(String superiorGroup) { - char lastChar = superiorGroup.charAt(superiorGroup.length() - 1); + /** + * extract superior group form general group e.g. 12K2 -> 12K + * @param generalGroup group for transformation + * @return superior group + */ + private static String trimLastDigit(String generalGroup) { + char lastChar = generalGroup.charAt(generalGroup.length() - 1); if (Character.isDigit(lastChar)) - superiorGroup = superiorGroup.substring(0, superiorGroup.length() - 1); - return superiorGroup; + generalGroup = generalGroup.substring(0, generalGroup.length() - 1); + return generalGroup; } - private static String trimLastDigit(Set superiorGroups) { + /** + * extract common superior group form provided general groups e.g. 12K2 -> 12K + * @param superiorGroups set of general groups from the same year of study + * @return single superior group of provided general groups + * @throws InvalidGroupIdentifierException when not all provided groups belong to the same year of study + */ + private static String trimLastDigit(Set superiorGroups) throws InvalidGroupIdentifierException { Set trimmedGroups = superiorGroups.stream() .map(ExamService::trimLastDigit) .collect(Collectors.toSet()); @@ -193,9 +228,8 @@ private static String trimLastDigit(Set superiorGroups) { /** * 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. + * @return set of StudentsGroup Entities with provided names */ private Set saveNewStudentGroups(Set groups) { // remove duplicates before saving records @@ -214,6 +248,10 @@ private Set saveNewStudentGroups(Set groups) { return existingGroups; } + /** + * @param generalGroups general groups for verification + * @throws SpecifiedGeneralGroupDoesntExistsException when format is invalid + */ private static void verifyGeneralGroupsFormat(Set generalGroups) throws SpecifiedGeneralGroupDoesntExistsException { generalGroups.forEach(group -> { if (!group.matches("^\\d.*")) @@ -221,10 +259,14 @@ private static void verifyGeneralGroupsFormat(Set generalGroups) throws }); } + /** + * @param subgroups subgroups for verification + * @throws SpecifiedSubGroupDoesntExistsException when format is invalid + */ private static void verifySubgroupsFormat(Set subgroups) throws SpecifiedSubGroupDoesntExistsException { subgroups.forEach(group -> { if (!group.matches("^[A-Z].*")) throw new SpecifiedSubGroupDoesntExistsException(group); }); } -} +} \ No newline at end of file diff --git a/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoMapper.java b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoMapper.java index cf3c3aa..aae4659 100644 --- a/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoMapper.java +++ b/src/main/java/org/pkwmtt/examCalendar/mapper/ExamDtoMapper.java @@ -11,7 +11,7 @@ import java.util.stream.Collectors; /** - * maps ExamDto to Exam entity. Couldn't be utility class, because needs ExamTypeRepository to validate exam types + * Utility class for mapping Exam entity to ExamDto and ExamDto to Exam entity */ @Slf4j public class ExamDtoMapper { @@ -51,10 +51,18 @@ public static Exam mapToExistingExam(ExamDto examDto, Set groups, .build(); } + /** + * @param exams Set of Exams that would be mapped + * @return List of ExamDtos + */ public static List mapToExamDto(Set exams) { return exams.stream().map(ExamDtoMapper::mapToExamDto).collect(Collectors.toList()); } + /** + * @param exam single exam that would be mapped + * @return ExamDto + */ 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()); diff --git a/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java b/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java index 45ff360..eec6202 100644 --- a/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java +++ b/src/main/java/org/pkwmtt/examCalendar/repository/ExamRepository.java @@ -11,16 +11,19 @@ public interface ExamRepository extends JpaRepository { Set findAllByTitle(String title); + /** * @param groups set of generalGroups - * @return list of exams for generalGroups + * @return set of exams for generalGroups */ Set findAllByGroups_NameIn(Set groups); + /** - * @param generalGroup superior group of subgroups e.g. 12K - * @param subgroup exam groups - * @return list of exams for subgroups + * @param superiorGroup group that identifies whole year of study e.g. 12K + * @param generalGroup set of exercise groups e.g. 12K2 + * @param subgroup set of subgroups of provided superior group e.g. L04 + * @return set of exams containing generalGroups or superiorGroup with at least one provided subgroup */ @Query(""" SELECT DISTINCT e FROM Exam e @@ -34,6 +37,14 @@ Set findAllBySubgroupsOfSuperiorGroupAndGeneralGroup( @Param("general") Set generalGroup, @Param("sub") Set subgroup); + + /** + * Method could be used to check if provided subgroups belong to superior group by finding existing exam + * related to provided groups in repository + * @param groups set of subgroups with one superior group of provided subgroups e.g. 12K2, K04, K05 + * @param expectedSize size of provided groups set + * @return set of ids of exams that contains all provided groups + */ @Query(value = """ SELECT exam_id FROM exams_groups INNER JOIN student_groups ON exams_groups.group_id = student_groups.group_id From 45325b1f8e9c2546f26ec2e6afbc0dde6a37420f Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Wed, 10 Sep 2025 16:16:55 +0200 Subject: [PATCH 6/7] remove redundant code from verifySubgroupsUsingRepository --- src/main/java/org/pkwmtt/examCalendar/ExamService.java | 8 -------- .../java/org/pkwmtt/examCalendar/ExamServiceTest.java | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/main/java/org/pkwmtt/examCalendar/ExamService.java b/src/main/java/org/pkwmtt/examCalendar/ExamService.java index 7178dec..3bd6bb7 100644 --- a/src/main/java/org/pkwmtt/examCalendar/ExamService.java +++ b/src/main/java/org/pkwmtt/examCalendar/ExamService.java @@ -180,23 +180,15 @@ private void verifySubgroups(String superiorGroup, Set subgroups){ } } -// TODO: /** * @param superiorGroup of provided subgroups * @param groups subgroups for verification * @throws ServiceNotAvailableException when verification not succeeded */ private void verifySubgroupsUsingRepository(String superiorGroup, Set groups) throws ServiceNotAvailableException { - verifySubgroupsFormat(groups); groups.add(trimLastDigit(superiorGroup)); if(examRepository.findCommonExamIdsForGroups(groups, groups.size()).isEmpty()) throw new ServiceNotAvailableException("Timetable service unavailable, couldn't verify groups using repository"); - Set groupsFromRepository = groupRepository.findAllByNameIn(groups).stream() - .map(StudentGroup::getName) - .collect(Collectors.toSet() - ); - if (!groupsFromRepository.containsAll(groups)) - throw new ServiceNotAvailableException("Timetable service unavailable, couldn't verify groups using repository"); } /** diff --git a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java index 65789de..f2c2fff 100644 --- a/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java +++ b/src/test/java/org/pkwmtt/examCalendar/ExamServiceTest.java @@ -609,7 +609,7 @@ void addExamWhenServiceIsUnavailableAndRepositoryContainsGroups() throws JsonPro // then verify(examTypeRepository, times(1)).findByName(examDto.getExamType()); verify(timetableService, times(1)).getGeneralGroupList(); - verify(groupRepository, times(3)).findAllByNameIn(any()); + verify(groupRepository, times(2)).findAllByNameIn(any()); verify(groupRepository, times(1)).saveAll(any()); ArgumentCaptor examCaptor = ArgumentCaptor.forClass(Exam.class); From bc55fb8685679841b65dd3da6602ad0b50da725c Mon Sep 17 00:00:00 2001 From: PatMaz999 Date: Wed, 10 Sep 2025 16:30:09 +0200 Subject: [PATCH 7/7] remove commented code from properties --- src/test/resources/application-database.properties | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/test/resources/application-database.properties b/src/test/resources/application-database.properties index 7f6d0ce..faff684 100644 --- a/src/test/resources/application-database.properties +++ b/src/test/resources/application-database.properties @@ -1,7 +1,3 @@ -#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 @@ -9,7 +5,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=create-drop + spring.datasource.hikari.initialization-fail-timeout=0 spring.mail.host=localhost