From e5bf21b7c425fbb173e338b39e830a39ed53e47f Mon Sep 17 00:00:00 2001 From: Sowmiya Ravikumar Date: Sun, 18 Jan 2026 03:40:17 +0000 Subject: [PATCH 1/2] Fix: Mentorship bean validation * Added @Valid to create and update of member, mentor and mentee * Fixed field annotations to standard placement * Removed unused imports * Remove redundant annotation from constructors --- .../platform/controller/MemberController.java | 11 +++--- .../domain/platform/mentorship/Mentee.java | 35 +++++++++---------- .../domain/platform/mentorship/Mentor.java | 29 ++++++++------- .../domain/platform/mentorship/MentorDto.java | 2 +- 4 files changed, 38 insertions(+), 39 deletions(-) diff --git a/src/main/java/com/wcc/platform/controller/MemberController.java b/src/main/java/com/wcc/platform/controller/MemberController.java index 949fd9f3..a4cac4fc 100644 --- a/src/main/java/com/wcc/platform/controller/MemberController.java +++ b/src/main/java/com/wcc/platform/controller/MemberController.java @@ -13,6 +13,7 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import java.util.List; import lombok.AllArgsConstructor; import org.springframework.http.HttpStatus; @@ -85,7 +86,7 @@ public ResponseEntity> getAllMentors() { @PostMapping("/members") @Operation(summary = "API to submit member registration") @ResponseStatus(HttpStatus.CREATED) - public ResponseEntity createMember(@RequestBody final Member member) { + public ResponseEntity createMember(@Valid @RequestBody final Member member) { return new ResponseEntity<>(memberService.createMember(member), HttpStatus.CREATED); } @@ -97,7 +98,7 @@ public ResponseEntity createMember(@RequestBody final Member member) { @PostMapping("/mentors") @Operation(summary = "API to submit mentor registration") @ResponseStatus(HttpStatus.CREATED) - public ResponseEntity createMentor(@RequestBody final Mentor mentor) { + public ResponseEntity createMentor(@Valid @RequestBody final Mentor mentor) { return new ResponseEntity<>(mentorshipService.create(mentor), HttpStatus.CREATED); } @@ -112,7 +113,7 @@ public ResponseEntity createMentor(@RequestBody final Mentor mentor) { @Operation(summary = "API to update mentor data") @ResponseStatus(HttpStatus.OK) public ResponseEntity updateMentor( - @PathVariable final Long mentorId, @RequestBody final MentorDto mentorDto) { + @PathVariable final Long mentorId, @Valid @RequestBody final MentorDto mentorDto) { return new ResponseEntity<>(mentorshipService.updateMentor(mentorId, mentorDto), HttpStatus.OK); } @@ -124,7 +125,7 @@ public ResponseEntity updateMentor( @PostMapping("/mentees") @Operation(summary = "API to submit mentee registration") @ResponseStatus(HttpStatus.CREATED) - public ResponseEntity createMentee(@RequestBody final Mentee mentee) { + public ResponseEntity createMentee(@Valid @RequestBody final Mentee mentee) { return new ResponseEntity<>(menteeService.create(mentee), HttpStatus.CREATED); } @@ -139,7 +140,7 @@ public ResponseEntity createMentee(@RequestBody final Mentee mentee) { @Operation(summary = "API to update member data") @ResponseStatus(HttpStatus.OK) public ResponseEntity updateMember( - @PathVariable final Long memberId, @RequestBody final MemberDto memberDto) { + @PathVariable final Long memberId, @Valid @RequestBody final MemberDto memberDto) { return new ResponseEntity<>(memberService.updateMember(memberId, memberDto), HttpStatus.OK); } diff --git a/src/main/java/com/wcc/platform/domain/platform/mentorship/Mentee.java b/src/main/java/com/wcc/platform/domain/platform/mentorship/Mentee.java index ac07d9f4..fc7ac038 100644 --- a/src/main/java/com/wcc/platform/domain/platform/mentorship/Mentee.java +++ b/src/main/java/com/wcc/platform/domain/platform/mentorship/Mentee.java @@ -6,7 +6,6 @@ import com.wcc.platform.domain.platform.member.Member; import com.wcc.platform.domain.platform.member.ProfileStatus; import com.wcc.platform.domain.platform.type.MemberType; -import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import java.util.Collections; @@ -21,31 +20,31 @@ @SuppressWarnings({"PMD.ExcessiveParameterList", "PMD.ImmutableField"}) public class Mentee extends Member { - private @NotBlank MentorshipType prevMentorshipType; - private @NotBlank MentorshipType mentorshipType; - private @NotNull ProfileStatus profileStatus; - private @NotBlank Skills skills; - private @NotBlank String bio; + @NotNull private MentorshipType prevMentorshipType; + @NotNull private MentorshipType mentorshipType; + @NotNull private ProfileStatus profileStatus; + @NotNull private Skills skills; + @NotBlank private String bio; private List spokenLanguages; @Builder(builderMethodName = "menteeBuilder") public Mentee( final Long id, - @NotBlank final String fullName, - @NotBlank final String position, - @NotBlank @Email final String email, - @NotBlank final String slackDisplayName, - @NotBlank final Country country, - @NotBlank final String city, + final String fullName, + final String position, + final String email, + final String slackDisplayName, + final Country country, + final String city, final String companyName, final List images, final List network, - @NotNull final ProfileStatus profileStatus, - final List spokenLanguages, // TODO - @NotBlank final String bio, - @NotBlank final Skills skills, - @NotBlank final MentorshipType mentorshipType, - @NotBlank final MentorshipType prevMentorshipType) { + final ProfileStatus profileStatus, + final List spokenLanguages, + final String bio, + final Skills skills, + final MentorshipType mentorshipType, + final MentorshipType prevMentorshipType) { super( id, fullName, diff --git a/src/main/java/com/wcc/platform/domain/platform/mentorship/Mentor.java b/src/main/java/com/wcc/platform/domain/platform/mentorship/Mentor.java index e4a6a052..2619cae6 100644 --- a/src/main/java/com/wcc/platform/domain/platform/mentorship/Mentor.java +++ b/src/main/java/com/wcc/platform/domain/platform/mentorship/Mentor.java @@ -10,7 +10,6 @@ import com.wcc.platform.domain.platform.mentorship.MentorDto.MentorDtoBuilder; import com.wcc.platform.domain.platform.type.MemberType; import com.wcc.platform.domain.resource.MentorResource; -import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import java.util.Collections; @@ -30,11 +29,11 @@ @SuppressWarnings("PMD.ImmutableField") public class Mentor extends Member { - private @NotBlank ProfileStatus profileStatus; - private @NotBlank Skills skills; + @NotNull private ProfileStatus profileStatus; + @NotNull private Skills skills; private List spokenLanguages; - private @NotBlank String bio; - private @NotNull MenteeSection menteeSection; + @NotBlank private String bio; + @NotNull private MenteeSection menteeSection; private FeedbackSection feedbackSection; private MentorResource resources; @@ -43,20 +42,20 @@ public class Mentor extends Member { @SuppressWarnings("PMD.ExcessiveParameterList") public Mentor( final Long id, - @NotBlank final String fullName, - @NotBlank final String position, - @NotBlank @Email final String email, - @NotBlank final String slackDisplayName, - @NotBlank final Country country, - @NotBlank final String city, + final String fullName, + final String position, + final String email, + final String slackDisplayName, + final Country country, + final String city, final String companyName, final List images, final List network, - @NotNull final ProfileStatus profileStatus, + final ProfileStatus profileStatus, final List spokenLanguages, - @NotBlank final String bio, - @NotBlank final Skills skills, - @NotBlank final MenteeSection menteeSection, + final String bio, + final Skills skills, + final MenteeSection menteeSection, final FeedbackSection feedbackSection, final MentorResource resources) { super( diff --git a/src/main/java/com/wcc/platform/domain/platform/mentorship/MentorDto.java b/src/main/java/com/wcc/platform/domain/platform/mentorship/MentorDto.java index c7157aac..e09e562b 100644 --- a/src/main/java/com/wcc/platform/domain/platform/mentorship/MentorDto.java +++ b/src/main/java/com/wcc/platform/domain/platform/mentorship/MentorDto.java @@ -49,7 +49,7 @@ public MentorDto( @NotBlank final String position, @NotBlank @Email final String email, @NotBlank final String slackDisplayName, - @NotBlank final Country country, + @NotNull final Country country, @NotBlank final String city, final String companyName, final List images, From 755b8ee85daddc61839fdd5bbfe9498efd4c5c87 Mon Sep 17 00:00:00 2001 From: Sowmiya Ravikumar Date: Mon, 19 Jan 2026 04:35:09 +0000 Subject: [PATCH 2/2] Fix: Mentorship bean validation * Add handler for MethodArgumentNotValidException * Made prevMentorshipType optional * Fix tests --- .../configuration/GlobalExceptionHandler.java | 20 ++++++ .../domain/platform/mentorship/Mentee.java | 2 +- .../GlobalExceptionHandlerTest.java | 30 ++++++++ .../factories/SetupMenteeFactories.java | 68 ++++++++++--------- 4 files changed, 86 insertions(+), 34 deletions(-) diff --git a/src/main/java/com/wcc/platform/configuration/GlobalExceptionHandler.java b/src/main/java/com/wcc/platform/configuration/GlobalExceptionHandler.java index f76af2ac..def633dc 100644 --- a/src/main/java/com/wcc/platform/configuration/GlobalExceptionHandler.java +++ b/src/main/java/com/wcc/platform/configuration/GlobalExceptionHandler.java @@ -16,8 +16,10 @@ import com.wcc.platform.repository.file.FileRepositoryException; import jakarta.validation.ConstraintViolationException; import java.util.NoSuchElementException; +import java.util.stream.Collectors; 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.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -100,4 +102,22 @@ public ResponseEntity handleNotAcceptableError( HttpStatus.NOT_ACCEPTABLE.value(), ex.getMessage(), request.getDescription(false)); return new ResponseEntity<>(errorDetails, HttpStatus.NOT_ACCEPTABLE); } + + /** + * Receive {@link MethodArgumentNotValidException} for bean validation errors and return {@link + * HttpStatus#NOT_ACCEPTABLE}. + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ResponseEntity handleMethodArgumentNotValidException( + final MethodArgumentNotValidException ex, final WebRequest request) { + final var errorMessage = + ex.getBindingResult().getFieldErrors().stream() + .map(error -> error.getField() + ": " + error.getDefaultMessage()) + .collect(Collectors.joining(", ")); + final var errorDetails = + new ErrorDetails( + HttpStatus.BAD_REQUEST.value(), errorMessage, request.getDescription(false)); + return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST); + } } diff --git a/src/main/java/com/wcc/platform/domain/platform/mentorship/Mentee.java b/src/main/java/com/wcc/platform/domain/platform/mentorship/Mentee.java index fc7ac038..0f010175 100644 --- a/src/main/java/com/wcc/platform/domain/platform/mentorship/Mentee.java +++ b/src/main/java/com/wcc/platform/domain/platform/mentorship/Mentee.java @@ -20,7 +20,7 @@ @SuppressWarnings({"PMD.ExcessiveParameterList", "PMD.ImmutableField"}) public class Mentee extends Member { - @NotNull private MentorshipType prevMentorshipType; + private MentorshipType prevMentorshipType; @NotNull private MentorshipType mentorshipType; @NotNull private ProfileStatus profileStatus; @NotNull private Skills skills; diff --git a/src/test/java/com/wcc/platform/configuration/GlobalExceptionHandlerTest.java b/src/test/java/com/wcc/platform/configuration/GlobalExceptionHandlerTest.java index 7601d932..f2c3c5e1 100644 --- a/src/test/java/com/wcc/platform/configuration/GlobalExceptionHandlerTest.java +++ b/src/test/java/com/wcc/platform/configuration/GlobalExceptionHandlerTest.java @@ -3,12 +3,18 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.springframework.http.HttpStatus.BAD_REQUEST; import static org.springframework.http.HttpStatus.CONFLICT; import com.wcc.platform.domain.exceptions.DuplicatedMemberException; import com.wcc.platform.domain.exceptions.ErrorDetails; +import java.util.List; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.springframework.validation.BindingResult; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.context.request.WebRequest; class GlobalExceptionHandlerTest { @@ -34,4 +40,28 @@ void givenHandleDuplicatedRecordWhenHandleDuplicatedRecordThenConsiderConflict() assertEquals(CONFLICT, response.getStatusCode()); assertEquals(expectation, response.getBody()); } + + @Test + @DisplayName( + "Given validation errors, when handling MethodArgumentNotValidException, then return NOT_ACCEPTABLE with error messages") + void shouldReturnNotAcceptableForMethodArgumentNotValidException() { + var bindingResult = mock(BindingResult.class); + var fieldError1 = new FieldError("object", "email", "must not be blank"); + var fieldError2 = new FieldError("object", "name", "size must be between 1 and 100"); + when(bindingResult.getFieldErrors()).thenReturn(List.of(fieldError1, fieldError2)); + + var exception = mock(MethodArgumentNotValidException.class); + when(exception.getBindingResult()).thenReturn(bindingResult); + + var response = + globalExceptionHandler.handleMethodArgumentNotValidException(exception, webRequest); + + var expectation = + new ErrorDetails( + BAD_REQUEST.value(), + "email: must not be blank, name: size must be between 1 and 100", + DETAILS); + assertEquals(BAD_REQUEST, response.getStatusCode()); + assertEquals(expectation, response.getBody()); + } } diff --git a/src/test/java/com/wcc/platform/factories/SetupMenteeFactories.java b/src/test/java/com/wcc/platform/factories/SetupMenteeFactories.java index 58dedf21..3d0debf5 100644 --- a/src/test/java/com/wcc/platform/factories/SetupMenteeFactories.java +++ b/src/test/java/com/wcc/platform/factories/SetupMenteeFactories.java @@ -8,46 +8,48 @@ import com.wcc.platform.domain.platform.member.Member; import com.wcc.platform.domain.platform.member.ProfileStatus; import com.wcc.platform.domain.platform.mentorship.Mentee; +import com.wcc.platform.domain.platform.mentorship.MentorshipType; import com.wcc.platform.domain.platform.mentorship.Skills; import com.wcc.platform.domain.platform.type.MemberType; import java.util.List; /** Mentee test factories. */ public class SetupMenteeFactories { - /** Mentee Builder. */ - public static Mentee createMenteeTest() { - final Member member = createMemberTest(MemberType.MENTEE); - return createMenteeTest(1L, member.getFullName(), member.getEmail()); - } - - /** Test factory for a Mentee. */ - public static Mentee createMenteeTest( - final Long menteeId, final String name, final String email) { - final Member member = createMemberTest(MemberType.MENTEE); + /** Mentee Builder. */ + public static Mentee createMenteeTest() { + final Member member = createMemberTest(MemberType.MENTEE); + return createMenteeTest(1L, member.getFullName(), member.getEmail()); + } - Mentee.MenteeBuilder menteeBuilder = - Mentee.menteeBuilder() - .fullName(name) - .position(member.getPosition()) - .email(email) - .companyName(member.getCompanyName()) - .slackDisplayName(member.getSlackDisplayName()) - .country(member.getCountry()) - .city(member.getCity()) - .images(member.getImages()) - .profileStatus(ProfileStatus.ACTIVE) - .bio("Mentee bio") - .spokenLanguages(List.of("English")) - .skills( - new Skills( - 2, - List.of(TechnicalArea.BACKEND, TechnicalArea.FRONTEND), - List.of(Languages.JAVASCRIPT), - List.of(MentorshipFocusArea.GROW_BEGINNER_TO_MID))); - if (menteeId != null) { - menteeBuilder.id(menteeId); - } + /** Test factory for a Mentee. */ + public static Mentee createMenteeTest( + final Long menteeId, final String name, final String email) { + final Member member = createMemberTest(MemberType.MENTEE); - return menteeBuilder.build(); + Mentee.MenteeBuilder menteeBuilder = + Mentee.menteeBuilder() + .fullName(name) + .position(member.getPosition()) + .email(email) + .companyName(member.getCompanyName()) + .slackDisplayName(member.getSlackDisplayName()) + .country(member.getCountry()) + .city(member.getCity()) + .images(member.getImages()) + .profileStatus(ProfileStatus.ACTIVE) + .bio("Mentee bio") + .spokenLanguages(List.of("English")) + .skills( + new Skills( + 2, + List.of(TechnicalArea.BACKEND, TechnicalArea.FRONTEND), + List.of(Languages.JAVASCRIPT), + List.of(MentorshipFocusArea.GROW_BEGINNER_TO_MID))) + .mentorshipType(MentorshipType.AD_HOC); + if (menteeId != null) { + menteeBuilder.id(menteeId); } + + return menteeBuilder.build(); + } }