From 12ab911846d7ac1de9ee11156612f0d31940aa29 Mon Sep 17 00:00:00 2001 From: Adriana Zencke Zimmermann Date: Thu, 7 May 2026 06:59:06 +0200 Subject: [PATCH 01/11] fix: use MemberDto instead of Member in CollaboratorPage The CollaboratorPage record was using the Member entity directly in its collaborators list, coupling the API response layer to the domain model. This caused a mismatch since collaborators should be presented as DTOs rather than exposing the full entity with its validation constraints. --- .../com/wcc/platform/domain/cms/pages/CollaboratorPage.java | 4 ++-- .../java/com/wcc/platform/service/CmsAboutUsService.java | 4 +--- .../com/wcc/platform/controller/AboutControllerTest.java | 5 +++-- .../java/com/wcc/platform/factories/SetupPagesFactories.java | 4 ++-- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/wcc/platform/domain/cms/pages/CollaboratorPage.java b/src/main/java/com/wcc/platform/domain/cms/pages/CollaboratorPage.java index 825a35945..4a01adf0a 100644 --- a/src/main/java/com/wcc/platform/domain/cms/pages/CollaboratorPage.java +++ b/src/main/java/com/wcc/platform/domain/cms/pages/CollaboratorPage.java @@ -3,7 +3,7 @@ import com.wcc.platform.domain.cms.attributes.CommonSection; import com.wcc.platform.domain.cms.attributes.Contact; import com.wcc.platform.domain.cms.attributes.HeroSection; -import com.wcc.platform.domain.platform.member.Member; +import com.wcc.platform.domain.platform.member.MemberDto; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import java.util.List; @@ -15,4 +15,4 @@ public record CollaboratorPage( @NotNull HeroSection heroSection, @NotNull CommonSection section, @NotNull Contact contact, - @NotEmpty List collaborators) {} + @NotEmpty List collaborators) {} diff --git a/src/main/java/com/wcc/platform/service/CmsAboutUsService.java b/src/main/java/com/wcc/platform/service/CmsAboutUsService.java index 0f42ee049..63500e8ea 100644 --- a/src/main/java/com/wcc/platform/service/CmsAboutUsService.java +++ b/src/main/java/com/wcc/platform/service/CmsAboutUsService.java @@ -12,10 +12,8 @@ import com.wcc.platform.domain.cms.pages.aboutus.PartnersPage; import com.wcc.platform.domain.exceptions.ContentNotFoundException; import com.wcc.platform.domain.exceptions.PlatformInternalException; -import com.wcc.platform.domain.platform.member.Member; import com.wcc.platform.repository.PageRepository; import com.wcc.platform.utils.PaginationUtil; -import java.util.List; import org.springframework.stereotype.Service; /** CMS service responsible for simple pages. */ @@ -61,7 +59,7 @@ public CollaboratorPage getCollaborator(final int currentPage, final int pageSiz try { final var page = objectMapper.convertValue(pageOptional.get(), CollaboratorPage.class); final var allCollaborators = page.collaborators(); - final List pagCollaborators = + final var pagCollaborators = PaginationUtil.getPaginatedResult(allCollaborators, currentPage, pageSize); final Pagination paginationRecord = diff --git a/src/test/java/com/wcc/platform/controller/AboutControllerTest.java b/src/test/java/com/wcc/platform/controller/AboutControllerTest.java index 45026f867..96539e4d6 100644 --- a/src/test/java/com/wcc/platform/controller/AboutControllerTest.java +++ b/src/test/java/com/wcc/platform/controller/AboutControllerTest.java @@ -221,7 +221,6 @@ void testCollaboratorInternalError() throws Exception { @Test void testCollaboratorOkResponse() throws Exception { var fileName = COLLABORATOR.getFileName(); - var expectedJson = FileUtil.readFileAsString(fileName); when(service.getCollaborator(anyInt(), anyInt())) .thenReturn(createCollaboratorPageTest(fileName)); @@ -231,7 +230,9 @@ void testCollaboratorOkResponse() throws Exception { getRequest(String.format("%s%s", API_COLLABORATORS, PAGINATION_COLLABORATORS)) .contentType(APPLICATION_JSON)) .andExpect(status().isOk()) - .andExpect(content().json(expectedJson)); + .andExpect(jsonPath("$.id", is("page:COLLABORATORS"))) + .andExpect(jsonPath("$.heroSection.title", is("WCC Collaborators"))) + .andExpect(jsonPath("$.collaborators.length()", is(1))); } @Test diff --git a/src/test/java/com/wcc/platform/factories/SetupPagesFactories.java b/src/test/java/com/wcc/platform/factories/SetupPagesFactories.java index 67649fc3d..f226530a7 100644 --- a/src/test/java/com/wcc/platform/factories/SetupPagesFactories.java +++ b/src/test/java/com/wcc/platform/factories/SetupPagesFactories.java @@ -32,7 +32,7 @@ import com.wcc.platform.domain.cms.pages.aboutus.PartnersPage; import com.wcc.platform.domain.platform.SocialNetwork; import com.wcc.platform.domain.platform.SocialNetworkType; -import com.wcc.platform.domain.platform.member.Member; +import com.wcc.platform.domain.platform.member.MemberDto; import com.wcc.platform.domain.platform.type.MemberType; import com.wcc.platform.utils.FileUtil; import java.util.Collections; @@ -86,7 +86,7 @@ public static CollaboratorPage createCollaboratorPageTest() { createCommonSectionTest(), createContactTest(), List.of( - Member.builder() + MemberDto.builder() .fullName("fullName " + MemberType.MEMBER.name()) .position("position " + MemberType.MEMBER.name()) .email("member@wcc.com") From 61adb44e445e99c6d7ed8e94c2e7be63bf6e909c Mon Sep 17 00:00:00 2001 From: Adriana Zencke Zimmermann Date: Thu, 7 May 2026 06:59:26 +0200 Subject: [PATCH 02/11] refactor: harden Member and MemberDto validation with messages Bean validation constraints on Member, MemberDto, and LeadershipMember previously used generic messages that were unhelpful to API consumers. This change adds @Validated, human-readable error messages, and moves memberTypes assignment to MemberService so the field remains server- controlled rather than populated from client input. --- .../platform/controller/MemberController.java | 2 +- .../platform/member/LeadershipMember.java | 18 ++++++----- .../domain/platform/member/Member.java | 31 +++++++++++++++---- .../domain/platform/member/MemberDto.java | 24 ++++++++++++++ .../wcc/platform/service/MemberService.java | 6 ++++ .../controller/MemberControllerTest.java | 3 +- 6 files changed, 68 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/wcc/platform/controller/MemberController.java b/src/main/java/com/wcc/platform/controller/MemberController.java index 0376a556c..d423f8740 100644 --- a/src/main/java/com/wcc/platform/controller/MemberController.java +++ b/src/main/java/com/wcc/platform/controller/MemberController.java @@ -46,7 +46,7 @@ public ResponseEntity> getAllMembers() { final List members = memberService.getAllMembers(); return ResponseEntity.ok(members); } - + /** * API to create member. * diff --git a/src/main/java/com/wcc/platform/domain/platform/member/LeadershipMember.java b/src/main/java/com/wcc/platform/domain/platform/member/LeadershipMember.java index a122f4f15..587c78d70 100644 --- a/src/main/java/com/wcc/platform/domain/platform/member/LeadershipMember.java +++ b/src/main/java/com/wcc/platform/domain/platform/member/LeadershipMember.java @@ -1,17 +1,20 @@ package com.wcc.platform.domain.platform.member; -import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; import com.wcc.platform.domain.cms.attributes.Country; import com.wcc.platform.domain.cms.attributes.Image; +import com.wcc.platform.domain.cms.attributes.PronounCategory; import com.wcc.platform.domain.platform.SocialNetwork; import com.wcc.platform.domain.platform.type.MemberType; -import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.NotEmpty; import java.util.List; import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.ToString; +import org.apache.commons.lang3.StringUtils; +import org.springframework.validation.annotation.Validated; /** * Represents the core team of the community: {@link MemberType#DIRECTOR}, {@link MemberType#LEADER} @@ -21,11 +24,12 @@ @EqualsAndHashCode(callSuper = true) @ToString @NoArgsConstructor +@Validated public class LeadershipMember extends Member { @SuppressWarnings("PMD.ImmutableField") - @JsonIgnore - @NotNull + @NotEmpty(message = "At least one member type must be provided: Leader/Director/Evangelist") + @JsonProperty(access = JsonProperty.Access.READ_ONLY) private List memberTypes; /** Leadership Builder. */ @@ -55,9 +59,9 @@ public LeadershipMember( memberTypes, images, network, - null, - null, - null); + StringUtils.EMPTY, + PronounCategory.FEMININE, + Boolean.TRUE); this.memberTypes = memberTypes; } diff --git a/src/main/java/com/wcc/platform/domain/platform/member/Member.java b/src/main/java/com/wcc/platform/domain/platform/member/Member.java index 56cb9ca4a..0e4724dfe 100644 --- a/src/main/java/com/wcc/platform/domain/platform/member/Member.java +++ b/src/main/java/com/wcc/platform/domain/platform/member/Member.java @@ -9,6 +9,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import java.util.List; import lombok.AllArgsConstructor; @@ -18,6 +19,7 @@ import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; +import org.springframework.validation.annotation.Validated; /** Member class with common attributes for all community members. */ @NoArgsConstructor @@ -26,20 +28,37 @@ @EqualsAndHashCode @Getter @Builder(toBuilder = true) +@Validated public class Member { @Setter @Schema(accessMode = Schema.AccessMode.READ_ONLY, description = "Auto-generated member ID") @JsonProperty(access = JsonProperty.Access.READ_ONLY) private Long id; - @NotBlank private String fullName; - @NotBlank private String position; - @Setter @NotBlank @Email private String email; - @NotBlank private String slackDisplayName; - @NotNull private Country country; + @NotBlank(message = "Full name cannot be blank") + private String fullName; + + @NotBlank(message = "Position cannot be blank") + private String position; + + @Setter + @NotBlank(message = "Email cannot be blank") + @Email(message = "Email format is not valid") + private String email; + + @NotBlank(message = "Slack name cannot be blank") + private String slackDisplayName; + + @NotNull(message = "Country cannot be null") + private Country country; + private String city; private String companyName; - @Setter @NotNull private List memberTypes; + + @Setter + @NotEmpty(message = "At least one member type must be provided (e.g., Member, Volunteer, etc.)") + private List memberTypes; + @Setter private List images; private List network; private String pronouns; diff --git a/src/main/java/com/wcc/platform/domain/platform/member/MemberDto.java b/src/main/java/com/wcc/platform/domain/platform/member/MemberDto.java index b1118fb1f..e379da7e5 100644 --- a/src/main/java/com/wcc/platform/domain/platform/member/MemberDto.java +++ b/src/main/java/com/wcc/platform/domain/platform/member/MemberDto.java @@ -7,13 +7,18 @@ import com.wcc.platform.domain.platform.SocialNetwork; import com.wcc.platform.domain.platform.type.MemberType; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import java.util.List; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; import lombok.ToString; +import org.springframework.validation.annotation.Validated; /** MemberDto class with common attributes for all community members. */ @Getter @@ -22,19 +27,38 @@ @EqualsAndHashCode @ToString @Builder +@Validated public class MemberDto { @Schema(accessMode = Schema.AccessMode.READ_ONLY) @JsonProperty(access = JsonProperty.Access.READ_ONLY) private Long id; + @NotBlank(message = "Full name cannot be blank") private String fullName; + + @NotBlank(message = "Position cannot be blank") private String position; + + @Setter + @NotBlank(message = "Email cannot be blank") + @Email(message = "Email format is not valid") private String email; + + @NotBlank(message = "Slack name cannot be blank") private String slackDisplayName; + + @NotNull(message = "Country cannot be null") private Country country; + private String city; private String companyName; + + @Schema( + accessMode = Schema.AccessMode.READ_ONLY, + description = "List of Member types (e.g., Mentor, Leader, Volunteer, etc.)") + @JsonProperty(access = JsonProperty.Access.READ_ONLY) private List memberTypes; + private List images; private List network; private String pronouns; diff --git a/src/main/java/com/wcc/platform/service/MemberService.java b/src/main/java/com/wcc/platform/service/MemberService.java index 5ff5fbc6e..2c9eab02a 100644 --- a/src/main/java/com/wcc/platform/service/MemberService.java +++ b/src/main/java/com/wcc/platform/service/MemberService.java @@ -7,6 +7,7 @@ import com.wcc.platform.domain.exceptions.MemberNotFoundException; import com.wcc.platform.domain.platform.member.Member; import com.wcc.platform.domain.platform.member.MemberDto; +import com.wcc.platform.domain.platform.type.MemberType; import com.wcc.platform.domain.platform.type.RoleType; import com.wcc.platform.domain.resource.MemberProfilePicture; import com.wcc.platform.domain.resource.Resource; @@ -18,6 +19,7 @@ import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; /** Platform Service. */ @Slf4j @@ -32,6 +34,10 @@ public class MemberService { /** Save Member into storage. */ public Member createMember(final Member member) { + if (CollectionUtils.isEmpty(member.getMemberTypes())) { + member.setMemberTypes(List.of(MemberType.MEMBER)); + } + final Optional memberOptional = emailExists(member.getEmail()); final var userExists = userRepository.findByEmail(member.getEmail()).isPresent(); if (memberOptional.isPresent()) { diff --git a/src/test/java/com/wcc/platform/controller/MemberControllerTest.java b/src/test/java/com/wcc/platform/controller/MemberControllerTest.java index 477135b62..d2cf1b865 100644 --- a/src/test/java/com/wcc/platform/controller/MemberControllerTest.java +++ b/src/test/java/com/wcc/platform/controller/MemberControllerTest.java @@ -66,8 +66,6 @@ void testCreateMemberReturnsCreated() throws Exception { mockMvc .perform(postRequest(API_MEMBERS, member)) .andExpect(status().isCreated()) - .andExpect(jsonPath("$.id", is(1))) - .andExpect(jsonPath("$.email", is("member@wcc.com"))) .andExpect(jsonPath("$.fullName", is("fullName MEMBER"))); } @@ -114,6 +112,7 @@ void testCreateMemberWithIsWomenReturnsFieldInResponse() throws Exception { mockMvc .perform(postRequest(API_MEMBERS, member)) .andExpect(status().isCreated()) + .andExpect(jsonPath("$.fullName", is("fullName MEMBER"))) .andExpect(jsonPath("$.isWomen", is(true))); } From e30f3667511f1f480dde44daeec7b270844d15d2 Mon Sep 17 00:00:00 2001 From: Adriana Zencke Zimmermann Date: Thu, 7 May 2026 06:59:38 +0200 Subject: [PATCH 03/11] feat: make Mentor memberTypes dynamic instead of hardcoded Mentor previously hardcoded [MENTOR] as its memberTypes in the constructor, silently discarding any prior types when an existing Member registered as a Mentor. MentorshipService now assigns memberTypes explicitly and merges them with the existing member's types, preserving the full type history across community roles. --- .../domain/platform/mentorship/Mentor.java | 13 ++++--- .../domain/platform/mentorship/MentorDto.java | 5 ++- .../platform/service/MentorshipService.java | 14 +++++++- .../platform/domain/platform/MentorTest.java | 16 ++++----- .../factories/SetupMentorFactories.java | 15 ++++++-- .../factories/SetupUserAccountFactories.java | 9 +++-- .../service/MentorshipServiceTest.java | 34 +++++++++---------- 7 files changed, 66 insertions(+), 40 deletions(-) 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 a1c5b4864..ee58e8e54 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 @@ -12,8 +12,8 @@ import com.wcc.platform.domain.platform.type.MemberType; import com.wcc.platform.domain.resource.MentorResource; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; -import java.util.Collections; import java.util.List; import lombok.Builder; import lombok.EqualsAndHashCode; @@ -22,12 +22,14 @@ import lombok.ToString; import org.apache.commons.lang3.StringUtils; import org.springframework.util.CollectionUtils; +import org.springframework.validation.annotation.Validated; /** Represents the mentor members of the community. */ @Getter @EqualsAndHashCode(callSuper = true) @ToString @NoArgsConstructor +@Validated @SuppressWarnings("PMD.ImmutableField") public class Mentor extends Member { @@ -36,6 +38,7 @@ public class Mentor extends Member { private List spokenLanguages; @NotBlank private String bio; @NotNull private MenteeSection menteeSection; + @NotEmpty private List memberTypes; private FeedbackSection feedbackSection; private MentorResource resources; private String calendlyLink; @@ -68,7 +71,8 @@ public Mentor( final Boolean isWomen, final String calendlyLink, final Boolean acceptMale, - final Boolean acceptPromotion) { + final Boolean acceptPromotion, + final List memberTypes) { super( id, fullName, @@ -78,7 +82,7 @@ public Mentor( country, city, companyName, - Collections.singletonList(MemberType.MENTOR), + memberTypes, images, network, pronouns, @@ -95,9 +99,10 @@ public Mentor( this.calendlyLink = calendlyLink; this.acceptMale = acceptMale; this.acceptPromotion = acceptPromotion; + this.memberTypes = memberTypes; } - /** Checks for empty or null and returns capitalized list of string. */ + /** Checks for empty or null and returns a capitalized list of string. */ private static List normalizeLanguages(final List languages) { if (CollectionUtils.isEmpty(languages)) { return List.of(); 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 c134b025e..c5f5129c9 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 @@ -10,6 +10,7 @@ import com.wcc.platform.domain.platform.SocialNetwork; import com.wcc.platform.domain.platform.member.MemberDto; import com.wcc.platform.domain.platform.member.ProfileStatus; +import com.wcc.platform.domain.platform.type.MemberType; import com.wcc.platform.domain.resource.MentorResource; import io.micrometer.common.util.StringUtils; import jakarta.validation.constraints.Email; @@ -23,12 +24,14 @@ import lombok.NoArgsConstructor; import lombok.ToString; import org.springframework.util.CollectionUtils; +import org.springframework.validation.annotation.Validated; /** Represents the mentor members of the community. */ @Getter @EqualsAndHashCode(callSuper = true) @ToString @NoArgsConstructor +@Validated @SuppressWarnings("PMD.ImmutableField") public class MentorDto extends MemberDto { @@ -88,7 +91,7 @@ public MentorDto( country, city, companyName, - null, // TODO to be fixe this will cleanup member types + List.of(MemberType.MENTOR), images, network, pronouns, diff --git a/src/main/java/com/wcc/platform/service/MentorshipService.java b/src/main/java/com/wcc/platform/service/MentorshipService.java index 141e236f9..b26368d50 100644 --- a/src/main/java/com/wcc/platform/service/MentorshipService.java +++ b/src/main/java/com/wcc/platform/service/MentorshipService.java @@ -7,11 +7,13 @@ import com.wcc.platform.domain.exceptions.DuplicatedMemberException; import com.wcc.platform.domain.exceptions.MemberNotFoundException; import com.wcc.platform.domain.exceptions.MentorStatusException; +import com.wcc.platform.domain.platform.member.Member; import com.wcc.platform.domain.platform.member.ProfileStatus; import com.wcc.platform.domain.platform.mentorship.CycleStatus; import com.wcc.platform.domain.platform.mentorship.Mentor; import com.wcc.platform.domain.platform.mentorship.MentorDto; import com.wcc.platform.domain.platform.mentorship.MentorshipCycleEntity; +import com.wcc.platform.domain.platform.type.MemberType; import com.wcc.platform.domain.platform.type.RoleType; import com.wcc.platform.domain.resource.MemberProfilePicture; import com.wcc.platform.domain.resource.Resource; @@ -20,6 +22,7 @@ import com.wcc.platform.repository.MentorRepository; import com.wcc.platform.repository.MentorshipCycleRepository; import com.wcc.platform.utils.FiltersUtil; +import java.util.ArrayList; import java.util.List; import java.util.Optional; import lombok.AllArgsConstructor; @@ -59,8 +62,15 @@ public class MentorshipService { public Mentor create(final Mentor mentor) { final var existingMember = memberRepository.findByEmail(mentor.getEmail()); + final var memberTypeMentor = MemberType.MENTOR; + mentor.setMemberTypes(List.of(memberTypeMentor)); + if (existingMember.isPresent()) { - final var existingMemberId = existingMember.get().getId(); + final Member member = existingMember.get(); + final var memberTypes = new ArrayList<>(member.getMemberTypes()); + memberTypes.add(memberTypeMentor); + + final var existingMemberId = member.getId(); final var mentorWithExistingId = Mentor.mentorBuilder() .id(existingMemberId) @@ -70,6 +80,7 @@ public Mentor create(final Mentor mentor) { .slackDisplayName(mentor.getSlackDisplayName()) .country(mentor.getCountry()) .city(mentor.getCity()) + .memberTypes(memberTypes) .companyName(mentor.getCompanyName()) .images(mentor.getImages()) .network(mentor.getNetwork()) @@ -97,6 +108,7 @@ public Mentor create(final Mentor mentor) { throw new DuplicatedMemberException(mentorExists.get().getEmail()); } } + validateMentorCommitment(mentor); final var mentorCreated = mentorRepository.create(mentor); if (mentorRepository.findById(mentorCreated.getId()).isPresent()) { diff --git a/src/test/java/com/wcc/platform/domain/platform/MentorTest.java b/src/test/java/com/wcc/platform/domain/platform/MentorTest.java index 4b9022706..5142182a9 100644 --- a/src/test/java/com/wcc/platform/domain/platform/MentorTest.java +++ b/src/test/java/com/wcc/platform/domain/platform/MentorTest.java @@ -1,6 +1,7 @@ package com.wcc.platform.domain.platform; import static com.wcc.platform.factories.SetupMentorFactories.createMentorTest; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; @@ -41,16 +42,11 @@ void testHashCodeNotEquals() { @Test void testToString() { - final var expected = - "Mentor(profileStatus=PENDING, skills=Skills[yearsExperience=2, areas=[TechnicalAreaProficiency[technicalArea=Backend, proficiencyLevel=Beginner], TechnicalAreaProficiency[technicalArea=Frontend, proficiencyLevel=Beginner]], " - + "languages=[LanguageProficiency[language=Javascript, proficiencyLevel=Beginner]], mentorshipFocus=[Grow from beginner to mid-level]], " - + "spokenLanguages=[English, Spanish, German], bio=Mentor bio, " - + "menteeSection=MenteeSection[idealMentee=ideal mentee description, " - + "additional=additional, longTerm=LongTermMentorship[numMentee=1, hours=4], " - + "adHoc=[MentorMonthAvailability[month=APRIL, hours=2]]], " - + "feedbackSection=null, resources=null, calendlyLink=null, " - + "acceptMale=null, acceptPromotion=null)"; - assertEquals(expected, mentor.toString()); + final var result = mentor.toString(); + assertThat(result).contains("profileStatus=PENDING"); + assertThat(result).contains("bio=Mentor bio"); + assertThat(result).contains("spokenLanguages=[English, Spanish, German]"); + assertThat(result).contains("menteeSection=MenteeSection"); } @Test diff --git a/src/test/java/com/wcc/platform/factories/SetupMentorFactories.java b/src/test/java/com/wcc/platform/factories/SetupMentorFactories.java index 997c9b755..12fa05483 100644 --- a/src/test/java/com/wcc/platform/factories/SetupMentorFactories.java +++ b/src/test/java/com/wcc/platform/factories/SetupMentorFactories.java @@ -52,7 +52,9 @@ public static Mentor createMentorTest( .email(email) .slackDisplayName(member.getSlackDisplayName()) .country(member.getCountry()) + .city("City") .images(member.getImages()) + .memberTypes(member.getMemberTypes()) .pronouns(null) .pronounCategory(null) .profileStatus(ProfileStatus.PENDING) @@ -67,7 +69,8 @@ public static Mentor createMentorTest( new TechnicalAreaProficiency( TechnicalArea.FRONTEND, ProficiencyLevel.BEGINNER)), List.of( - new LanguageProficiency(CodeLanguage.JAVASCRIPT, ProficiencyLevel.BEGINNER)), + new LanguageProficiency( + CodeLanguage.JAVASCRIPT, ProficiencyLevel.BEGINNER)), List.of(MentorshipFocusArea.GROW_BEGINNER_TO_MID))) .menteeSection( new MenteeSection( @@ -110,7 +113,8 @@ public static MentorDto createMentorDtoTest(final Long mentorId, final MemberTyp new TechnicalAreaProficiency(TechnicalArea.BACKEND, ProficiencyLevel.BEGINNER), new TechnicalAreaProficiency( TechnicalArea.FRONTEND, ProficiencyLevel.BEGINNER)), - List.of(new LanguageProficiency(CodeLanguage.JAVASCRIPT, ProficiencyLevel.BEGINNER)), + List.of( + new LanguageProficiency(CodeLanguage.JAVASCRIPT, ProficiencyLevel.BEGINNER)), List.of(MentorshipFocusArea.GROW_BEGINNER_TO_MID))) .menteeSection( new MenteeSection( @@ -148,7 +152,8 @@ public static MentorDto createMentorDtoTest( new TechnicalAreaProficiency(TechnicalArea.BACKEND, ProficiencyLevel.BEGINNER), new TechnicalAreaProficiency( TechnicalArea.FRONTEND, ProficiencyLevel.BEGINNER)), - List.of(new LanguageProficiency(CodeLanguage.JAVASCRIPT, ProficiencyLevel.BEGINNER)), + List.of( + new LanguageProficiency(CodeLanguage.JAVASCRIPT, ProficiencyLevel.BEGINNER)), List.of(MentorshipFocusArea.GROW_BEGINNER_TO_MID))) .menteeSection( new MenteeSection( @@ -166,7 +171,9 @@ public static Mentor createUpdatedMentorTest(final Mentor mentor, final MentorDt .email(mentorDto.getEmail()) .slackDisplayName(mentorDto.getSlackDisplayName()) .country(mentorDto.getCountry()) + .city(mentorDto.getCity()) .images(mentorDto.getImages()) + .memberTypes(List.of(MemberType.MENTOR)) .pronouns(null) .pronounCategory(null) .profileStatus(mentorDto.getProfileStatus()) @@ -204,7 +211,9 @@ public static Mentor createUpdatedMentorTest( .email(mentorDto.getEmail()) .slackDisplayName(mentorDto.getSlackDisplayName()) .country(mentorDto.getCountry()) + .city(mentorDto.getCity()) .images(mentorDto.getImages()) + .memberTypes(List.of(MemberType.MENTOR)) .pronouns(null) .pronounCategory(null) .profileStatus(mentorDto.getProfileStatus()) diff --git a/src/test/java/com/wcc/platform/factories/SetupUserAccountFactories.java b/src/test/java/com/wcc/platform/factories/SetupUserAccountFactories.java index dbfbabda5..3fe6e2807 100644 --- a/src/test/java/com/wcc/platform/factories/SetupUserAccountFactories.java +++ b/src/test/java/com/wcc/platform/factories/SetupUserAccountFactories.java @@ -3,8 +3,10 @@ import com.wcc.platform.domain.auth.MemberTypeRoleMapper; import com.wcc.platform.domain.auth.UserAccount; import com.wcc.platform.domain.platform.member.Member; +import com.wcc.platform.domain.platform.type.MemberType; import com.wcc.platform.domain.platform.type.RoleType; import java.util.List; +import java.util.Objects; public class SetupUserAccountFactories { @@ -22,8 +24,9 @@ public static UserAccount createUserAccountTest(final RoleType... roles) { } private static List getRolesForMember(final Member member) { - return member.getMemberTypes().stream() - .map(MemberTypeRoleMapper::getRoleForMemberType) - .toList(); + List memberTypes = member.getMemberTypes(); + Objects.requireNonNull(memberTypes); + + return memberTypes.stream().map(MemberTypeRoleMapper::getRoleForMemberType).toList(); } } diff --git a/src/test/java/com/wcc/platform/service/MentorshipServiceTest.java b/src/test/java/com/wcc/platform/service/MentorshipServiceTest.java index 00c819bc5..6c31e827e 100644 --- a/src/test/java/com/wcc/platform/service/MentorshipServiceTest.java +++ b/src/test/java/com/wcc/platform/service/MentorshipServiceTest.java @@ -3,7 +3,6 @@ import static com.wcc.platform.factories.SetupMentorFactories.createMentorDtoTest; import static com.wcc.platform.factories.SetupMentorFactories.createMentorTest; import static com.wcc.platform.factories.SetupMentorFactories.createUpdatedMentorTest; -import static com.wcc.platform.factories.SetupUserAccountFactories.createUserAccountTest; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -13,11 +12,9 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import com.wcc.platform.domain.auth.UserAccount; import com.wcc.platform.domain.cms.attributes.PronounCategory; import com.wcc.platform.domain.cms.pages.mentorship.LongTermMentorship; import com.wcc.platform.domain.cms.pages.mentorship.MenteeSection; @@ -66,7 +63,6 @@ class MentorshipServiceTest { private Mentor mentor; private Mentor updatedMentor; private MentorDto mentorDto; - private UserAccount userAccount; private MentorshipService service; public MentorshipServiceTest() { @@ -79,17 +75,15 @@ void setUp() { mentor = createMentorTest(); mentorDto = createMentorDtoTest(1L, MemberType.DIRECTOR); updatedMentor = createUpdatedMentorTest(mentor, mentorDto); - userAccount = createUserAccountTest(mentor); service = - spy( - new MentorshipService( - mentorRepository, - memberRepository, - cycleRepository, - userProvisionService, - profilePicRepo, - notificationService, - resourceService)); + new MentorshipService( + mentorRepository, + memberRepository, + cycleRepository, + userProvisionService, + profilePicRepo, + notificationService, + resourceService); } @Test @@ -230,7 +224,12 @@ void shouldPreserveAllFieldsWhenMentorReRegistersWithExistingEmail() { when(mentor.getAcceptMale()).thenReturn(true); when(mentor.getAcceptPromotion()).thenReturn(false); - Member existingMember = Member.builder().id(999L).email("existing@test.com").build(); + Member existingMember = + Member.builder() + .id(999L) + .email("existing@test.com") + .memberTypes(List.of(MemberType.MEMBER)) + .build(); when(memberRepository.findByEmail("existing@test.com")).thenReturn(Optional.of(existingMember)); when(mentorRepository.create(any(Mentor.class))).thenReturn(mock(Mentor.class)); @@ -475,7 +474,7 @@ void shouldReturnOnlyActiveMentorsWhenCycleIsOpen() { @Test @DisplayName( "Given active and pending mentors with a closed cycle, " - + "when getAllActiveMentors is called, then exception is throws for closed cycle") + + "when getAllActiveMentors is called, then only active mentors are returned") void shouldReturnOnlyActiveMentorsWhenCycleIsClosed() { var activeMentor = mock(Mentor.class); var pendingMentor = mock(Mentor.class); @@ -487,8 +486,7 @@ void shouldReturnOnlyActiveMentorsWhenCycleIsClosed() { when(activeMentorDto.getId()).thenReturn(1L); when(mentorRepository.getAll()).thenReturn(List.of(activeMentor, pendingMentor)); when(profilePicRepo.findByMemberId(1L)).thenReturn(Optional.empty()); - when(service.getCurrentCycle()) - .thenReturn(MentorshipCycleEntity.builder().status(CycleStatus.CLOSED).build()); + when(cycleRepository.findOpenCycle()).thenReturn(Optional.empty()); List result = service.getAllActiveMentors(); From 71d47adea93471afc385ad4c21fada9e8232c1d7 Mon Sep 17 00:00:00 2001 From: Adriana Zencke Zimmermann Date: Thu, 7 May 2026 06:59:48 +0200 Subject: [PATCH 04/11] refactor: improve Mentee and Skills validation messages Mentee and Skills validation constraints used @NotNull for list fields where an empty list is equally invalid. This replaces them with @NotEmpty, adds @Validated, and provides context-specific messages to give API consumers clearer feedback when registration input is missing required fields like spoken languages or mentorship focus areas. --- .../platform/domain/platform/mentorship/Mentee.java | 12 +++++++++++- .../platform/domain/platform/mentorship/Skills.java | 13 +++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) 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 862cb43c5..e0fd266af 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 @@ -9,6 +9,7 @@ import com.wcc.platform.domain.platform.type.MemberType; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import java.util.Collections; import java.util.List; @@ -18,22 +19,31 @@ import lombok.NoArgsConstructor; import lombok.ToString; import org.apache.commons.lang3.StringUtils; +import org.springframework.validation.annotation.Validated; +/** Represents the mentee members of the community. */ @Getter @NoArgsConstructor @ToString(callSuper = true) +@Validated @SuppressWarnings({"PMD.ExcessiveParameterList", "PMD.ImmutableField"}) public class Mentee extends Member { private ProfileStatus profileStatus; - private @NotNull Skills skills; + + @NotNull(message = "Skills must be provided") + private Skills skills; + private @NotBlank String bio; + + @NotEmpty(message = "At least one spoken language must be provided") private List spokenLanguages; @NotNull @Min(1) private Integer availableHsMonth; + /** Mentee Builder. */ @Builder(builderMethodName = "menteeBuilder") public Mentee( final Long id, diff --git a/src/main/java/com/wcc/platform/domain/platform/mentorship/Skills.java b/src/main/java/com/wcc/platform/domain/platform/mentorship/Skills.java index dc7383d7f..be903b6e9 100644 --- a/src/main/java/com/wcc/platform/domain/platform/mentorship/Skills.java +++ b/src/main/java/com/wcc/platform/domain/platform/mentorship/Skills.java @@ -1,8 +1,10 @@ package com.wcc.platform.domain.platform.mentorship; import com.wcc.platform.domain.cms.attributes.MentorshipFocusArea; +import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import java.util.List; +import org.springframework.validation.annotation.Validated; /** * Skills of the mentor. @@ -12,8 +14,11 @@ * @param languages programming languages with proficiency levels * @param mentorshipFocus mentorship focus areas like Grow from beginner to mid-level */ +@Validated public record Skills( - @NotNull Integer yearsExperience, - @NotNull List areas, - @NotNull List languages, - @NotNull List mentorshipFocus) {} + @NotNull(message = "Years of experience is required. It can be `0`") Integer yearsExperience, + @NotEmpty(message = "At least one technical area must be provided") + List areas, + List languages, + @NotEmpty(message = "At least one mentorship focus area must be provided") + List mentorshipFocus) {} From 6cd3a4b76123cd2b29a3cd238e54a730c74444ae Mon Sep 17 00:00:00 2001 From: Adriana Zencke Zimmermann Date: Thu, 7 May 2026 06:59:58 +0200 Subject: [PATCH 05/11] fix: restore builder defaults for EmailRequest and add @Validated Lombok's @Builder strips field initializers by default, so EmailRequest lost its default values for recipients (empty list) and html (true) when constructed via the builder API. @Builder.Default restores the intended defaults. PaginationUtil gains @Validated to enable method- level constraint checking on its parameters. --- src/main/java/com/wcc/platform/domain/email/EmailRequest.java | 2 ++ src/main/java/com/wcc/platform/utils/PaginationUtil.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/main/java/com/wcc/platform/domain/email/EmailRequest.java b/src/main/java/com/wcc/platform/domain/email/EmailRequest.java index 0abf62fa6..5109dbece 100644 --- a/src/main/java/com/wcc/platform/domain/email/EmailRequest.java +++ b/src/main/java/com/wcc/platform/domain/email/EmailRequest.java @@ -23,6 +23,7 @@ public class EmailRequest { @Schema(description = "BCC recipients", example = "[\"bcc@example.com\"]") @NotEmpty(message = "Recipient emails are required") + @Builder.Default private List<@jakarta.validation.constraints.Email(message = "Invalid email format") String> recipients = List.of(); @@ -41,6 +42,7 @@ public class EmailRequest { description = "Whether the email body contains HTML content", example = "false", defaultValue = "true") + @Builder.Default private boolean html = true; @Schema(description = "Reply-to email address", example = "noreply@womencodingcommunity.com") diff --git a/src/main/java/com/wcc/platform/utils/PaginationUtil.java b/src/main/java/com/wcc/platform/utils/PaginationUtil.java index 743e62965..ba9b73b26 100644 --- a/src/main/java/com/wcc/platform/utils/PaginationUtil.java +++ b/src/main/java/com/wcc/platform/utils/PaginationUtil.java @@ -4,6 +4,7 @@ import jakarta.validation.constraints.NotEmpty; import java.util.List; import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; /** Util class for pagination data for any page. */ @Slf4j @@ -19,6 +20,7 @@ private PaginationUtil() {} * @param pageSize items per page * @return list of items on the current page */ + @Validated public static List getPaginatedResult( @NotEmpty(message = "Items list cannot be null or empty.") final List items, final int currentPage, From ada897675049fdbc763910ca1967b20acc94e0a1 Mon Sep 17 00:00:00 2001 From: Adriana Zencke Zimmermann Date: Sat, 9 May 2026 17:26:34 +0200 Subject: [PATCH 06/11] test: Fix integration tests --- ...orshipPagesControllerRestTemplateIntegrationTest.java | 2 ++ .../mentorship/MentorshipServiceIntegrationTest.java | 9 ++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/testInt/java/com/wcc/platform/controller/mentorship/MentorshipPagesControllerRestTemplateIntegrationTest.java b/src/testInt/java/com/wcc/platform/controller/mentorship/MentorshipPagesControllerRestTemplateIntegrationTest.java index 8afcffe33..0ac40e9bb 100644 --- a/src/testInt/java/com/wcc/platform/controller/mentorship/MentorshipPagesControllerRestTemplateIntegrationTest.java +++ b/src/testInt/java/com/wcc/platform/controller/mentorship/MentorshipPagesControllerRestTemplateIntegrationTest.java @@ -78,6 +78,7 @@ private void seedMentors() { .email("matchedMentor.berlin@wcc.com") .slackDisplayName(base.getSlackDisplayName()) .country(base.getCountry()) + .memberTypes(List.of(MemberType.MENTOR)) .city("Berlin") .companyName("Acme") .images(base.getImages()) @@ -113,6 +114,7 @@ private void seedMentors() { .slackDisplayName(base.getSlackDisplayName()) .country(base.getCountry()) .city("Paris") + .memberTypes(List.of(MemberType.MENTOR)) .companyName("Globex") .images(base.getImages()) .network(base.getNetwork()) diff --git a/src/testInt/java/com/wcc/platform/service/mentorship/MentorshipServiceIntegrationTest.java b/src/testInt/java/com/wcc/platform/service/mentorship/MentorshipServiceIntegrationTest.java index 8a7831b70..12cc33d5b 100644 --- a/src/testInt/java/com/wcc/platform/service/mentorship/MentorshipServiceIntegrationTest.java +++ b/src/testInt/java/com/wcc/platform/service/mentorship/MentorshipServiceIntegrationTest.java @@ -11,6 +11,7 @@ import com.wcc.platform.domain.platform.member.Member; import com.wcc.platform.domain.platform.member.ProfileStatus; import com.wcc.platform.domain.platform.mentorship.Mentor; +import com.wcc.platform.domain.platform.type.MemberType; import com.wcc.platform.domain.platform.type.ResourceType; import com.wcc.platform.domain.resource.MemberProfilePicture; import com.wcc.platform.repository.MemberProfilePictureRepository; @@ -21,6 +22,7 @@ import com.wcc.platform.repository.postgres.DefaultDatabaseSetup; import com.wcc.platform.service.MentorshipService; import com.wcc.platform.service.PageService; +import java.util.List; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -168,9 +170,9 @@ void shouldUseExistingMemberWhenMentorEmailAlreadyExists() { .country(new com.wcc.platform.domain.cms.attributes.Country("US", "United States")) .city("New York") .companyName("Tech Corp") - .memberTypes(java.util.List.of(com.wcc.platform.domain.platform.type.MemberType.MEMBER)) - .images(java.util.List.of()) - .network(java.util.List.of()) + .memberTypes(List.of(MemberType.MEMBER)) + .images(List.of()) + .network(List.of()) .build(); final Member savedMember = memberRepository.create(existingMember); @@ -211,6 +213,7 @@ void shouldReturnMentorsWithPronounsFromDatabase() { .city(baseMentor.getCity()) .companyName(baseMentor.getCompanyName()) .images(baseMentor.getImages()) + .memberTypes(List.of(MemberType.MENTOR)) .network(baseMentor.getNetwork()) .pronouns("they/them") .pronounCategory(com.wcc.platform.domain.cms.attributes.PronounCategory.NEUTRAL) From fac334fc5f60534b46fd841f4534d0043f96b57e Mon Sep 17 00:00:00 2001 From: Adriana Zencke Zimmermann Date: Sat, 9 May 2026 18:01:16 +0200 Subject: [PATCH 07/11] refactor: return MentorDto instead of Mentor in MentorshipService and update related tests Standardize API responses and improve consistency by switching MentorshipService methods to return MentorDto, updating tests and controller logic accordingly. --- .../com/wcc/platform/controller/MentorController.java | 2 +- .../com/wcc/platform/service/MentorshipService.java | 6 +++--- .../wcc/platform/controller/MentorControllerTest.java | 9 +++++---- .../wcc/platform/service/MentorshipServiceTest.java | 6 +++--- .../mentorship/MentorshipServiceIntegrationTest.java | 10 ++++++---- 5 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/wcc/platform/controller/MentorController.java b/src/main/java/com/wcc/platform/controller/MentorController.java index cc2c1c607..a00b688b3 100644 --- a/src/main/java/com/wcc/platform/controller/MentorController.java +++ b/src/main/java/com/wcc/platform/controller/MentorController.java @@ -68,7 +68,7 @@ public ResponseEntity> getAllMentors() { @PostMapping("/mentors") @Operation(summary = "API to submit mentor registration") @ResponseStatus(HttpStatus.CREATED) - public ResponseEntity createMentor(@Valid @RequestBody final MentorDto mentorDto) { + public ResponseEntity createMentor(@Valid @RequestBody final MentorDto mentorDto) { return new ResponseEntity<>(mentorshipService.create(mentorDto.toMentor()), HttpStatus.CREATED); } diff --git a/src/main/java/com/wcc/platform/service/MentorshipService.java b/src/main/java/com/wcc/platform/service/MentorshipService.java index 5504d17b3..2ae136def 100644 --- a/src/main/java/com/wcc/platform/service/MentorshipService.java +++ b/src/main/java/com/wcc/platform/service/MentorshipService.java @@ -59,7 +59,7 @@ public class MentorshipService { * * @return Mentor record created successfully. */ - public Mentor create(final Mentor mentor) { + public MentorDto create(final Mentor mentor) { final var existingMember = memberRepository.findByEmail(mentor.getEmail()); final var memberTypeMentor = MemberType.MENTOR; @@ -99,7 +99,7 @@ public Mentor create(final Mentor mentor) { .acceptPromotion(mentor.getAcceptPromotion()) .build(); - return mentorRepository.create(mentorWithExistingId); + return mentorRepository.create(mentorWithExistingId).toDto(); } if (mentor.getId() != null) { @@ -115,7 +115,7 @@ public Mentor create(final Mentor mentor) { userProvisionService.provisionUserRole( mentorCreated.getId(), mentorCreated.getEmail(), RoleType.MENTOR); } - return enrichMentorWithProfilePicture(mentorCreated); + return enrichMentorWithProfilePicture(mentorCreated).toDto(); } /** diff --git a/src/test/java/com/wcc/platform/controller/MentorControllerTest.java b/src/test/java/com/wcc/platform/controller/MentorControllerTest.java index b5b6fd7f0..f4754f8cd 100644 --- a/src/test/java/com/wcc/platform/controller/MentorControllerTest.java +++ b/src/test/java/com/wcc/platform/controller/MentorControllerTest.java @@ -71,7 +71,7 @@ void shouldGetAllMentorsAndReturnOk() throws Exception { void shouldCreateMentorAndReturnCreated() throws Exception { var mentorRequestBody = createMentorDtoTest(1L, MemberType.MENTOR); var returnedMentor = createMentorTest("Jane"); - when(mentorshipService.create(any(Mentor.class))).thenReturn(returnedMentor); + when(mentorshipService.create(any(Mentor.class))).thenReturn(returnedMentor.toDto()); mockMvc .perform(postRequest(API_MENTORS, mentorRequestBody)) @@ -354,7 +354,7 @@ void shouldReturnIsWomenFieldWhenCreatingMentor() throws Exception { .isWomen(true) .build(); - when(mentorshipService.create(any(Mentor.class))).thenReturn(mentor); + when(mentorshipService.create(any(Mentor.class))).thenReturn(mentor.toDto()); mockMvc .perform(postRequest(API_MENTORS, mentor)) @@ -425,12 +425,13 @@ void shouldRequireAdminLeaderOrMentorshipAdminRoleOnGetAllMentors() throws NoSuc } @Test - @DisplayName("Given POST request with id in body, when creating mentor, then id in body is ignored") + @DisplayName( + "Given POST request with id in body, when creating mentor, then id in body is ignored") void shouldIgnoreIdInRequestBodyWhenCreatingMentor() throws Exception { var requestWithId = createMentorDtoTest(999L, MemberType.MENTOR); var savedMentor = createMentorTest("Jane"); - when(mentorshipService.create(any())).thenReturn(savedMentor); + when(mentorshipService.create(any())).thenReturn(savedMentor.toDto()); mockMvc .perform(postRequest(API_MENTORS, requestWithId)) diff --git a/src/test/java/com/wcc/platform/service/MentorshipServiceTest.java b/src/test/java/com/wcc/platform/service/MentorshipServiceTest.java index 6c31e827e..2e8336060 100644 --- a/src/test/java/com/wcc/platform/service/MentorshipServiceTest.java +++ b/src/test/java/com/wcc/platform/service/MentorshipServiceTest.java @@ -133,7 +133,7 @@ void testCreateAvailableLongTermMentor() { var result = service.create(mentor); - assertEquals(mentor, result); + assertEquals(mentor.toDto(), result); verify(mentorRepository).create(mentor); } @@ -151,7 +151,7 @@ void testCreateAdHocOnlyMentor() { var result = service.create(mentor); - assertEquals(mentor, result); + assertEquals(mentor.toDto(), result); verify(mentorRepository).create(mentor); } @@ -190,7 +190,7 @@ void testCreateLongTermMentorWithMinimumHours() { var result = service.create(mentor); - assertEquals(mentor, result); + assertEquals(mentor.toDto(), result); verify(mentorRepository).create(mentor); } diff --git a/src/testInt/java/com/wcc/platform/service/mentorship/MentorshipServiceIntegrationTest.java b/src/testInt/java/com/wcc/platform/service/mentorship/MentorshipServiceIntegrationTest.java index 12cc33d5b..e047e0dfa 100644 --- a/src/testInt/java/com/wcc/platform/service/mentorship/MentorshipServiceIntegrationTest.java +++ b/src/testInt/java/com/wcc/platform/service/mentorship/MentorshipServiceIntegrationTest.java @@ -11,6 +11,7 @@ import com.wcc.platform.domain.platform.member.Member; import com.wcc.platform.domain.platform.member.ProfileStatus; import com.wcc.platform.domain.platform.mentorship.Mentor; +import com.wcc.platform.domain.platform.mentorship.MentorDto; import com.wcc.platform.domain.platform.type.MemberType; import com.wcc.platform.domain.platform.type.ResourceType; import com.wcc.platform.domain.resource.MemberProfilePicture; @@ -49,6 +50,7 @@ class MentorshipServiceIntegrationTest extends DefaultDatabaseSetup { @Autowired private JdbcTemplate jdbcTemplate; private Mentor setupMentor; + private MentorDto mentorDTO; @BeforeEach void setUp() { @@ -58,13 +60,13 @@ void setUp() { cleanupMentor(setupMentor); pageRepository.deleteById(MENTORS.getId()); pageService.create(MENTORS, page); - setupMentor = service.create(setupMentor); - repository.updateProfileStatus(setupMentor.getId(), ProfileStatus.ACTIVE); + mentorDTO = service.create(setupMentor); + repository.updateProfileStatus(mentorDTO.getId(), ProfileStatus.ACTIVE); } @AfterEach void tearDown() { - if (setupMentor != null) { + if (setupMentor != null || mentorDTO != null) { cleanupMentor(setupMentor); } } @@ -182,7 +184,7 @@ void shouldUseExistingMemberWhenMentorEmailAlreadyExists() { null, "Mentor From Existing Member", "existing-mentor-member@test.com"); // Should successfully create mentor using existing member's ID - final Mentor savedMentor = service.create(mentor); + final var savedMentor = service.create(mentor); assertThat(savedMentor).isNotNull(); assertThat(savedMentor.getId()).isEqualTo(savedMember.getId()); From 24fecb145639ddd39ed2c1a9cdf6adc3170bd9a0 Mon Sep 17 00:00:00 2001 From: Adriana Zencke Zimmermann Date: Sat, 9 May 2026 18:01:16 +0200 Subject: [PATCH 08/11] test: adjust test --- .../com/wcc/platform/controller/MentorController.java | 2 +- .../com/wcc/platform/service/MentorshipService.java | 6 +++--- .../wcc/platform/controller/MentorControllerTest.java | 9 +++++---- .../wcc/platform/service/MentorshipServiceTest.java | 6 +++--- .../PostgresMemberRepositoryIntegrationTest.java | 2 +- .../mentorship/MentorshipServiceIntegrationTest.java | 10 ++++++---- 6 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/wcc/platform/controller/MentorController.java b/src/main/java/com/wcc/platform/controller/MentorController.java index cc2c1c607..a00b688b3 100644 --- a/src/main/java/com/wcc/platform/controller/MentorController.java +++ b/src/main/java/com/wcc/platform/controller/MentorController.java @@ -68,7 +68,7 @@ public ResponseEntity> getAllMentors() { @PostMapping("/mentors") @Operation(summary = "API to submit mentor registration") @ResponseStatus(HttpStatus.CREATED) - public ResponseEntity createMentor(@Valid @RequestBody final MentorDto mentorDto) { + public ResponseEntity createMentor(@Valid @RequestBody final MentorDto mentorDto) { return new ResponseEntity<>(mentorshipService.create(mentorDto.toMentor()), HttpStatus.CREATED); } diff --git a/src/main/java/com/wcc/platform/service/MentorshipService.java b/src/main/java/com/wcc/platform/service/MentorshipService.java index 5504d17b3..2ae136def 100644 --- a/src/main/java/com/wcc/platform/service/MentorshipService.java +++ b/src/main/java/com/wcc/platform/service/MentorshipService.java @@ -59,7 +59,7 @@ public class MentorshipService { * * @return Mentor record created successfully. */ - public Mentor create(final Mentor mentor) { + public MentorDto create(final Mentor mentor) { final var existingMember = memberRepository.findByEmail(mentor.getEmail()); final var memberTypeMentor = MemberType.MENTOR; @@ -99,7 +99,7 @@ public Mentor create(final Mentor mentor) { .acceptPromotion(mentor.getAcceptPromotion()) .build(); - return mentorRepository.create(mentorWithExistingId); + return mentorRepository.create(mentorWithExistingId).toDto(); } if (mentor.getId() != null) { @@ -115,7 +115,7 @@ public Mentor create(final Mentor mentor) { userProvisionService.provisionUserRole( mentorCreated.getId(), mentorCreated.getEmail(), RoleType.MENTOR); } - return enrichMentorWithProfilePicture(mentorCreated); + return enrichMentorWithProfilePicture(mentorCreated).toDto(); } /** diff --git a/src/test/java/com/wcc/platform/controller/MentorControllerTest.java b/src/test/java/com/wcc/platform/controller/MentorControllerTest.java index b5b6fd7f0..f4754f8cd 100644 --- a/src/test/java/com/wcc/platform/controller/MentorControllerTest.java +++ b/src/test/java/com/wcc/platform/controller/MentorControllerTest.java @@ -71,7 +71,7 @@ void shouldGetAllMentorsAndReturnOk() throws Exception { void shouldCreateMentorAndReturnCreated() throws Exception { var mentorRequestBody = createMentorDtoTest(1L, MemberType.MENTOR); var returnedMentor = createMentorTest("Jane"); - when(mentorshipService.create(any(Mentor.class))).thenReturn(returnedMentor); + when(mentorshipService.create(any(Mentor.class))).thenReturn(returnedMentor.toDto()); mockMvc .perform(postRequest(API_MENTORS, mentorRequestBody)) @@ -354,7 +354,7 @@ void shouldReturnIsWomenFieldWhenCreatingMentor() throws Exception { .isWomen(true) .build(); - when(mentorshipService.create(any(Mentor.class))).thenReturn(mentor); + when(mentorshipService.create(any(Mentor.class))).thenReturn(mentor.toDto()); mockMvc .perform(postRequest(API_MENTORS, mentor)) @@ -425,12 +425,13 @@ void shouldRequireAdminLeaderOrMentorshipAdminRoleOnGetAllMentors() throws NoSuc } @Test - @DisplayName("Given POST request with id in body, when creating mentor, then id in body is ignored") + @DisplayName( + "Given POST request with id in body, when creating mentor, then id in body is ignored") void shouldIgnoreIdInRequestBodyWhenCreatingMentor() throws Exception { var requestWithId = createMentorDtoTest(999L, MemberType.MENTOR); var savedMentor = createMentorTest("Jane"); - when(mentorshipService.create(any())).thenReturn(savedMentor); + when(mentorshipService.create(any())).thenReturn(savedMentor.toDto()); mockMvc .perform(postRequest(API_MENTORS, requestWithId)) diff --git a/src/test/java/com/wcc/platform/service/MentorshipServiceTest.java b/src/test/java/com/wcc/platform/service/MentorshipServiceTest.java index 6c31e827e..2e8336060 100644 --- a/src/test/java/com/wcc/platform/service/MentorshipServiceTest.java +++ b/src/test/java/com/wcc/platform/service/MentorshipServiceTest.java @@ -133,7 +133,7 @@ void testCreateAvailableLongTermMentor() { var result = service.create(mentor); - assertEquals(mentor, result); + assertEquals(mentor.toDto(), result); verify(mentorRepository).create(mentor); } @@ -151,7 +151,7 @@ void testCreateAdHocOnlyMentor() { var result = service.create(mentor); - assertEquals(mentor, result); + assertEquals(mentor.toDto(), result); verify(mentorRepository).create(mentor); } @@ -190,7 +190,7 @@ void testCreateLongTermMentorWithMinimumHours() { var result = service.create(mentor); - assertEquals(mentor, result); + assertEquals(mentor.toDto(), result); verify(mentorRepository).create(mentor); } diff --git a/src/testInt/java/com/wcc/platform/repository/postgres/PostgresMemberRepositoryIntegrationTest.java b/src/testInt/java/com/wcc/platform/repository/postgres/PostgresMemberRepositoryIntegrationTest.java index 95a46adf7..5a79ff102 100644 --- a/src/testInt/java/com/wcc/platform/repository/postgres/PostgresMemberRepositoryIntegrationTest.java +++ b/src/testInt/java/com/wcc/platform/repository/postgres/PostgresMemberRepositoryIntegrationTest.java @@ -70,7 +70,7 @@ void testCreateAndUpdate() { @Test void createReturnEmptyForNotFoundMemberId() { - var optionalMember = repository.findById(7L); + var optionalMember = repository.findById(42L); assertTrue(optionalMember.isEmpty(), "Should not find optionalMember with this id"); } diff --git a/src/testInt/java/com/wcc/platform/service/mentorship/MentorshipServiceIntegrationTest.java b/src/testInt/java/com/wcc/platform/service/mentorship/MentorshipServiceIntegrationTest.java index 12cc33d5b..e047e0dfa 100644 --- a/src/testInt/java/com/wcc/platform/service/mentorship/MentorshipServiceIntegrationTest.java +++ b/src/testInt/java/com/wcc/platform/service/mentorship/MentorshipServiceIntegrationTest.java @@ -11,6 +11,7 @@ import com.wcc.platform.domain.platform.member.Member; import com.wcc.platform.domain.platform.member.ProfileStatus; import com.wcc.platform.domain.platform.mentorship.Mentor; +import com.wcc.platform.domain.platform.mentorship.MentorDto; import com.wcc.platform.domain.platform.type.MemberType; import com.wcc.platform.domain.platform.type.ResourceType; import com.wcc.platform.domain.resource.MemberProfilePicture; @@ -49,6 +50,7 @@ class MentorshipServiceIntegrationTest extends DefaultDatabaseSetup { @Autowired private JdbcTemplate jdbcTemplate; private Mentor setupMentor; + private MentorDto mentorDTO; @BeforeEach void setUp() { @@ -58,13 +60,13 @@ void setUp() { cleanupMentor(setupMentor); pageRepository.deleteById(MENTORS.getId()); pageService.create(MENTORS, page); - setupMentor = service.create(setupMentor); - repository.updateProfileStatus(setupMentor.getId(), ProfileStatus.ACTIVE); + mentorDTO = service.create(setupMentor); + repository.updateProfileStatus(mentorDTO.getId(), ProfileStatus.ACTIVE); } @AfterEach void tearDown() { - if (setupMentor != null) { + if (setupMentor != null || mentorDTO != null) { cleanupMentor(setupMentor); } } @@ -182,7 +184,7 @@ void shouldUseExistingMemberWhenMentorEmailAlreadyExists() { null, "Mentor From Existing Member", "existing-mentor-member@test.com"); // Should successfully create mentor using existing member's ID - final Mentor savedMentor = service.create(mentor); + final var savedMentor = service.create(mentor); assertThat(savedMentor).isNotNull(); assertThat(savedMentor.getId()).isEqualTo(savedMember.getId()); From 9b9adb6b81ae1e252c218437c673c85764eb70d8 Mon Sep 17 00:00:00 2001 From: Adriana Zencke Zimmermann Date: Mon, 15 Jun 2026 23:02:54 +0200 Subject: [PATCH 09/11] refactor: Simplify mentor creation handling, remove unnecessary DTO conversions, and clean up unused dependencies in tests --- .../com/wcc/platform/controller/MentorController.java | 3 ++- .../com/wcc/platform/service/MentorshipService.java | 11 ++++++----- .../wcc/platform/controller/MentorControllerTest.java | 6 +++--- .../service/MentorshipServiceFilteringTest.java | 4 +--- .../service/MentorshipServiceRetrievalTest.java | 10 +++++----- .../wcc/platform/service/MentorshipServiceTest.java | 10 ++++------ .../mentorship/MentorshipServiceIntegrationTest.java | 7 ++----- 7 files changed, 23 insertions(+), 28 deletions(-) diff --git a/src/main/java/com/wcc/platform/controller/MentorController.java b/src/main/java/com/wcc/platform/controller/MentorController.java index 3ff7ff5e7..1a900ab4b 100644 --- a/src/main/java/com/wcc/platform/controller/MentorController.java +++ b/src/main/java/com/wcc/platform/controller/MentorController.java @@ -69,7 +69,8 @@ public ResponseEntity> getAllMentors() { @Operation(summary = "API to submit mentor registration") @ResponseStatus(HttpStatus.CREATED) public ResponseEntity createMentor(@Valid @RequestBody final MentorDto mentorDto) { - return new ResponseEntity<>(mentorshipService.create(mentorDto.toMentor()), HttpStatus.CREATED); + final var savedMentor = mentorshipService.create(mentorDto.toMentor()); + return new ResponseEntity<>(savedMentor.toDto(), HttpStatus.CREATED); } /** diff --git a/src/main/java/com/wcc/platform/service/MentorshipService.java b/src/main/java/com/wcc/platform/service/MentorshipService.java index 2ae136def..d507416fb 100644 --- a/src/main/java/com/wcc/platform/service/MentorshipService.java +++ b/src/main/java/com/wcc/platform/service/MentorshipService.java @@ -52,14 +52,13 @@ public class MentorshipService { private final UserProvisionService userProvisionService; private final MemberProfilePictureRepository profilePicRepo; @Getter private final MentorshipNotificationService notificationService; - private final ResourceService resourceService; /** * Create a mentor record. * * @return Mentor record created successfully. */ - public MentorDto create(final Mentor mentor) { + public Mentor create(final Mentor mentor) { final var existingMember = memberRepository.findByEmail(mentor.getEmail()); final var memberTypeMentor = MemberType.MENTOR; @@ -68,7 +67,9 @@ public MentorDto create(final Mentor mentor) { if (existingMember.isPresent()) { final Member member = existingMember.get(); final var memberTypes = new ArrayList<>(member.getMemberTypes()); - memberTypes.add(memberTypeMentor); + if (!memberTypes.contains(memberTypeMentor)) { + memberTypes.add(memberTypeMentor); + } final var existingMemberId = member.getId(); final var mentorWithExistingId = @@ -99,7 +100,7 @@ public MentorDto create(final Mentor mentor) { .acceptPromotion(mentor.getAcceptPromotion()) .build(); - return mentorRepository.create(mentorWithExistingId).toDto(); + return mentorRepository.create(mentorWithExistingId); } if (mentor.getId() != null) { @@ -115,7 +116,7 @@ public MentorDto create(final Mentor mentor) { userProvisionService.provisionUserRole( mentorCreated.getId(), mentorCreated.getEmail(), RoleType.MENTOR); } - return enrichMentorWithProfilePicture(mentorCreated).toDto(); + return enrichMentorWithProfilePicture(mentorCreated); } /** diff --git a/src/test/java/com/wcc/platform/controller/MentorControllerTest.java b/src/test/java/com/wcc/platform/controller/MentorControllerTest.java index f4754f8cd..cc2826d59 100644 --- a/src/test/java/com/wcc/platform/controller/MentorControllerTest.java +++ b/src/test/java/com/wcc/platform/controller/MentorControllerTest.java @@ -71,7 +71,7 @@ void shouldGetAllMentorsAndReturnOk() throws Exception { void shouldCreateMentorAndReturnCreated() throws Exception { var mentorRequestBody = createMentorDtoTest(1L, MemberType.MENTOR); var returnedMentor = createMentorTest("Jane"); - when(mentorshipService.create(any(Mentor.class))).thenReturn(returnedMentor.toDto()); + when(mentorshipService.create(any(Mentor.class))).thenReturn(returnedMentor); mockMvc .perform(postRequest(API_MENTORS, mentorRequestBody)) @@ -354,7 +354,7 @@ void shouldReturnIsWomenFieldWhenCreatingMentor() throws Exception { .isWomen(true) .build(); - when(mentorshipService.create(any(Mentor.class))).thenReturn(mentor.toDto()); + when(mentorshipService.create(any(Mentor.class))).thenReturn(mentor); mockMvc .perform(postRequest(API_MENTORS, mentor)) @@ -431,7 +431,7 @@ void shouldIgnoreIdInRequestBodyWhenCreatingMentor() throws Exception { var requestWithId = createMentorDtoTest(999L, MemberType.MENTOR); var savedMentor = createMentorTest("Jane"); - when(mentorshipService.create(any())).thenReturn(savedMentor.toDto()); + when(mentorshipService.create(any())).thenReturn(savedMentor); mockMvc .perform(postRequest(API_MENTORS, requestWithId)) diff --git a/src/test/java/com/wcc/platform/service/MentorshipServiceFilteringTest.java b/src/test/java/com/wcc/platform/service/MentorshipServiceFilteringTest.java index 142e8a37c..e25c12e7a 100644 --- a/src/test/java/com/wcc/platform/service/MentorshipServiceFilteringTest.java +++ b/src/test/java/com/wcc/platform/service/MentorshipServiceFilteringTest.java @@ -49,7 +49,6 @@ class MentorshipServiceFilteringTest { @Mock private MemberProfilePictureRepository profilePicRepo; @Mock private UserProvisionService userProvisionService; @Mock private MentorshipNotificationService notificationService; - @Mock private ResourceService resourceService; private MentorshipService service; private Mentor mentor1; private MentorsPage mentorsPage; @@ -64,8 +63,7 @@ void setUp() { cycleRepository, userProvisionService, profilePicRepo, - notificationService, - resourceService)); + notificationService)); var cycle = MentorshipCycleEntity.builder() .mentorshipType(MentorshipType.AD_HOC) diff --git a/src/test/java/com/wcc/platform/service/MentorshipServiceRetrievalTest.java b/src/test/java/com/wcc/platform/service/MentorshipServiceRetrievalTest.java index 3273eec29..8de15506c 100644 --- a/src/test/java/com/wcc/platform/service/MentorshipServiceRetrievalTest.java +++ b/src/test/java/com/wcc/platform/service/MentorshipServiceRetrievalTest.java @@ -43,7 +43,6 @@ class MentorshipServiceRetrievalTest { @Mock private UserProvisionService userProvisionService; @Mock private MemberProfilePictureRepository profilePicRepo; @Mock private MentorshipNotificationService notificationService; - @Mock private ResourceService resourceService; private MentorshipService service; @BeforeEach @@ -57,8 +56,7 @@ void setUp() { cycleRepository, userProvisionService, profilePicRepo, - notificationService, - resourceService)); + notificationService)); } @Test @@ -226,7 +224,8 @@ void shouldHandleExceptionWhenFetchingProfilePictureFails() { @Test @DisplayName( - "Given mentor with email and slack name, when enriched with profile picture, then email and slackDisplayName should be preserved") + "Given mentor with email and slack name, when enriched with profile picture, " + + "then email and slackDisplayName should be preserved") void shouldPreserveEmailAndSlackNameWhenEnrichedWithProfilePicture() { var mentor = mock(Mentor.class, withSettings().defaultAnswer(RETURNS_DEEP_STUBS)); var dto = mock(MentorDto.class); @@ -254,7 +253,8 @@ void shouldPreserveEmailAndSlackNameWhenEnrichedWithProfilePicture() { @Test @DisplayName( - "Given mentor with pronouns, when enriched with profile picture, then pronouns should be preserved") + "Given mentor with pronouns, when enriched with profile picture, " + + "then pronouns should be preserved") void shouldPreservePronounsWhenEnrichedWithProfilePicture() { var mentor = mock(Mentor.class, withSettings().defaultAnswer(RETURNS_DEEP_STUBS)); var dto = mock(MentorDto.class); diff --git a/src/test/java/com/wcc/platform/service/MentorshipServiceTest.java b/src/test/java/com/wcc/platform/service/MentorshipServiceTest.java index 2e8336060..1602cb914 100644 --- a/src/test/java/com/wcc/platform/service/MentorshipServiceTest.java +++ b/src/test/java/com/wcc/platform/service/MentorshipServiceTest.java @@ -59,7 +59,6 @@ class MentorshipServiceTest { @Mock private MemberProfilePictureRepository profilePicRepo; @Mock private MentorshipNotificationService notificationService; @Mock private MentorshipCycleRepository cycleRepository; - @Mock private ResourceService resourceService; private Mentor mentor; private Mentor updatedMentor; private MentorDto mentorDto; @@ -82,8 +81,7 @@ void setUp() { cycleRepository, userProvisionService, profilePicRepo, - notificationService, - resourceService); + notificationService); } @Test @@ -133,7 +131,7 @@ void testCreateAvailableLongTermMentor() { var result = service.create(mentor); - assertEquals(mentor.toDto(), result); + assertEquals(mentor, result); verify(mentorRepository).create(mentor); } @@ -151,7 +149,7 @@ void testCreateAdHocOnlyMentor() { var result = service.create(mentor); - assertEquals(mentor.toDto(), result); + assertEquals(mentor, result); verify(mentorRepository).create(mentor); } @@ -190,7 +188,7 @@ void testCreateLongTermMentorWithMinimumHours() { var result = service.create(mentor); - assertEquals(mentor.toDto(), result); + assertEquals(mentor, result); verify(mentorRepository).create(mentor); } diff --git a/src/testInt/java/com/wcc/platform/service/mentorship/MentorshipServiceIntegrationTest.java b/src/testInt/java/com/wcc/platform/service/mentorship/MentorshipServiceIntegrationTest.java index e047e0dfa..a66bf8af5 100644 --- a/src/testInt/java/com/wcc/platform/service/mentorship/MentorshipServiceIntegrationTest.java +++ b/src/testInt/java/com/wcc/platform/service/mentorship/MentorshipServiceIntegrationTest.java @@ -11,7 +11,6 @@ import com.wcc.platform.domain.platform.member.Member; import com.wcc.platform.domain.platform.member.ProfileStatus; import com.wcc.platform.domain.platform.mentorship.Mentor; -import com.wcc.platform.domain.platform.mentorship.MentorDto; import com.wcc.platform.domain.platform.type.MemberType; import com.wcc.platform.domain.platform.type.ResourceType; import com.wcc.platform.domain.resource.MemberProfilePicture; @@ -50,7 +49,6 @@ class MentorshipServiceIntegrationTest extends DefaultDatabaseSetup { @Autowired private JdbcTemplate jdbcTemplate; private Mentor setupMentor; - private MentorDto mentorDTO; @BeforeEach void setUp() { @@ -60,13 +58,12 @@ void setUp() { cleanupMentor(setupMentor); pageRepository.deleteById(MENTORS.getId()); pageService.create(MENTORS, page); - mentorDTO = service.create(setupMentor); - repository.updateProfileStatus(mentorDTO.getId(), ProfileStatus.ACTIVE); + repository.updateProfileStatus(service.create(setupMentor).getId(), ProfileStatus.ACTIVE); } @AfterEach void tearDown() { - if (setupMentor != null || mentorDTO != null) { + if (setupMentor != null) { cleanupMentor(setupMentor); } } From 4c66025254d065ff682e027d2ca1a037a8520e58 Mon Sep 17 00:00:00 2001 From: Adriana Zencke Zimmermann Date: Mon, 15 Jun 2026 23:07:06 +0200 Subject: [PATCH 10/11] refactor: Remove validation annotations in PaginationUtil and replace with CollectionUtils checks --- .../java/com/wcc/platform/utils/PaginationUtil.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/wcc/platform/utils/PaginationUtil.java b/src/main/java/com/wcc/platform/utils/PaginationUtil.java index ba9b73b26..8afc8bfbd 100644 --- a/src/main/java/com/wcc/platform/utils/PaginationUtil.java +++ b/src/main/java/com/wcc/platform/utils/PaginationUtil.java @@ -1,10 +1,8 @@ package com.wcc.platform.utils; -import jakarta.validation.constraints.Min; -import jakarta.validation.constraints.NotEmpty; import java.util.List; import lombok.extern.slf4j.Slf4j; -import org.springframework.validation.annotation.Validated; +import org.springframework.util.CollectionUtils; /** Util class for pagination data for any page. */ @Slf4j @@ -20,14 +18,15 @@ private PaginationUtil() {} * @param pageSize items per page * @return list of items on the current page */ - @Validated public static List getPaginatedResult( - @NotEmpty(message = "Items list cannot be null or empty.") final List items, - final int currentPage, - @Min(value = 1, message = "Page size must be greater than zero") final int pageSize) { + final List items, final int currentPage, final int pageSize) { final int totalItems = items.size(); final int totalPages = getTotalPages(items, pageSize); + if (CollectionUtils.isEmpty(items)) { + throw new IllegalArgumentException("Items list cannot be null or empty."); + } + if (currentPage < 1 || currentPage > totalPages) { throw new IllegalArgumentException( "currentPage exceeds total pages. Total Pages: " From d04114894a1a9e5a3c60cc4bb22de62de3624b1f Mon Sep 17 00:00:00 2001 From: Adriana Zencke Zimmermann Date: Mon, 15 Jun 2026 23:28:33 +0200 Subject: [PATCH 11/11] refactor: Introduce MemberBase class to reduce duplication in Member and MemberDto, enhance builder pattern usage, and clean up tests --- .../platform/member/LeadershipMember.java | 2 +- .../domain/platform/member/Member.java | 99 +++++++++---------- .../domain/platform/member/MemberBase.java | 69 +++++++++++++ .../domain/platform/member/MemberDto.java | 86 ++++++++-------- .../domain/platform/mentorship/Mentor.java | 68 +++++++++++-- .../domain/platform/mentorship/MentorDto.java | 4 +- .../postgres/component/MentorMapper.java | 10 +- .../domain/platform/LeadershipMemberTest.java | 3 +- .../platform/factories/SetupFactories.java | 15 +-- ...stgresMemberRepositoryIntegrationTest.java | 2 +- 10 files changed, 238 insertions(+), 120 deletions(-) create mode 100644 src/main/java/com/wcc/platform/domain/platform/member/MemberBase.java diff --git a/src/main/java/com/wcc/platform/domain/platform/member/LeadershipMember.java b/src/main/java/com/wcc/platform/domain/platform/member/LeadershipMember.java index 587c78d70..e0ff80dd5 100644 --- a/src/main/java/com/wcc/platform/domain/platform/member/LeadershipMember.java +++ b/src/main/java/com/wcc/platform/domain/platform/member/LeadershipMember.java @@ -22,7 +22,7 @@ */ @Getter @EqualsAndHashCode(callSuper = true) -@ToString +@ToString(callSuper = true) @NoArgsConstructor @Validated public class LeadershipMember extends Member { diff --git a/src/main/java/com/wcc/platform/domain/platform/member/Member.java b/src/main/java/com/wcc/platform/domain/platform/member/Member.java index 0e4724dfe..6700dfdf7 100644 --- a/src/main/java/com/wcc/platform/domain/platform/member/Member.java +++ b/src/main/java/com/wcc/platform/domain/platform/member/Member.java @@ -1,73 +1,46 @@ package com.wcc.platform.domain.platform.member; -import com.fasterxml.jackson.annotation.JsonProperty; import com.wcc.platform.domain.cms.attributes.Country; import com.wcc.platform.domain.cms.attributes.Image; import com.wcc.platform.domain.cms.attributes.PronounCategory; import com.wcc.platform.domain.platform.SocialNetwork; import com.wcc.platform.domain.platform.type.MemberType; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; import java.util.List; -import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.Setter; import lombok.ToString; +import lombok.experimental.SuperBuilder; import org.springframework.validation.annotation.Validated; /** Member class with common attributes for all community members. */ @NoArgsConstructor -@AllArgsConstructor -@ToString -@EqualsAndHashCode +@ToString(callSuper = true) +@EqualsAndHashCode(callSuper = true) @Getter -@Builder(toBuilder = true) +@SuperBuilder(toBuilder = true) @Validated -public class Member { - @Setter - @Schema(accessMode = Schema.AccessMode.READ_ONLY, description = "Auto-generated member ID") - @JsonProperty(access = JsonProperty.Access.READ_ONLY) - private Long id; +public class Member extends MemberBase { - @NotBlank(message = "Full name cannot be blank") - private String fullName; - - @NotBlank(message = "Position cannot be blank") - private String position; - - @Setter - @NotBlank(message = "Email cannot be blank") - @Email(message = "Email format is not valid") - private String email; - - @NotBlank(message = "Slack name cannot be blank") - private String slackDisplayName; - - @NotNull(message = "Country cannot be null") - private Country country; - - private String city; - private String companyName; - - @Setter - @NotEmpty(message = "At least one member type must be provided (e.g., Member, Volunteer, etc.)") - private List memberTypes; - - @Setter private List images; - private List network; - private String pronouns; - private PronounCategory pronounCategory; - private Boolean isWomen; - - /** Converts this Member entity to a MemberDto for data transfer purposes. */ - public MemberDto toDto() { - return new MemberDto( + /** Constructor for SuperBuilder and manual use. */ + @SuppressWarnings("PMD.ExcessiveParameterList") + public Member( + final Long id, + final String fullName, + final String position, + final String email, + final String slackDisplayName, + final Country country, + final String city, + final String companyName, + final List memberTypes, + final List images, + final List network, + final String pronouns, + final PronounCategory pronounCategory, + final Boolean isWomen) { + super( id, fullName, position, @@ -83,4 +56,30 @@ public MemberDto toDto() { pronounCategory, isWomen); } + + @Override + @NotEmpty(message = "At least one member type must be provided (e.g., Member, Volunteer, etc.)") + public List getMemberTypes() { + return super.getMemberTypes(); + } + + /** Converts this Member entity to a MemberDto for data transfer purposes. */ + @Override + public MemberDto toDto() { + return new MemberDto( + getId(), + getFullName(), + getPosition(), + getEmail(), + getSlackDisplayName(), + getCountry(), + getCity(), + getCompanyName(), + getMemberTypes(), + getImages(), + getNetwork(), + getPronouns(), + getPronounCategory(), + getIsWomen()); + } } diff --git a/src/main/java/com/wcc/platform/domain/platform/member/MemberBase.java b/src/main/java/com/wcc/platform/domain/platform/member/MemberBase.java new file mode 100644 index 000000000..39c69857d --- /dev/null +++ b/src/main/java/com/wcc/platform/domain/platform/member/MemberBase.java @@ -0,0 +1,69 @@ +package com.wcc.platform.domain.platform.member; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.wcc.platform.domain.cms.attributes.Country; +import com.wcc.platform.domain.cms.attributes.Image; +import com.wcc.platform.domain.cms.attributes.PronounCategory; +import com.wcc.platform.domain.platform.SocialNetwork; +import com.wcc.platform.domain.platform.type.MemberType; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.util.List; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.SuperBuilder; +import org.springframework.validation.annotation.Validated; + +/** Base class for Member and MemberDto to reduce code duplication. */ +@NoArgsConstructor +@AllArgsConstructor(access = AccessLevel.PROTECTED) +@ToString +@EqualsAndHashCode +@Getter +@Setter +@SuperBuilder(toBuilder = true) +@Validated +public abstract class MemberBase { + @Schema(accessMode = Schema.AccessMode.READ_ONLY, description = "Auto-generated member ID") + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + private Long id; + + @NotBlank(message = "Full name cannot be blank") + private String fullName; + + @NotBlank(message = "Position cannot be blank") + private String position; + + @NotBlank(message = "Email cannot be blank") + @Email(message = "Email format is not valid") + @Setter + private String email; + + @NotBlank(message = "Slack name cannot be blank") + private String slackDisplayName; + + @NotNull(message = "Country cannot be null") + private Country country; + + private String city; + private String companyName; + + private List memberTypes; + + private List images; + private List network; + private String pronouns; + private PronounCategory pronounCategory; + + private Boolean isWomen; + + /** Converts this Member entity to a MemberDto for data transfer purposes. */ + public abstract MemberDto toDto(); +} diff --git a/src/main/java/com/wcc/platform/domain/platform/member/MemberDto.java b/src/main/java/com/wcc/platform/domain/platform/member/MemberDto.java index e379da7e5..fec6b2656 100644 --- a/src/main/java/com/wcc/platform/domain/platform/member/MemberDto.java +++ b/src/main/java/com/wcc/platform/domain/platform/member/MemberDto.java @@ -7,63 +7,71 @@ import com.wcc.platform.domain.platform.SocialNetwork; import com.wcc.platform.domain.platform.type.MemberType; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; import java.util.List; -import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.Setter; import lombok.ToString; +import lombok.experimental.SuperBuilder; import org.springframework.validation.annotation.Validated; /** MemberDto class with common attributes for all community members. */ @Getter -@AllArgsConstructor @NoArgsConstructor -@EqualsAndHashCode -@ToString -@Builder +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@SuperBuilder @Validated -public class MemberDto { - @Schema(accessMode = Schema.AccessMode.READ_ONLY) - @JsonProperty(access = JsonProperty.Access.READ_ONLY) - private Long id; - - @NotBlank(message = "Full name cannot be blank") - private String fullName; - - @NotBlank(message = "Position cannot be blank") - private String position; - - @Setter - @NotBlank(message = "Email cannot be blank") - @Email(message = "Email format is not valid") - private String email; - - @NotBlank(message = "Slack name cannot be blank") - private String slackDisplayName; +public class MemberDto extends MemberBase { - @NotNull(message = "Country cannot be null") - private Country country; - - private String city; - private String companyName; + /** Constructor for SuperBuilder and manual use. */ + @SuppressWarnings("PMD.ExcessiveParameterList") + public MemberDto( + final Long id, + final String fullName, + final String position, + final String email, + final String slackDisplayName, + final Country country, + final String city, + final String companyName, + final List memberTypes, + final List images, + final List network, + final String pronouns, + final PronounCategory pronounCategory, + final Boolean isWomen) { + super( + id, + fullName, + position, + email, + slackDisplayName, + country, + city, + companyName, + memberTypes, + images, + network, + pronouns, + pronounCategory, + isWomen); + } @Schema( accessMode = Schema.AccessMode.READ_ONLY, description = "List of Member types (e.g., Mentor, Leader, Volunteer, etc.)") @JsonProperty(access = JsonProperty.Access.READ_ONLY) - private List memberTypes; + @Override + public List getMemberTypes() { + return super.getMemberTypes(); + } - private List images; - private List network; - private String pronouns; - private PronounCategory pronounCategory; - private Boolean isWomen; + /** Converts this MemberDto entity to a MemberDto for data transfer purposes. */ + @Override + public MemberDto toDto() { + return this; + } /** * Update member using attributes from his DTO. 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 ee58e8e54..0e1dcf5f2 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 @@ -15,11 +15,11 @@ import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import java.util.List; -import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.ToString; +import lombok.experimental.SuperBuilder; import org.apache.commons.lang3.StringUtils; import org.springframework.util.CollectionUtils; import org.springframework.validation.annotation.Validated; @@ -30,6 +30,7 @@ @ToString @NoArgsConstructor @Validated +@SuperBuilder(builderMethodName = "mentorBuilder", toBuilder = true) @SuppressWarnings("PMD.ImmutableField") public class Mentor extends Member { @@ -45,8 +46,7 @@ public class Mentor extends Member { private Boolean acceptMale; private Boolean acceptPromotion; - /** Mentor Builder. */ - @Builder(builderMethodName = "mentorBuilder") + /** Mentor Constructor. */ @SuppressWarnings("PMD.ExcessiveParameterList") public Mentor( final Long id, @@ -159,11 +159,65 @@ private MentorDtoBuilder buildFromMentor(final Mentor mentor) { .acceptPromotion(mentor.getAcceptPromotion()); } - /** Lombok builder hook to enforce normalization. */ - public static class MentorBuilder { - public MentorBuilder spokenLanguages(final List spokenLanguages) { + /** Mentor Builder implementation to ensure proper inheritance. */ + @SuppressWarnings("unchecked") + public abstract static class MentorBuilder> + extends MemberBuilder { + + public B profileStatus(final ProfileStatus profileStatus) { + this.profileStatus = profileStatus; + return (B) this; + } + + public B skills(final Skills skills) { + this.skills = skills; + return (B) this; + } + + public B spokenLanguages(final List spokenLanguages) { this.spokenLanguages = normalizeLanguages(spokenLanguages); - return this; + return (B) this; + } + + public B bio(final String bio) { + this.bio = bio; + return (B) this; + } + + public B menteeSection(final MenteeSection menteeSection) { + this.menteeSection = menteeSection; + return (B) this; + } + + public B feedbackSection(final FeedbackSection feedbackSection) { + this.feedbackSection = feedbackSection; + return (B) this; + } + + public B resources(final MentorResource resources) { + this.resources = resources; + return (B) this; + } + + public B calendlyLink(final String calendlyLink) { + this.calendlyLink = calendlyLink; + return (B) this; + } + + public B acceptMale(final Boolean acceptMale) { + this.acceptMale = acceptMale; + return (B) this; + } + + public B acceptPromotion(final Boolean acceptPromotion) { + this.acceptPromotion = acceptPromotion; + return (B) this; + } + + @Override + public B memberTypes(final List memberTypes) { + this.memberTypes = memberTypes; + return (B) this; } } } 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 c5f5129c9..850811966 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 @@ -18,11 +18,11 @@ import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import java.util.List; -import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.ToString; +import lombok.experimental.SuperBuilder; import org.springframework.util.CollectionUtils; import org.springframework.validation.annotation.Validated; @@ -32,6 +32,7 @@ @ToString @NoArgsConstructor @Validated +@SuperBuilder(builderMethodName = "mentorDtoBuilder") @SuppressWarnings("PMD.ImmutableField") public class MentorDto extends MemberDto { @@ -57,7 +58,6 @@ public class MentorDto extends MemberDto { /** Mentor Builder. */ @SuppressWarnings("PMD.ExcessiveParameterList") - @Builder(builderMethodName = "mentorDtoBuilder") public MentorDto( final Long id, @NotBlank final String fullName, diff --git a/src/main/java/com/wcc/platform/repository/postgres/component/MentorMapper.java b/src/main/java/com/wcc/platform/repository/postgres/component/MentorMapper.java index 4e65c88ca..c11e668bf 100644 --- a/src/main/java/com/wcc/platform/repository/postgres/component/MentorMapper.java +++ b/src/main/java/com/wcc/platform/repository/postgres/component/MentorMapper.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.mentorship.Mentor; -import com.wcc.platform.domain.platform.mentorship.Mentor.MentorBuilder; import com.wcc.platform.repository.postgres.PostgresMemberRepository; import com.wcc.platform.repository.postgres.mentorship.PostgresMenteeSectionRepository; import com.wcc.platform.repository.postgres.mentorship.PostgresSkillRepository; @@ -29,7 +28,7 @@ public class MentorMapper { /** Maps a ResultSet row to a Mentor object. */ public Mentor mapRowToMentor(final ResultSet rs) throws SQLException { final long mentorId = rs.getLong(COLUMN_MENTOR_ID); - final MentorBuilder builder = Mentor.mentorBuilder(); + final Mentor.MentorBuilder builder = Mentor.mentorBuilder(); final Optional memberOpt = memberRepository.findById(mentorId); @@ -55,15 +54,16 @@ public Mentor mapRowToMentor(final ResultSet rs) throws SQLException { final var menteeSection = menteeSectionRepo.findByMentorId(mentorId); menteeSection.ifPresent(builder::menteeSection); - return builder + builder .id(mentorId) .profileStatus(ProfileStatus.fromId(rs.getInt(COLUMN_PROFILE_STATUS))) .spokenLanguages(List.of(rs.getString(COLUMN_SPOKEN_LANG).split(COMMA))) .bio(rs.getString(COLUMN_BIO)) .calendlyLink(rs.getString(COL_CALENDLY_LINK)) .acceptMale(rs.getBoolean(COL_ACCEPT_MALE)) - .acceptPromotion(rs.getBoolean(COL_ACCEPT_PROMO)) - .build(); + .acceptPromotion(rs.getBoolean(COL_ACCEPT_PROMO)); + + return builder.build(); } public void addMentor(final Mentor mentor, final Long memberId) { diff --git a/src/test/java/com/wcc/platform/domain/platform/LeadershipMemberTest.java b/src/test/java/com/wcc/platform/domain/platform/LeadershipMemberTest.java index cc4377f02..743a87d55 100644 --- a/src/test/java/com/wcc/platform/domain/platform/LeadershipMemberTest.java +++ b/src/test/java/com/wcc/platform/domain/platform/LeadershipMemberTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import com.wcc.platform.domain.platform.member.LeadershipMember; import com.wcc.platform.domain.platform.type.MemberType; @@ -38,6 +39,6 @@ void testToString() { .memberTypes(List.of(MemberType.EVANGELIST)) .build(); - assertEquals("LeadershipMember(memberTypes=[EVANGELIST])", evangelist.toString()); + assertTrue(evangelist.toString().contains("memberTypes=[EVANGELIST]")); } } diff --git a/src/test/java/com/wcc/platform/factories/SetupFactories.java b/src/test/java/com/wcc/platform/factories/SetupFactories.java index 2d3ca318b..28f922408 100644 --- a/src/test/java/com/wcc/platform/factories/SetupFactories.java +++ b/src/test/java/com/wcc/platform/factories/SetupFactories.java @@ -2,8 +2,6 @@ import static com.wcc.platform.factories.SetUpStyleFactories.backgroundSecondary; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.wcc.platform.configuration.ObjectMapperConfig; import com.wcc.platform.domain.cms.attributes.CmsIcon; @@ -26,7 +24,6 @@ import com.wcc.platform.domain.platform.member.MemberDto; import com.wcc.platform.domain.platform.type.MemberType; import com.wcc.platform.domain.platform.type.ProgramType; -import com.wcc.platform.utils.FileUtil; import java.util.List; /** Setup Factory tests. */ @@ -170,17 +167,7 @@ public static Member createUpdatedMemberTest(final Member member, final MemberDt .network(memberDto.getNetwork()) .build(); } - - /** Factory test to get a list of members for testing get members API. */ - public static List createMembersTest(final String fileName) { - try { - final String content = FileUtil.readFileAsString(fileName); - return OBJECT_MAPPER.readValue(content, new TypeReference<>() {}); - } catch (JsonProcessingException e) { - return List.of(createMemberTest(MemberType.MEMBER)); - } - } - + /** Factory test. */ public static LeadershipMember createLeadershipMemberTest(final MemberType type) { return LeadershipMember.leadershipMemberBuilder() diff --git a/src/testInt/java/com/wcc/platform/repository/postgres/PostgresMemberRepositoryIntegrationTest.java b/src/testInt/java/com/wcc/platform/repository/postgres/PostgresMemberRepositoryIntegrationTest.java index 9485e00d9..3c4f5d471 100644 --- a/src/testInt/java/com/wcc/platform/repository/postgres/PostgresMemberRepositoryIntegrationTest.java +++ b/src/testInt/java/com/wcc/platform/repository/postgres/PostgresMemberRepositoryIntegrationTest.java @@ -71,7 +71,7 @@ void testCreateAndUpdate() { @Test void createReturnEmptyForNotFoundMemberId() { - var optionalMember = repository.findById(7L); + var optionalMember = repository.findById(Long.MAX_VALUE); assertTrue(optionalMember.isEmpty(), "Should not find optionalMember with this id"); }