diff --git a/src/main/java/org/sefglobal/scholarx/controller/IntrospectionController.java b/src/main/java/org/sefglobal/scholarx/controller/IntrospectionController.java index 199e5548..480b0649 100644 --- a/src/main/java/org/sefglobal/scholarx/controller/IntrospectionController.java +++ b/src/main/java/org/sefglobal/scholarx/controller/IntrospectionController.java @@ -1,5 +1,6 @@ package org.sefglobal.scholarx.controller; +import org.sefglobal.scholarx.exception.BadRequestException; import org.sefglobal.scholarx.exception.NoContentException; import org.sefglobal.scholarx.exception.ResourceNotFoundException; import org.sefglobal.scholarx.exception.UnauthorizedException; @@ -12,6 +13,7 @@ import org.springframework.web.bind.annotation.CookieValue; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseStatus; @@ -59,4 +61,12 @@ public List getMentees(@CookieValue long profileId, throws ResourceNotFoundException, NoContentException { return introspectionService.getMentees(id, profileId, menteeStates); } + + @PutMapping("/mentor/{id}/confirmation") + @ResponseStatus(HttpStatus.OK) + public Mentee confirmMentor(@PathVariable long id, + @CookieValue(value = "profileId") long profileId) + throws ResourceNotFoundException, BadRequestException { + return introspectionService.confirmMentor(id, profileId); + } } diff --git a/src/main/java/org/sefglobal/scholarx/controller/ProgramController.java b/src/main/java/org/sefglobal/scholarx/controller/ProgramController.java index f6bd5f11..4e3dd732 100644 --- a/src/main/java/org/sefglobal/scholarx/controller/ProgramController.java +++ b/src/main/java/org/sefglobal/scholarx/controller/ProgramController.java @@ -6,6 +6,7 @@ import org.sefglobal.scholarx.model.Mentor; import org.sefglobal.scholarx.model.Program; import org.sefglobal.scholarx.service.ProgramService; +import org.sefglobal.scholarx.util.EnrolmentState; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.CookieValue; import org.springframework.web.bind.annotation.GetMapping; @@ -14,6 +15,7 @@ import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; @@ -77,9 +79,10 @@ public Mentor updateMentorData(@PathVariable long id, @GetMapping("/{id}/mentee/mentors") @ResponseStatus(HttpStatus.OK) public List getAppliedMentors(@PathVariable long id, + @RequestParam(required = false) List menteeStates, @CookieValue(value = "profileId") long profileId) throws NoContentException { - return programService.getAppliedMentorsOfMentee(id, profileId); + return programService.getAppliedMentorsOfMentee(id, menteeStates, profileId); } @GetMapping("/{id}/mentee/mentor") diff --git a/src/main/java/org/sefglobal/scholarx/repository/MenteeRepository.java b/src/main/java/org/sefglobal/scholarx/repository/MenteeRepository.java index 6664630b..575a9af2 100644 --- a/src/main/java/org/sefglobal/scholarx/repository/MenteeRepository.java +++ b/src/main/java/org/sefglobal/scholarx/repository/MenteeRepository.java @@ -21,6 +21,8 @@ public interface MenteeRepository extends JpaRepository { List findAllByProgramIdAndProfileId(long programId, long profileId); + List findAllByProgramIdAndProfileIdAndStateIn(long programId, long profileId, List states); + Optional findByProfileIdAndMentorId(long profileId, long mentorId); List findAllByProfileId(long profileId); @@ -36,4 +38,16 @@ public interface MenteeRepository extends JpaRepository { nativeQuery = true ) void deleteByMentorProgramId(long id); + + @Modifying + @Query( + value = "UPDATE " + + "mentee " + + "SET state = 'REMOVED' " + + "WHERE profile_id = :profileId " + + "AND program_id = :programId " + + "AND mentor_id != :mentorId", + nativeQuery = true + ) + void removeAllByProgramIdAndProfileIdAndMentorIdNot(long programId, long profileId, long mentorId); } diff --git a/src/main/java/org/sefglobal/scholarx/service/IntrospectionService.java b/src/main/java/org/sefglobal/scholarx/service/IntrospectionService.java index 4577d70f..3bb50a4a 100644 --- a/src/main/java/org/sefglobal/scholarx/service/IntrospectionService.java +++ b/src/main/java/org/sefglobal/scholarx/service/IntrospectionService.java @@ -1,5 +1,6 @@ package org.sefglobal.scholarx.service; +import org.sefglobal.scholarx.exception.BadRequestException; import org.sefglobal.scholarx.exception.NoContentException; import org.sefglobal.scholarx.exception.ResourceNotFoundException; import org.sefglobal.scholarx.exception.UnauthorizedException; @@ -166,4 +167,43 @@ public List getMentees(long programId, long profileId, } return mentees; } + + /** + * Confirm a {@link Mentor} for a specific {@link Mentee} + * + * @param mentorId which is the id of the confirmed {@link Mentor} + * @param profileId which is the id of the {@link Profile} + * @return the updated {@link Mentee} + * + * @throws ResourceNotFoundException is thrown if the {@link Mentor} doesn't exist + * @throws BadRequestException is thrown if the {@link Mentee} is not approved + */ + public Mentee confirmMentor(long mentorId, long profileId) + throws ResourceNotFoundException, BadRequestException { + Optional optionalMentor = mentorRepository.findById(mentorId); + if (!optionalMentor.isPresent()) { + String msg = "Error, Mentor by id: " + mentorId + " doesn't exist."; + log.error(msg); + throw new ResourceNotFoundException(msg); + } + + Optional optionalMentee = menteeRepository.findByProfileIdAndMentorId(profileId, mentorId); + if (!optionalMentee.isPresent()) { + String msg = "Error, User with id: " + profileId + " haven't applied for " + + "mentor by id: " + mentorId + "."; + log.error(msg); + throw new BadRequestException(msg); + } + + if (!optionalMentee.get().getState().equals(EnrolmentState.APPROVED)) { + String msg = "Error, User with id: " + profileId + " is not approved " + + "by the mentor by id: " + mentorId + "."; + log.error(msg); + throw new BadRequestException(msg); + } + + long programId = optionalMentor.get().getProgram().getId(); + menteeRepository.removeAllByProgramIdAndProfileIdAndMentorIdNot(programId, profileId, mentorId); + return optionalMentee.get(); + } } diff --git a/src/main/java/org/sefglobal/scholarx/service/ProgramService.java b/src/main/java/org/sefglobal/scholarx/service/ProgramService.java index 2b26c59d..06703e96 100644 --- a/src/main/java/org/sefglobal/scholarx/service/ProgramService.java +++ b/src/main/java/org/sefglobal/scholarx/service/ProgramService.java @@ -280,16 +280,22 @@ public Mentor updateMentorData(long profileId, long programId, Mentor mentor) /** * Retrieves the applied {@link Mentor} objects of the {@link Mentee} * - * @param programId which is the id of the {@link Program} - * @param profileId which is the profile id of the {@link Mentee} + * @param programId which is the id of the {@link Program} + * @param profileId which is the profile id of the {@link Mentee} + * @param menteeStates which is the list of states that {@link Mentee} objects should be + * filtered from * @return {@link List} of {@link Mentor} objects * * @throws NoContentException if the user hasn't applied for {@link Mentor} objects */ - public List getAppliedMentorsOfMentee(long programId, long profileId) + public List getAppliedMentorsOfMentee(long programId, List menteeStates, long profileId) throws NoContentException { - List menteeList = menteeRepository - .findAllByProgramIdAndProfileId(programId, profileId); + List menteeList; + if (menteeStates == null || menteeStates.isEmpty()) { + menteeList = menteeRepository.findAllByProgramIdAndProfileId(programId, profileId); + } else { + menteeList = menteeRepository.findAllByProgramIdAndProfileIdAndStateIn(programId, profileId, menteeStates); + } if (menteeList.isEmpty()) { String msg = "Error, Mentee by program id: " + programId + " and " + "profile id: " + profileId + " doesn't exist."; diff --git a/src/main/java/org/sefglobal/scholarx/util/ProgramState.java b/src/main/java/org/sefglobal/scholarx/util/ProgramState.java index 725124b7..a892e009 100644 --- a/src/main/java/org/sefglobal/scholarx/util/ProgramState.java +++ b/src/main/java/org/sefglobal/scholarx/util/ProgramState.java @@ -6,6 +6,7 @@ public enum ProgramState { MENTOR_SELECTION, MENTEE_APPLICATION, MENTEE_SELECTION, + MENTOR_CONFIRMATION, ONGOING, COMPLETED, REMOVED; diff --git a/src/test/java/org/sefglobal/scholarx/controller/IntrospectionControllerTest.java b/src/test/java/org/sefglobal/scholarx/controller/IntrospectionControllerTest.java index b8e1e81f..8171e5b3 100644 --- a/src/test/java/org/sefglobal/scholarx/controller/IntrospectionControllerTest.java +++ b/src/test/java/org/sefglobal/scholarx/controller/IntrospectionControllerTest.java @@ -1,6 +1,7 @@ package org.sefglobal.scholarx.controller; import org.junit.jupiter.api.Test; +import org.sefglobal.scholarx.exception.BadRequestException; import org.sefglobal.scholarx.exception.NoContentException; import org.sefglobal.scholarx.exception.ResourceNotFoundException; import org.sefglobal.scholarx.exception.UnauthorizedException; @@ -16,6 +17,7 @@ import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.doThrow; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @WebMvcTest(controllers = IntrospectionController.class) @@ -25,6 +27,7 @@ public class IntrospectionControllerTest { @MockBean private IntrospectionService introspectionService; private final Long programId = 1L; + private final Long mentorId = 1L; private final Cookie profileIdCookie = new Cookie("profileId", "1"); @Test @@ -142,4 +145,33 @@ void getMentees_withUnavailableData_thenReturns204() throws Exception { .cookie(profileIdCookie)) .andExpect(status().isNoContent()); } + + @Test + void confirmMentor_withValidData_thenReturns200() throws Exception { + mockMvc.perform(put("/me/mentor/{mentorId}/confirmation", mentorId) + .cookie(profileIdCookie)) + .andExpect(status().isOk()); + } + + @Test + void confirmMentor_withUnavailableData_thenReturn404() throws Exception { + doThrow(ResourceNotFoundException.class) + .when(introspectionService) + .confirmMentor(anyLong(), anyLong()); + + mockMvc.perform(put("/me/mentor/{mentorId}/confirmation", mentorId) + .cookie(profileIdCookie)) + .andExpect(status().isNotFound()); + } + + @Test + void confirmMentor_withUnsuitableData_thenReturn400() throws Exception { + doThrow(BadRequestException.class) + .when(introspectionService) + .confirmMentor(anyLong(), anyLong()); + + mockMvc.perform(put("/me/mentor/{mentorId}/confirmation", mentorId) + .cookie(profileIdCookie)) + .andExpect(status().isBadRequest()); + } } diff --git a/src/test/java/org/sefglobal/scholarx/controller/MentorControllerTest.java b/src/test/java/org/sefglobal/scholarx/controller/MentorControllerTest.java index a7c019c6..827f354b 100644 --- a/src/test/java/org/sefglobal/scholarx/controller/MentorControllerTest.java +++ b/src/test/java/org/sefglobal/scholarx/controller/MentorControllerTest.java @@ -16,7 +16,6 @@ import org.springframework.test.web.servlet.MockMvc; import javax.servlet.http.Cookie; - import java.util.HashMap; import java.util.Map; diff --git a/src/test/java/org/sefglobal/scholarx/controller/ProgramControllerTest.java b/src/test/java/org/sefglobal/scholarx/controller/ProgramControllerTest.java index a16225cc..2ab53d2d 100644 --- a/src/test/java/org/sefglobal/scholarx/controller/ProgramControllerTest.java +++ b/src/test/java/org/sefglobal/scholarx/controller/ProgramControllerTest.java @@ -266,7 +266,7 @@ void getAppliedMentors_withValidData_thenReturns200() throws Exception { void getAppliedMentors_withUnavailableData_thenReturns204() throws Exception { doThrow(NoContentException.class) .when(programService) - .getAppliedMentorsOfMentee(anyLong(), anyLong()); + .getAppliedMentorsOfMentee(anyLong(), any(), anyLong()); mockMvc.perform(get("/programs/{id}/mentee/mentors", programId) .cookie(profileIdCookie)) diff --git a/src/test/java/org/sefglobal/scholarx/service/IntrospectionServiceTest.java b/src/test/java/org/sefglobal/scholarx/service/IntrospectionServiceTest.java index f2f42c5d..5a03c774 100644 --- a/src/test/java/org/sefglobal/scholarx/service/IntrospectionServiceTest.java +++ b/src/test/java/org/sefglobal/scholarx/service/IntrospectionServiceTest.java @@ -5,13 +5,17 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.sefglobal.scholarx.exception.BadRequestException; import org.sefglobal.scholarx.exception.NoContentException; import org.sefglobal.scholarx.exception.ResourceNotFoundException; import org.sefglobal.scholarx.exception.UnauthorizedException; +import org.sefglobal.scholarx.model.Mentee; import org.sefglobal.scholarx.model.Mentor; +import org.sefglobal.scholarx.model.Program; import org.sefglobal.scholarx.repository.MenteeRepository; import org.sefglobal.scholarx.repository.MentorRepository; import org.sefglobal.scholarx.repository.ProfileRepository; +import org.sefglobal.scholarx.util.EnrolmentState; import java.util.ArrayList; import java.util.Collections; @@ -19,6 +23,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.catchThrowable; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.anyLong; import static org.mockito.Mockito.doReturn; @@ -34,6 +39,10 @@ public class IntrospectionServiceTest { private IntrospectionService introspectionService; private final Long programId = 1L; private final long profileId = 1L; + private final long mentorId = 1L; + private final Mentor mentor = new Mentor(); + private final Mentee mentee = + new Mentee("http://submission.url/"); @Test void getLoggedInUser_withUnavailableData_thenThrowResourceNotFound() { @@ -143,4 +152,69 @@ void getMentees_withUnavailableData_thenThrowNoContent() { .isInstanceOf(NoContentException.class) .hasMessage("No mentees exist for the required program: 1 for the profile: 1"); } + + @Test + void confirmMentor_withValidData_thenReturnUpdatedData() + throws ResourceNotFoundException, BadRequestException { + mentee.setState(EnrolmentState.APPROVED); + Program program = new Program(); + program.setId(programId); + mentor.setProgram(program); + doReturn(Optional.of(mentor)) + .when(mentorRepository) + .findById(anyLong()); + doReturn(Optional.of(mentee)) + .when(menteeRepository) + .findByProfileIdAndMentorId(anyLong(), anyLong()); + + Mentee savedMentee = introspectionService.confirmMentor(mentorId, profileId); + assertThat(savedMentee).isNotNull(); + assertThat(savedMentee.getState()).isEqualTo(EnrolmentState.APPROVED); + } + + @Test + void confirmMentor_withUnavailableData_thenThrowResourceNotFound() { + doReturn(Optional.empty()) + .when(mentorRepository) + .findById(anyLong()); + + Throwable thrown = catchThrowable( + () -> introspectionService.confirmMentor(mentorId, profileId)); + assertThat(thrown) + .isInstanceOf(ResourceNotFoundException.class) + .hasMessage("Error, Mentor by id: 1 doesn't exist."); + } + + @Test + void confirmMentor_withUnavailableData_thenThrowBadRequest() { + doReturn(Optional.of(mentor)) + .when(mentorRepository) + .findById(anyLong()); + doReturn(Optional.empty()) + .when(menteeRepository) + .findByProfileIdAndMentorId(anyLong(), anyLong()); + + Throwable thrown = catchThrowable( + () -> introspectionService.confirmMentor(mentorId, profileId)); + assertThat(thrown) + .isInstanceOf(BadRequestException.class) + .hasMessage("Error, User with id: 1 haven't applied for mentor by id: 1."); + } + + @Test + void confirmMentor_withUnsuitableData_thenThrowBadRequest() { + mentee.setState(EnrolmentState.PENDING); + doReturn(Optional.of(mentor)) + .when(mentorRepository) + .findById(anyLong()); + doReturn(Optional.of(mentee)) + .when(menteeRepository) + .findByProfileIdAndMentorId(anyLong(), anyLong()); + + Throwable thrown = catchThrowable( + () -> introspectionService.confirmMentor(mentorId, profileId)); + assertThat(thrown) + .isInstanceOf(BadRequestException.class) + .hasMessage("Error, User with id: 1 is not approved by the mentor by id: 1."); + } } diff --git a/src/test/java/org/sefglobal/scholarx/service/ProgramServiceTest.java b/src/test/java/org/sefglobal/scholarx/service/ProgramServiceTest.java index 7b531ba0..0cc172a7 100644 --- a/src/test/java/org/sefglobal/scholarx/service/ProgramServiceTest.java +++ b/src/test/java/org/sefglobal/scholarx/service/ProgramServiceTest.java @@ -20,7 +20,6 @@ import org.sefglobal.scholarx.util.ProgramState; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Optional; @@ -291,7 +290,7 @@ void getAppliedMentorsOfMentee_withUnavailableData_thenThrowNoContent() { .findAllByProgramIdAndProfileId(anyLong(), anyLong()); Throwable thrown = catchThrowable( - () -> programService.getAppliedMentorsOfMentee(programId, profileId)); + () -> programService.getAppliedMentorsOfMentee(programId, new ArrayList<>(), profileId)); assertThat(thrown) .isInstanceOf(NoContentException.class) .hasMessage("Error, Mentee by program id: 1 and " +