From af312d23a0f6869027361f271b6ba603ecac4e26 Mon Sep 17 00:00:00 2001 From: Adriana Zencke Zimmermann Date: Thu, 2 Apr 2026 22:30:03 +0200 Subject: [PATCH 1/5] refactor: Replace hardcoded date cycle logic with DB-driven cycle lookup MentorshipService previously determined the active cycle using London-timezone date windows hard-coded to specific months and day counts. Replace this with a cycleRepository.findOpenCycle() call so cycle state is driven by the database. MenteeService is simplified to use the same repository directly, removing the backward-compatibility fallback. Add @JsonIgnoreProperties to DTO classes to tolerate unknown fields. Tests updated throughout. --- .../pages/mentorship/MentorFilterSection.java | 2 + .../domain/platform/member/MemberDto.java | 3 + .../domain/platform/mentorship/MentorDto.java | 2 + .../mentorship/MentorshipCycleEntity.java | 15 ++- .../PostgresMenteeApplicationRepository.java | 6 +- .../wcc/platform/service/MenteeService.java | 63 +++-------- .../platform/service/MentorshipService.java | 101 +++++++----------- .../platform/service/MenteeServiceTest.java | 78 ++++++++------ .../MentorshipServiceFilteringTest.java | 20 +++- .../MentorshipServiceRetrievalTest.java | 83 +++++++------- .../service/MentorshipServiceTest.java | 38 +++++-- .../service/MenteeServiceIntegrationTest.java | 45 +++++--- 12 files changed, 237 insertions(+), 219 deletions(-) diff --git a/src/main/java/com/wcc/platform/domain/cms/pages/mentorship/MentorFilterSection.java b/src/main/java/com/wcc/platform/domain/cms/pages/mentorship/MentorFilterSection.java index 1532f030..ff3715ba 100644 --- a/src/main/java/com/wcc/platform/domain/cms/pages/mentorship/MentorFilterSection.java +++ b/src/main/java/com/wcc/platform/domain/cms/pages/mentorship/MentorFilterSection.java @@ -1,5 +1,6 @@ package com.wcc.platform.domain.cms.pages.mentorship; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.wcc.platform.domain.platform.mentorship.MentorshipType; import com.wcc.platform.domain.platform.mentorship.SkillsFilter; import java.util.List; @@ -17,6 +18,7 @@ @EqualsAndHashCode @ToString @Getter +@JsonIgnoreProperties(ignoreUnknown = true) public class MentorFilterSection { private String keyword; private List types; 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 ad97a4cd..424a5afe 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 @@ -1,5 +1,6 @@ package com.wcc.platform.domain.platform.member; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.wcc.platform.domain.cms.attributes.Country; import com.wcc.platform.domain.cms.attributes.Image; @@ -22,10 +23,12 @@ @EqualsAndHashCode @ToString @Builder +@JsonIgnoreProperties(ignoreUnknown = true) public class MemberDto { @Schema(accessMode = Schema.AccessMode.READ_ONLY) @JsonProperty(access = JsonProperty.Access.READ_ONLY) private Long id; + private String fullName; private String position; private String email; 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 c134b025..6a1d2616 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 @@ -1,5 +1,6 @@ package com.wcc.platform.domain.platform.mentorship; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.wcc.platform.domain.cms.attributes.Country; import com.wcc.platform.domain.cms.attributes.Image; @@ -30,6 +31,7 @@ @ToString @NoArgsConstructor @SuppressWarnings("PMD.ImmutableField") +@JsonIgnoreProperties(ignoreUnknown = true) public class MentorDto extends MemberDto { /** diff --git a/src/main/java/com/wcc/platform/domain/platform/mentorship/MentorshipCycleEntity.java b/src/main/java/com/wcc/platform/domain/platform/mentorship/MentorshipCycleEntity.java index ee92de09..87da9bba 100644 --- a/src/main/java/com/wcc/platform/domain/platform/mentorship/MentorshipCycleEntity.java +++ b/src/main/java/com/wcc/platform/domain/platform/mentorship/MentorshipCycleEntity.java @@ -1,5 +1,6 @@ package com.wcc.platform.domain.platform.mentorship; +import com.wcc.platform.domain.cms.pages.mentorship.MentorsPage; import java.time.LocalDate; import java.time.Month; import java.time.Year; @@ -52,11 +53,21 @@ public boolean isActive() { } /** - * Convert to MentorshipCycle value object for backward compatibility. + * Convert to MentorsPage.OpenCycle for backward compatibility. + * + * @return MentorsPage.OpenCycle value object + */ + public MentorsPage.OpenCycle toOpenCycleValue() { + return new MentorshipCycle(mentorshipType, cycleMonth != null ? cycleMonth : null) + .toOpenCycle(); + } + + /** + * Convert to MentorshipCycle value object. * * @return MentorshipCycle value object */ public MentorshipCycle toMentorshipCycle() { - return new MentorshipCycle(mentorshipType, cycleMonth != null ? cycleMonth : null); + return new MentorshipCycle(mentorshipType, cycleMonth); } } diff --git a/src/main/java/com/wcc/platform/repository/postgres/mentorship/PostgresMenteeApplicationRepository.java b/src/main/java/com/wcc/platform/repository/postgres/mentorship/PostgresMenteeApplicationRepository.java index 8cd4c59c..3c934141 100644 --- a/src/main/java/com/wcc/platform/repository/postgres/mentorship/PostgresMenteeApplicationRepository.java +++ b/src/main/java/com/wcc/platform/repository/postgres/mentorship/PostgresMenteeApplicationRepository.java @@ -54,7 +54,8 @@ public class PostgresMenteeApplicationRepository implements MenteeApplicationRep private static final String INSERT_APPLICATION = "INSERT INTO mentee_applications " - + "(mentee_id, mentor_id, cycle_id, priority_order, application_status, application_message, why_mentor) " + + "(mentee_id, mentor_id, cycle_id, priority_order, " + + "application_status, application_message, why_mentor) " + "VALUES (?, ?, ?, ?, ?::application_status, ?, ?) " + "RETURNING application_id"; @@ -163,8 +164,7 @@ public List getAll() { @Override public Long countMenteeApplications(final Long menteeId, final Long cycleId) { - final Long count = jdbc.queryForObject(COUNT_MENTEE_APPS, Long.class, menteeId, cycleId); - return count != null ? count : 0L; + return jdbc.queryForObject(COUNT_MENTEE_APPS, Long.class, menteeId, cycleId); } private MenteeApplication mapRow(final ResultSet rs) throws SQLException { diff --git a/src/main/java/com/wcc/platform/service/MenteeService.java b/src/main/java/com/wcc/platform/service/MenteeService.java index 06736d96..4793e40a 100644 --- a/src/main/java/com/wcc/platform/service/MenteeService.java +++ b/src/main/java/com/wcc/platform/service/MenteeService.java @@ -1,15 +1,11 @@ package com.wcc.platform.service; -import com.wcc.platform.configuration.MentorshipConfig; import com.wcc.platform.domain.exceptions.*; import com.wcc.platform.domain.platform.member.Member; -import com.wcc.platform.domain.platform.mentorship.CycleStatus; import com.wcc.platform.domain.platform.mentorship.Mentee; import com.wcc.platform.domain.platform.mentorship.MenteeApplication; import com.wcc.platform.domain.platform.mentorship.MenteeRegistration; -import com.wcc.platform.domain.platform.mentorship.MentorshipCycle; import com.wcc.platform.domain.platform.mentorship.MentorshipCycleEntity; -import com.wcc.platform.domain.platform.mentorship.MentorshipType; import com.wcc.platform.domain.platform.type.MemberType; import com.wcc.platform.domain.platform.type.RoleType; import com.wcc.platform.repository.MemberRepository; @@ -17,7 +13,6 @@ import com.wcc.platform.repository.MenteeRepository; import com.wcc.platform.repository.MentorRepository; import com.wcc.platform.repository.MentorshipCycleRepository; -import java.time.Year; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -28,11 +23,7 @@ @Service @AllArgsConstructor public class MenteeService { - private static final int MAX_MENTORS = 5; - - private final MentorshipService mentorshipService; - private final MentorshipConfig mentorshipConfig; private final MentorshipCycleRepository cycleRepository; private final MenteeApplicationRepository registrationsRepo; private final MenteeRepository menteeRepository; @@ -62,8 +53,14 @@ public List getAllMentees() { public Mentee saveRegistration(final MenteeRegistration request) { validateMentors(request); - final Year cycleYear = request.cycleYear() != null ? request.cycleYear() : Year.now(); - final var cycle = getMentorshipCycle(request.mentorshipType(), cycleYear); + final var cycle = getCurrentCycle(); + + if (!request.mentorshipType().equals(cycle.getMentorshipType())) { + throw new InvalidMentorshipTypeException( + String.format( + "Mentee mentorship type '%s' does not match current cycle type '%s'", + request.mentorshipType(), cycle.getMentorshipType())); + } final var menteeId = ensureMenteeId(request.mentee()); if (menteeId != null) { @@ -255,49 +252,13 @@ private void validateMentors(final MenteeRegistration registrationRequest) { * Retrieves the MentorshipCycleEntity for the given mentorship type and cycle year. Validates * that the cycle is open and the mentorship type matches the current cycle. * - * @param mentorshipType The type of mentorship for which the cycle is being retrieved. - * @param cycleYear The year of the mentorship cycle. * @return The MentorshipCycleEntity corresponding to the specified type and year. * @throws MentorshipCycleClosedException If the mentorship cycle is closed. - * @throws InvalidMentorshipTypeException If the mentorship type does not match the current cycle - * type. */ - private MentorshipCycleEntity getMentorshipCycle( - final MentorshipType mentorshipType, final Year cycleYear) { - final var openCycle = cycleRepository.findByYearAndType(cycleYear, mentorshipType); - - if (openCycle.isPresent()) { - final MentorshipCycleEntity cycle = openCycle.get(); - - if (mentorshipConfig.getValidation().isEnabled() && cycle.getStatus() != CycleStatus.OPEN) { - throw new MentorshipCycleClosedException( - String.format( - "Mentorship cycle for %s in %d is %s. Registration is not available.", - mentorshipType, cycleYear.getValue(), cycle.getStatus())); - } - - return cycle; - } - - // Fallback to old mentorship service validation for backward compatibility - final MentorshipCycle currentCycle = mentorshipService.getCurrentCycle(); - - if (currentCycle == MentorshipService.CYCLE_CLOSED) { - throw new MentorshipCycleClosedException( - "Mentorship cycle is currently closed. Registration is not available."); - } - - if (mentorshipType != currentCycle.cycle()) { - throw new InvalidMentorshipTypeException( - String.format( - "Mentee mentorship type '%s' does not match current cycle type '%s'.", - mentorshipType, currentCycle.cycle())); - } - - return MentorshipCycleEntity.builder() - .cycleYear(cycleYear) - .mentorshipType(mentorshipType) - .build(); + private MentorshipCycleEntity getCurrentCycle() { + return cycleRepository + .findOpenCycle() + .orElseThrow(() -> new MentorshipCycleClosedException("Mentorship cycle is closed")); } /** diff --git a/src/main/java/com/wcc/platform/service/MentorshipService.java b/src/main/java/com/wcc/platform/service/MentorshipService.java index 72d1059d..39dd96f1 100644 --- a/src/main/java/com/wcc/platform/service/MentorshipService.java +++ b/src/main/java/com/wcc/platform/service/MentorshipService.java @@ -7,68 +7,47 @@ 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.exceptions.MentorshipCycleClosedException; 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.MentorshipCycle; -import com.wcc.platform.domain.platform.mentorship.MentorshipType; +import com.wcc.platform.domain.platform.mentorship.MentorshipCycleEntity; import com.wcc.platform.domain.platform.type.RoleType; import com.wcc.platform.domain.resource.MemberProfilePicture; import com.wcc.platform.domain.resource.Resource; import com.wcc.platform.repository.MemberProfilePictureRepository; import com.wcc.platform.repository.MemberRepository; import com.wcc.platform.repository.MentorRepository; +import com.wcc.platform.repository.MentorshipCycleRepository; import com.wcc.platform.utils.FiltersUtil; -import java.time.LocalDate; -import java.time.Month; -import java.time.ZoneId; -import java.time.ZonedDateTime; import java.util.List; import java.util.Optional; +import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; /** Platform Service. */ @Slf4j @Service +@AllArgsConstructor @SuppressWarnings({ "PMD.ExcessiveImports", "PMD.TooManyMethods" }) // TODO: https://github.com/Women-Coding-Community/wcc-backend/issues/520 public class MentorshipService { - /* package */ static final MentorshipCycle CYCLE_CLOSED = new MentorshipCycle(null, null); + public static final MentorshipCycleEntity CLOSED_CYCLE = + MentorshipCycleEntity.builder().status(CycleStatus.CLOSED).build(); - private static final String EUROPE_LONDON = "Europe/London"; - private static final MentorshipCycle ACTIVE_LONG_TERM = - new MentorshipCycle(MentorshipType.LONG_TERM, Month.MARCH); private static final int MINIMUM_HOURS = 2; - private final MentorRepository mentorRepository; private final MemberRepository memberRepository; + private final MentorshipCycleRepository cycleRepository; private final UserProvisionService userProvisionService; private final MemberProfilePictureRepository profilePicRepo; - private final int daysCycleOpen; private final MentorshipNotificationService notificationService; - @Autowired - public MentorshipService( - final MentorRepository mentorRepository, - final MemberRepository memberRepository, - final UserProvisionService userProvisionService, - final MemberProfilePictureRepository profilePicRepo, - final @Value("${mentorship.daysCycleOpen}") int daysCycleOpen, - final MentorshipNotificationService notificationService) { - this.mentorRepository = mentorRepository; - this.memberRepository = memberRepository; - this.userProvisionService = userProvisionService; - this.profilePicRepo = profilePicRepo; - this.daysCycleOpen = daysCycleOpen; - this.notificationService = notificationService; - } - /** * Create a mentor record. * @@ -136,7 +115,7 @@ public MentorsPage getMentorsPage( final var mentors = FiltersUtil.applyFilters(getAllActiveMentors(currentCycle), filters); return mentorsPage.updateUpdate( - currentCycle.toOpenCycle(), FiltersUtil.mentorshipAllFilters(), mentors); + currentCycle.toOpenCycleValue(), FiltersUtil.mentorshipAllFilters(), mentors); } public MentorsPage getMentorsPage(final MentorsPage mentorsPage) { @@ -144,8 +123,8 @@ public MentorsPage getMentorsPage(final MentorsPage mentorsPage) { } /** - * Return all mentors ignoring their status and the current cycle. - * Intended for privileged (admin/leader) use only. + * Return all mentors ignoring their status and the current cycle. Intended for privileged + * (admin/leader) use only. * * @return list of all mentor DTOs regardless of {@link ProfileStatus}. */ @@ -155,6 +134,32 @@ public List getAllMentors() { .toList(); } + /** + * Retrieves the mentorship cycle for the given mentorship type and cycle year. If no open cycle + * is found, returns a default closed cycle. + * + * @return The MentorshipCycleEntity + */ + public MentorshipCycleEntity getCurrentCycle() { + final var openCycle = cycleRepository.findOpenCycle(); + + return openCycle.orElse(CLOSED_CYCLE); + } + + /** + * Retrieves the MentorshipCycleEntity for the given mentorship type and cycle year. Validates + * that the cycle is open and the mentorship type matches the current cycle. + * + * @return The MentorshipCycleEntity + * @throws MentorshipCycleClosedException If the mentorship cycle is closed. + */ + public MentorshipCycleEntity getOpenCycle() { + final var openCycle = cycleRepository.findOpenCycle(); + + return openCycle.orElseThrow( + () -> new MentorshipCycleClosedException("Mentorship cycle is closed")); + } + /** * Return all ACTIVE mentors in the current cycle. Mentors with PENDING or REJECTED status are * always excluded regardless of the cycle state. @@ -165,44 +170,20 @@ public List getAllActiveMentors() { return getAllActiveMentors(getCurrentCycle()); } - private List getAllActiveMentors(final MentorshipCycle currentCycle) { + private List getAllActiveMentors(final MentorshipCycleEntity currentCycle) { final var allActiveMentors = mentorRepository.getAll().stream() .filter(m -> m.getProfileStatus() == ProfileStatus.ACTIVE); - if (currentCycle == CYCLE_CLOSED) { + if (currentCycle.getStatus() == CycleStatus.CLOSED) { return allActiveMentors.map(mentor -> enrichWithProfilePicture(mentor.toDto())).toList(); } return allActiveMentors - .map(mentor -> enrichWithProfilePicture(mentor.toDto(currentCycle))) + .map(mentor -> enrichWithProfilePicture(mentor.toDto(currentCycle.toMentorshipCycle()))) .toList(); } - /* package */ MentorshipCycle getCurrentCycle() { - final ZonedDateTime londonTime = nowLondon(); - final LocalDate currentDate = londonTime.toLocalDate(); - - final var currentMonth = currentDate.getMonth(); - final int dayOfMonth = currentDate.getDayOfMonth(); - - if (currentMonth == Month.MARCH && dayOfMonth <= daysCycleOpen) { - return ACTIVE_LONG_TERM; - } - - if (currentMonth.getValue() >= Month.MAY.getValue() - && currentMonth.getValue() <= Month.NOVEMBER.getValue() - && dayOfMonth <= daysCycleOpen) { - return new MentorshipCycle(MentorshipType.AD_HOC, currentMonth); - } - - return CYCLE_CLOSED; - } - - /* package */ ZonedDateTime nowLondon() { - return ZonedDateTime.now(ZoneId.of(EUROPE_LONDON)); - } - private MentorDto enrichWithProfilePicture(final MentorDto dto) { final Optional profilePicture = fetchProfilePicture(dto.getId()); diff --git a/src/test/java/com/wcc/platform/service/MenteeServiceTest.java b/src/test/java/com/wcc/platform/service/MenteeServiceTest.java index bc6c150c..87fb76b5 100644 --- a/src/test/java/com/wcc/platform/service/MenteeServiceTest.java +++ b/src/test/java/com/wcc/platform/service/MenteeServiceTest.java @@ -26,7 +26,6 @@ import com.wcc.platform.domain.platform.mentorship.MenteeApplicationDto; import com.wcc.platform.domain.platform.mentorship.MenteeRegistration; import com.wcc.platform.domain.platform.mentorship.Mentor; -import com.wcc.platform.domain.platform.mentorship.MentorshipCycle; import com.wcc.platform.domain.platform.mentorship.MentorshipCycleEntity; import com.wcc.platform.domain.platform.mentorship.MentorshipType; import com.wcc.platform.domain.platform.type.MemberType; @@ -69,8 +68,6 @@ void setUp() { when(validation.isEnabled()).thenReturn(true); menteeService = new MenteeService( - mentorshipService, - mentorshipConfig, cycleRepository, applicationRepository, menteeRepository, @@ -79,6 +76,14 @@ void setUp() { userProvisionService); mentee = createMenteeTest(null, "Test Mentee", "test@wcc.com"); when(mentorRepository.findById(any())).thenReturn(Optional.of(Mentor.mentorBuilder().build())); + + var openCycle = + MentorshipCycleEntity.builder() + .cycleId(1L) + .status(CycleStatus.OPEN) + .mentorshipType(MentorshipType.AD_HOC) + .build(); + when(cycleRepository.findOpenCycle()).thenReturn(Optional.of(openCycle)); } @Test @@ -110,8 +115,7 @@ void testSaveRegistrationMentee() { when(menteeRepository.findById(any())) .thenReturn(Optional.empty()) .thenReturn(Optional.of(mentee)); - when(cycleRepository.findByYearAndType(currentYear, MentorshipType.AD_HOC)) - .thenReturn(Optional.of(cycle)); + when(cycleRepository.findOpenCycle()).thenReturn(Optional.of(cycle)); when(applicationRepository.findByMenteeAndCycle(any(), any())).thenReturn(List.of()); Mentee result = menteeService.saveRegistration(registration); @@ -160,8 +164,7 @@ void shouldThrowExceptionWhenRegistrationLimitExceeded() { .build(); when(menteeRepository.findById(1L)).thenReturn(Optional.of(menteeWithId)); - when(cycleRepository.findByYearAndType(currentYear, MentorshipType.AD_HOC)) - .thenReturn(Optional.of(cycle)); + when(cycleRepository.findOpenCycle()).thenReturn(Optional.of(cycle)); when(applicationRepository.findByMenteeAndCycle(any(), any())).thenReturn(List.of()); when(applicationRepository.countMenteeApplications(1L, 1L)).thenReturn(5L); @@ -210,8 +213,7 @@ void shouldThrowExceptionWhenPriorityAlreadyExists() { .build(); when(menteeRepository.findById(1L)).thenReturn(Optional.of(menteeWithId)); - when(cycleRepository.findByYearAndType(currentYear, MentorshipType.AD_HOC)) - .thenReturn(Optional.of(cycle)); + when(cycleRepository.findOpenCycle()).thenReturn(Optional.of(cycle)); // Simulate existing application with priority 1 var existingApplication = @@ -264,8 +266,7 @@ void shouldThrowExceptionWhenPriorityDuplicatedInRequest() { .build(); when(menteeRepository.findById(1L)).thenReturn(Optional.of(menteeWithId)); - when(cycleRepository.findByYearAndType(currentYear, MentorshipType.AD_HOC)) - .thenReturn(Optional.of(cycle)); + when(cycleRepository.findOpenCycle()).thenReturn(Optional.of(cycle)); when(applicationRepository.findByMenteeAndCycle(1L, 1L)).thenReturn(List.of()); @@ -297,14 +298,14 @@ void shouldThrowExceptionWhenCycleIsClosed() { currentYear, List.of( new MenteeApplicationDto(1L, 1, "Test application message", "Test why mentor"))); - when(mentorshipService.getCurrentCycle()).thenReturn(MentorshipService.CYCLE_CLOSED); + when(cycleRepository.findOpenCycle()).thenReturn(Optional.empty()); MentorshipCycleClosedException exception = assertThrows( MentorshipCycleClosedException.class, () -> menteeService.saveRegistration(registration)); - assertThat(exception.getMessage()).contains("Mentorship cycle is currently closed"); + assertThat(exception.getMessage()).contains("Mentorship cycle is closed"); } @Test @@ -321,8 +322,14 @@ void shouldThrowExceptionWhenMenteeTypeDoesNotMatchCycleType() { List.of( new MenteeApplicationDto(1L, 1, "Test application message", "Test why mentor"))); - MentorshipCycle longTermCycle = new MentorshipCycle(MentorshipType.LONG_TERM, Month.MARCH); - when(mentorshipService.getCurrentCycle()).thenReturn(longTermCycle); + when(cycleRepository.findOpenCycle()) + .thenReturn( + Optional.of( + MentorshipCycleEntity.builder() + .mentorshipType(MentorshipType.LONG_TERM) + .cycleMonth(Month.MARCH) + .status(CycleStatus.OPEN) + .build())); InvalidMentorshipTypeException exception = assertThrows( @@ -347,8 +354,14 @@ void shouldSaveRegistrationMenteeWhenCycleIsOpenAndTypeMatches() { List.of( new MenteeApplicationDto(1L, 1, "Test application message", "Test why mentor"))); - MentorshipCycle adHocCycle = new MentorshipCycle(MentorshipType.AD_HOC, Month.MAY); - when(mentorshipService.getCurrentCycle()).thenReturn(adHocCycle); + var cycle = + MentorshipCycleEntity.builder() + .mentorshipType(MentorshipType.AD_HOC) + .cycleMonth(Month.MAY) + .status(CycleStatus.OPEN) + .build(); + when(cycleRepository.findOpenCycle()).thenReturn(Optional.of(cycle)); + Member existingMember = Member.builder().id(1L).build(); when(memberRepository.findByEmail(anyString())).thenReturn(Optional.of(existingMember)); when(menteeRepository.create(any(Mentee.class))) @@ -364,7 +377,7 @@ void shouldSaveRegistrationMenteeWhenCycleIsOpenAndTypeMatches() { assertThat(result).isEqualTo(mentee); verify(menteeRepository).create(any(Mentee.class)); - verify(mentorshipService).getCurrentCycle(); + verify(cycleRepository, atLeastOnce()).findOpenCycle(); verify(mentorRepository).findById(1L); } @@ -406,8 +419,14 @@ void shouldSkipValidationWhenValidationIsDisabled() { when(validation.isEnabled()).thenReturn(false); when(cycleRepository.findByYearAndType(any(), any())).thenReturn(Optional.empty()); - when(mentorshipService.getCurrentCycle()) - .thenReturn(new MentorshipCycle(MentorshipType.AD_HOC, Month.JANUARY)); + when(cycleRepository.findOpenCycle()) + .thenReturn( + Optional.of( + MentorshipCycleEntity.builder() + .mentorshipType(MentorshipType.AD_HOC) + .cycleMonth(Month.MAY) + .status(CycleStatus.OPEN) + .build())); Member existingMember = Member.builder().id(1L).build(); when(memberRepository.findByEmail(anyString())).thenReturn(Optional.of(existingMember)); when(menteeRepository.create(any())).thenAnswer(invocation -> invocation.getArgument(0)); @@ -422,7 +441,7 @@ void shouldSkipValidationWhenValidationIsDisabled() { assertThat(result).isEqualTo(mentee); verify(menteeRepository).create(any(Mentee.class)); - verify(mentorshipService).getCurrentCycle(); + verify(cycleRepository, atLeastOnce()).findOpenCycle(); } @Test @@ -465,8 +484,7 @@ void shouldUseExistingMemberWhenMenteeEmailAlreadyExists() { .build(); when(memberRepository.findByEmail(mentee.getEmail())).thenReturn(Optional.of(existingMember)); - when(cycleRepository.findByYearAndType(currentYear, MentorshipType.AD_HOC)) - .thenReturn(Optional.of(cycle)); + when(cycleRepository.findOpenCycle()).thenReturn(Optional.of(cycle)); when(applicationRepository.findByMenteeAndCycle(any(), any())).thenReturn(List.of()); when(menteeRepository.create(any(Mentee.class))) .thenAnswer(invocation -> invocation.getArgument(0)); @@ -509,8 +527,7 @@ void shouldFallbackToExistingMemberWhenProvidedIdDoesNotExistButEmailExists() { when(menteeRepository.findById(12_345L)).thenReturn(Optional.empty()); when(memberRepository.findByEmail("existing@test.com")) .thenReturn(Optional.of(Member.builder().id(999L).build())); - when(cycleRepository.findByYearAndType(currentYear, MentorshipType.AD_HOC)) - .thenReturn(Optional.of(cycle)); + when(cycleRepository.findOpenCycle()).thenReturn(Optional.of(cycle)); when(applicationRepository.findByMenteeAndCycle(any(), any())).thenReturn(List.of()); when(applicationRepository.countMenteeApplications(any(), any())).thenReturn(0L); when(menteeRepository.findById(999L)) @@ -556,14 +573,13 @@ void shouldDefaultToCurrentYearWhenCycleYearIsNull() { when(menteeRepository.findById(any())) .thenReturn(Optional.empty()) .thenReturn(Optional.of(mentee)); - when(cycleRepository.findByYearAndType(currentYear, MentorshipType.AD_HOC)) - .thenReturn(Optional.of(cycle)); + when(cycleRepository.findOpenCycle()).thenReturn(Optional.of(cycle)); when(applicationRepository.findByMenteeAndCycle(any(), any())).thenReturn(List.of()); Mentee result = menteeService.saveRegistration(registration); assertThat(result).isEqualTo(mentee); - verify(cycleRepository).findByYearAndType(currentYear, MentorshipType.AD_HOC); + verify(cycleRepository, atLeastOnce()).findOpenCycle(); } @Test @@ -588,8 +604,7 @@ void shouldReturnExistingMenteeWhenIdExistsInRepository() { .status(CycleStatus.OPEN) .build(); - when(cycleRepository.findByYearAndType(currentYear, MentorshipType.AD_HOC)) - .thenReturn(Optional.of(cycle)); + when(cycleRepository.findOpenCycle()).thenReturn(Optional.of(cycle)); when(menteeRepository.findById(5L)).thenReturn(Optional.of(existingMentee)); when(applicationRepository.findByMenteeAndCycle(any(), any())).thenReturn(List.of()); when(applicationRepository.countMenteeApplications(any(), any())).thenReturn(0L); @@ -640,8 +655,7 @@ void shouldPreserveMemberTypesWhenExistingMentorRegistersAsMentee() { .thenReturn(Optional.of(existingMentorMember)); when(memberRepository.findById(100L)).thenReturn(Optional.of(existingMentorMember)); when(menteeRepository.findById(100L)).thenReturn(Optional.empty()); - when(cycleRepository.findByYearAndType(currentYear, MentorshipType.AD_HOC)) - .thenReturn(Optional.of(cycle)); + when(cycleRepository.findOpenCycle()).thenReturn(Optional.of(cycle)); when(applicationRepository.findByMenteeAndCycle(any(), any())).thenReturn(List.of()); when(applicationRepository.countMenteeApplications(100L, 1L)).thenReturn(0L); // Capture the created mentee to verify member types diff --git a/src/test/java/com/wcc/platform/service/MentorshipServiceFilteringTest.java b/src/test/java/com/wcc/platform/service/MentorshipServiceFilteringTest.java index cc42194c..80edd008 100644 --- a/src/test/java/com/wcc/platform/service/MentorshipServiceFilteringTest.java +++ b/src/test/java/com/wcc/platform/service/MentorshipServiceFilteringTest.java @@ -17,9 +17,10 @@ import com.wcc.platform.domain.cms.pages.mentorship.MentorsPage; 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.LanguageProficiency; import com.wcc.platform.domain.platform.mentorship.Mentor; -import com.wcc.platform.domain.platform.mentorship.MentorshipCycle; +import com.wcc.platform.domain.platform.mentorship.MentorshipCycleEntity; import com.wcc.platform.domain.platform.mentorship.MentorshipType; import com.wcc.platform.domain.platform.mentorship.Skills; import com.wcc.platform.domain.platform.mentorship.TechnicalAreaProficiency; @@ -29,6 +30,7 @@ import com.wcc.platform.repository.MemberProfilePictureRepository; import com.wcc.platform.repository.MemberRepository; import com.wcc.platform.repository.MentorRepository; +import com.wcc.platform.repository.MentorshipCycleRepository; import java.time.Month; import java.util.List; import org.junit.jupiter.api.BeforeEach; @@ -43,6 +45,7 @@ class MentorshipServiceFilteringTest { @Mock private MentorRepository mentorRepository; @Mock private MemberRepository memberRepository; + @Mock private MentorshipCycleRepository cycleRepository; @Mock private MemberProfilePictureRepository profilePicRepo; @Mock private UserProvisionService userProvisionService; @Mock private MentorshipNotificationService notificationService; @@ -55,8 +58,19 @@ void setUp() { service = spy( new MentorshipService( - mentorRepository, memberRepository, userProvisionService, profilePicRepo, 10, notificationService)); - doReturn(new MentorshipCycle(MentorshipType.AD_HOC, Month.MAY)).when(service).getCurrentCycle(); + mentorRepository, + memberRepository, + cycleRepository, + userProvisionService, + profilePicRepo, + notificationService)); + var cycle = + MentorshipCycleEntity.builder() + .mentorshipType(MentorshipType.AD_HOC) + .cycleMonth(Month.MAY) + .status(CycleStatus.OPEN) + .build(); + doReturn(cycle).when(service).getCurrentCycle(); mentorsPage = SetupMentorshipPagesFactories.createMentorPageTest(); mentor1 = buildMentor( diff --git a/src/test/java/com/wcc/platform/service/MentorshipServiceRetrievalTest.java b/src/test/java/com/wcc/platform/service/MentorshipServiceRetrievalTest.java index 09294e76..41ff926e 100644 --- a/src/test/java/com/wcc/platform/service/MentorshipServiceRetrievalTest.java +++ b/src/test/java/com/wcc/platform/service/MentorshipServiceRetrievalTest.java @@ -2,7 +2,7 @@ import static com.wcc.platform.factories.SetupMentorFactories.createMemberProfilePictureTest; import static com.wcc.platform.factories.SetupMentorFactories.createResourceTest; -import static com.wcc.platform.service.MentorshipService.CYCLE_CLOSED; +import static com.wcc.platform.service.MentorshipService.CLOSED_CYCLE; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Answers.RETURNS_DEEP_STUBS; @@ -14,16 +14,16 @@ import com.wcc.platform.domain.cms.attributes.ImageType; 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.MentorshipCycle; +import com.wcc.platform.domain.platform.mentorship.MentorshipCycleEntity; import com.wcc.platform.domain.platform.mentorship.MentorshipType; import com.wcc.platform.repository.MemberProfilePictureRepository; import com.wcc.platform.repository.MemberRepository; import com.wcc.platform.repository.MentorRepository; +import com.wcc.platform.repository.MentorshipCycleRepository; import java.time.Month; -import java.time.ZoneId; -import java.time.ZonedDateTime; import java.util.List; import java.util.Optional; import org.junit.jupiter.api.BeforeEach; @@ -39,6 +39,7 @@ class MentorshipServiceRetrievalTest { @Mock private MentorRepository mentorRepository; @Mock private MemberRepository memberRepository; + @Mock private MentorshipCycleRepository cycleRepository; @Mock private UserProvisionService userProvisionService; @Mock private MemberProfilePictureRepository profilePicRepo; @Mock private MentorshipNotificationService notificationService; @@ -46,17 +47,15 @@ class MentorshipServiceRetrievalTest { @BeforeEach void setUp() { - final var daysOpen = 10; - MockitoAnnotations.openMocks(this); service = spy( new MentorshipService( mentorRepository, memberRepository, + cycleRepository, userProvisionService, profilePicRepo, - daysOpen, notificationService)); } @@ -69,7 +68,7 @@ void whenGetAllMentorsGivenCycleClosedThenReturnDtos() { when(mentorRepository.getAll()).thenReturn(List.of(mentor)); when(profilePicRepo.findByMemberId(1L)).thenReturn(Optional.empty()); - doReturn(CYCLE_CLOSED).when(service).getCurrentCycle(); + doReturn(CLOSED_CYCLE).when(service).getCurrentCycle(); var result = service.getAllActiveMentors(); @@ -85,7 +84,11 @@ void whenGetAllMentorsGivenAdHocCycleOpenThenReturnDtos() { when(dto.getId()).thenReturn(1L); when(mentorRepository.getAll()).thenReturn(List.of(mentor)); - var cycle = new MentorshipCycle(MentorshipType.AD_HOC, Month.MAY); + var cycle = + MentorshipCycleEntity.builder() + .mentorshipType(MentorshipType.AD_HOC) + .cycleMonth(Month.MAY) + .build(); doReturn(cycle).when(service).getCurrentCycle(); var result = service.getAllActiveMentors(); @@ -103,7 +106,11 @@ void whenGetAllMentorsGivenLongTermCycleOpenThenReturnDtos() { when(mentorRepository.getAll()).thenReturn(List.of(mentor)); when(profilePicRepo.findByMemberId(1L)).thenReturn(Optional.empty()); - var cycle = new MentorshipCycle(MentorshipType.LONG_TERM, Month.MARCH); + var cycle = + MentorshipCycleEntity.builder() + .mentorshipType(MentorshipType.LONG_TERM) + .cycleMonth(Month.MARCH) + .build(); doReturn(cycle).when(service).getCurrentCycle(); var result = service.getAllActiveMentors(); @@ -116,42 +123,30 @@ private T any() { } @Test - void testGetCurrentCycleReturnsLongTermDuringMarchWithinOpenDays() { - var marchDate = ZonedDateTime.of(2024, 3, 5, 12, 0, 0, 0, ZoneId.of("Europe/London")); - doReturn(marchDate).when(service).nowLondon(); - - var result = service.getCurrentCycle(); - - assertEquals(MentorshipType.LONG_TERM, result.cycle()); - } - - @Test - void testGetCurrentCycleReturnsAdHocFromMayWithinOpenDays() { - var mayDate = ZonedDateTime.of(2024, 5, 5, 12, 0, 0, 0, ZoneId.of("Europe/London")); - doReturn(mayDate).when(service).nowLondon(); + void testGetCurrentCycleReturnsOpenCycle() { + var cycle = + MentorshipCycleEntity.builder() + .mentorshipType(MentorshipType.LONG_TERM) + .cycleMonth(Month.MARCH) + .status(CycleStatus.OPEN) + .build(); + when(cycleRepository.findOpenCycle()).thenReturn(Optional.of(cycle)); var result = service.getCurrentCycle(); - assertEquals(MentorshipType.AD_HOC, result.cycle()); - assertEquals(Month.MAY, result.month()); + assertEquals(MentorshipType.LONG_TERM, result.getMentorshipType()); + assertEquals(Month.MARCH, result.getCycleMonth()); } @Test - void testGetCurrentCycleReturnsClosedOutsideWindows() { - // January - Closed - var janDate = ZonedDateTime.of(2024, 1, 5, 12, 0, 0, 0, ZoneId.of("Europe/London")); - doReturn(janDate).when(service).nowLondon(); - assertEquals(CYCLE_CLOSED, service.getCurrentCycle()); - - // March after 10 days - Closed - var marchLateDate = ZonedDateTime.of(2024, 3, 15, 12, 0, 0, 0, ZoneId.of("Europe/London")); - doReturn(marchLateDate).when(service).nowLondon(); - assertEquals(CYCLE_CLOSED, service.getCurrentCycle()); - - // April - Closed - var aprilDate = ZonedDateTime.of(2024, 4, 5, 12, 0, 0, 0, ZoneId.of("Europe/London")); - doReturn(aprilDate).when(service).nowLondon(); - assertEquals(CYCLE_CLOSED, service.getCurrentCycle()); + void testGetCurrentCycleReturnsClosedWhenNoOpenCycle() { + when(cycleRepository.findOpenCycle()).thenReturn(Optional.empty()); + + try { + service.getCurrentCycle(); + } catch (com.wcc.platform.domain.exceptions.MentorshipCycleClosedException e) { + assertEquals("Mentorship cycle is closed", e.getMessage()); + } } @Test @@ -171,7 +166,7 @@ void shouldMergeProfilePictureIntoImagesWhenMentorHasProfilePicture() { var profilePicture = createMemberProfilePictureTest(1L).toBuilder().resource(resource).build(); when(profilePicRepo.findByMemberId(1L)).thenReturn(Optional.of(profilePicture)); - doReturn(CYCLE_CLOSED).when(service).getCurrentCycle(); + doReturn(CLOSED_CYCLE).when(service).getCurrentCycle(); var result = service.getAllActiveMentors(); @@ -196,7 +191,7 @@ void shouldReturnEmptyImagesWhenMentorHasNoProfilePicture() { when(mentorRepository.getAll()).thenReturn(List.of(mentor)); when(profilePicRepo.findByMemberId(1L)).thenReturn(Optional.empty()); - doReturn(CYCLE_CLOSED).when(service).getCurrentCycle(); + doReturn(CLOSED_CYCLE).when(service).getCurrentCycle(); var result = service.getAllActiveMentors(); @@ -218,7 +213,7 @@ void shouldHandleExceptionWhenFetchingProfilePictureFails() { when(mentorRepository.getAll()).thenReturn(List.of(mentor)); when(profilePicRepo.findByMemberId(1L)).thenThrow(new RuntimeException("Database error")); - doReturn(CYCLE_CLOSED).when(service).getCurrentCycle(); + doReturn(CLOSED_CYCLE).when(service).getCurrentCycle(); var result = service.getAllActiveMentors(); @@ -245,7 +240,7 @@ void shouldPreservePronounsWhenEnrichedWithProfilePicture() { var profilePicture = createMemberProfilePictureTest(1L).toBuilder().resource(resource).build(); when(profilePicRepo.findByMemberId(1L)).thenReturn(Optional.of(profilePicture)); - doReturn(CYCLE_CLOSED).when(service).getCurrentCycle(); + doReturn(CLOSED_CYCLE).when(service).getCurrentCycle(); var result = service.getAllActiveMentors(); diff --git a/src/test/java/com/wcc/platform/service/MentorshipServiceTest.java b/src/test/java/com/wcc/platform/service/MentorshipServiceTest.java index 9f833844..5409c711 100644 --- a/src/test/java/com/wcc/platform/service/MentorshipServiceTest.java +++ b/src/test/java/com/wcc/platform/service/MentorshipServiceTest.java @@ -25,11 +25,14 @@ 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.exceptions.MentorshipCycleClosedException; 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.MentorshipCycle; +import com.wcc.platform.domain.platform.mentorship.MentorshipCycleEntity; import com.wcc.platform.domain.platform.mentorship.MentorshipType; import com.wcc.platform.domain.platform.mentorship.Skills; import com.wcc.platform.domain.platform.type.MemberType; @@ -37,6 +40,7 @@ import com.wcc.platform.repository.MemberProfilePictureRepository; import com.wcc.platform.repository.MemberRepository; import com.wcc.platform.repository.MentorRepository; +import com.wcc.platform.repository.MentorshipCycleRepository; import java.time.Month; import java.util.List; import java.util.Optional; @@ -49,7 +53,7 @@ import org.mockito.MockitoAnnotations; import org.mockito.junit.jupiter.MockitoExtension; -@SuppressWarnings("PMD.TooManyMethods") +@SuppressWarnings({"PMD.TooManyMethods", "PMD.ExcessiveImports"}) @ExtendWith(MockitoExtension.class) class MentorshipServiceTest { @@ -58,6 +62,7 @@ class MentorshipServiceTest { @Mock private UserProvisionService userProvisionService; @Mock private MemberProfilePictureRepository profilePicRepo; @Mock private MentorshipNotificationService notificationService; + @Mock private MentorshipCycleRepository cycleRepository; private Mentor mentor; private Mentor updatedMentor; private MentorDto mentorDto; @@ -81,9 +86,9 @@ void setUp() { new MentorshipService( mentorRepository, memberRepository, + cycleRepository, userProvisionService, profilePicRepo, - daysOpen, notificationService)); } @@ -103,7 +108,15 @@ void whenCreateGivenMentorAlreadyExistsThenThrowDuplicatedMemberException() { @Test @DisplayName( - "Given mentor with new email and no existing ID conflict, when creating mentor, then create and provision user role") + "Given closed cycle, when get current cycle, then throw MentorshipCycleClosedException") + void whenGetCurrentCycleGivenNoOpenCycleThenThrowException() { + when(cycleRepository.findOpenCycle()).thenReturn(Optional.empty()); + + var exception = + assertThrows(MentorshipCycleClosedException.class, service::getOpenCycle); + assertEquals("Mentorship cycle is closed", exception.getMessage()); + } + void whenCreateGivenMentorDoesNotExistThenCreateMentor() { var mentor = mock(Mentor.class); var menteeSection = mock(MenteeSection.class); @@ -449,15 +462,21 @@ void shouldReturnOnlyActiveMentorsWhenCycleIsOpen() { var activeMentor = mock(Mentor.class); var pendingMentor = mock(Mentor.class); var activeMentorDto = mock(MentorDto.class); - var openCycle = new MentorshipCycle(MentorshipType.LONG_TERM, Month.MARCH); + var openCycle = + Optional.of( + MentorshipCycleEntity.builder() + .mentorshipType(MentorshipType.LONG_TERM) + .cycleMonth(Month.MARCH) + .status(CycleStatus.OPEN) + .build()); when(activeMentor.getProfileStatus()).thenReturn(ProfileStatus.ACTIVE); when(pendingMentor.getProfileStatus()).thenReturn(ProfileStatus.PENDING); - when(activeMentor.toDto(openCycle)).thenReturn(activeMentorDto); + when(activeMentor.toDto(any(MentorshipCycle.class))).thenReturn(activeMentorDto); when(activeMentorDto.getId()).thenReturn(1L); when(mentorRepository.getAll()).thenReturn(List.of(activeMentor, pendingMentor)); when(profilePicRepo.findByMemberId(1L)).thenReturn(Optional.empty()); - when(service.getCurrentCycle()).thenReturn(openCycle); + when(cycleRepository.findOpenCycle()).thenReturn(openCycle); List result = service.getAllActiveMentors(); @@ -466,8 +485,8 @@ void shouldReturnOnlyActiveMentorsWhenCycleIsOpen() { @Test @DisplayName( - "Given active and pending mentors with a closed cycle, when getAllActiveMentors is called, then all" - + " active mentors are returned") + "Given active and pending mentors with a closed cycle, " + + "when getAllActiveMentors is called, then exception is throws for closed cycle") void shouldReturnOnlyActiveMentorsWhenCycleIsClosed() { var activeMentor = mock(Mentor.class); var pendingMentor = mock(Mentor.class); @@ -479,7 +498,8 @@ 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(MentorshipService.CYCLE_CLOSED); + when(service.getCurrentCycle()) + .thenReturn(MentorshipCycleEntity.builder().status(CycleStatus.CLOSED).build()); List result = service.getAllActiveMentors(); diff --git a/src/testInt/java/com/wcc/platform/service/MenteeServiceIntegrationTest.java b/src/testInt/java/com/wcc/platform/service/MenteeServiceIntegrationTest.java index b60ebf52..f8d08bb8 100644 --- a/src/testInt/java/com/wcc/platform/service/MenteeServiceIntegrationTest.java +++ b/src/testInt/java/com/wcc/platform/service/MenteeServiceIntegrationTest.java @@ -312,24 +312,39 @@ void shouldThrowExceptionWhenMentorDoesNotExist() { } private void ensureLongTermCycleExists() { - final var cycle = cycleRepository.findByYearAndType(TEST_YEAR_2026, MentorshipType.LONG_TERM); - if (cycle.isEmpty()) { - cycleRepository.create( - MentorshipCycleEntity.builder() - .cycleYear(TEST_YEAR_2026) - .mentorshipType(MentorshipType.LONG_TERM) - .cycleMonth(Month.MARCH) - .registrationStartDate(LocalDate.now().minusDays(1)) - .registrationEndDate(LocalDate.now().plusDays(10)) - .cycleStartDate(LocalDate.now().plusDays(15)) - .status(CycleStatus.OPEN) - .maxMenteesPerMentor(MAX_MENTEES_PER_MENTOR) - .description("Test Cycle") - .build()); - } + cycleRepository + .findOpenCycle() + .ifPresent( + cycle -> { + cycle.setStatus(CycleStatus.CLOSED); + cycleRepository.update(cycle.getCycleId(), cycle); + }); + + final var cycle = + MentorshipCycleEntity.builder() + .cycleYear(TEST_YEAR_2026) + .mentorshipType(MentorshipType.LONG_TERM) + .cycleMonth(Month.MARCH) + .registrationStartDate(LocalDate.now().minusDays(1)) + .registrationEndDate(LocalDate.now().plusDays(10)) + .cycleStartDate(LocalDate.now().plusDays(15)) + .status(CycleStatus.OPEN) + .maxMenteesPerMentor(MAX_MENTEES_PER_MENTOR) + .description("Test Cycle") + .build(); + final var savedCycle = cycleRepository.create(cycle); + createdCycles.add(savedCycle.getCycleId()); } private void createAdHocCycle() { + cycleRepository + .findOpenCycle() + .ifPresent( + cycle -> { + cycle.setStatus(CycleStatus.CLOSED); + cycleRepository.update(cycle.getCycleId(), cycle); + }); + final var adHocCycle = MentorshipCycleEntity.builder() .cycleYear(TEST_YEAR_2028) From 24a92982b6616cab25a345281ab38e0011de576f Mon Sep 17 00:00:00 2001 From: Adriana Zencke Zimmermann Date: Thu, 2 Apr 2026 23:00:24 +0200 Subject: [PATCH 2/5] chore: Remove unused daysCycleOpen config and dead test mocks The daysCycleOpen property and DEFAULT_DAYS constant are no longer used now that cycle state is DB-driven. Remove the field from MentorshipConfig, drop the property from application.yml, and delete the two daysCycleOpen tests from MentorshipConfigTest. Clean up dead MentorshipConfig and MentorshipService mocks from MenteeServiceTest, and remove the leftover unused daysOpen variable from MentorshipServiceTest setUp. --- .../platform/configuration/MentorshipConfig.java | 9 +-------- src/main/resources/application.yml | 1 - .../configuration/MentorshipConfigTest.java | 12 +----------- .../wcc/platform/service/MenteeServiceTest.java | 14 +++----------- .../platform/service/MentorshipServiceTest.java | 13 ------------- 5 files changed, 5 insertions(+), 44 deletions(-) diff --git a/src/main/java/com/wcc/platform/configuration/MentorshipConfig.java b/src/main/java/com/wcc/platform/configuration/MentorshipConfig.java index b9b24ad9..b1ae1484 100644 --- a/src/main/java/com/wcc/platform/configuration/MentorshipConfig.java +++ b/src/main/java/com/wcc/platform/configuration/MentorshipConfig.java @@ -9,8 +9,7 @@ * Configuration properties for mentorship-related settings. This class binds to properties under * the prefix "mentorship" in the application configuration file. * - *

Properties: - daysCycleOpen: Number of days a mentorship cycle remains open for registration. - * Default is 10. - validation.enabled: Flag to enable or disable mentorship cycle validation during + *

Properties: - validation.enabled: Flag to enable or disable mentorship cycle validation during * mentee registration. Default is true. Set to false for testing and debugging purposes. */ @Getter @@ -19,12 +18,6 @@ @ConfigurationProperties(prefix = "mentorship") public class MentorshipConfig { - /** the default n. days for the mentorship cycle remains open from the beginning of the month. */ - private static final int DEFAULT_DAYS = 10; - - /** Number of days a mentorship cycle remains open for registration. */ - private int daysCycleOpen = DEFAULT_DAYS; - /** Validation-specific configuration. */ private Validation validation = new Validation(); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 9acacbba..69ad343c 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -37,7 +37,6 @@ backend: url: ${SPRING_APP_BASE_URL:http://localhost:8080} mentorship: - daysCycleOpen: 10 validation: enabled: false diff --git a/src/test/java/com/wcc/platform/configuration/MentorshipConfigTest.java b/src/test/java/com/wcc/platform/configuration/MentorshipConfigTest.java index c17bad32..e865c178 100644 --- a/src/test/java/com/wcc/platform/configuration/MentorshipConfigTest.java +++ b/src/test/java/com/wcc/platform/configuration/MentorshipConfigTest.java @@ -12,20 +12,10 @@ class MentorshipConfigTest { void shouldHaveDefaultValues() { MentorshipConfig config = new MentorshipConfig(); - assertThat(config.getDaysCycleOpen()).isEqualTo(10); assertThat(config.getValidation()).isNotNull(); assertThat(config.getValidation().isEnabled()).isTrue(); } - @Test - @DisplayName("Given custom daysCycleOpen When setting value Then should update correctly") - void shouldSetDaysCycleOpen() { - MentorshipConfig config = new MentorshipConfig(); - config.setDaysCycleOpen(15); - - assertThat(config.getDaysCycleOpen()).isEqualTo(15); - } - @Test @DisplayName("Given validation enabled When disabling Then should update correctly") void shouldDisableValidation() { @@ -57,4 +47,4 @@ void shouldSetNewValidationObject() { assertThat(config.getValidation()).isEqualTo(newValidation); assertThat(config.getValidation().isEnabled()).isFalse(); } -} \ No newline at end of file +} diff --git a/src/test/java/com/wcc/platform/service/MenteeServiceTest.java b/src/test/java/com/wcc/platform/service/MenteeServiceTest.java index 87fb76b5..7542824a 100644 --- a/src/test/java/com/wcc/platform/service/MenteeServiceTest.java +++ b/src/test/java/com/wcc/platform/service/MenteeServiceTest.java @@ -12,7 +12,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import com.wcc.platform.configuration.MentorshipConfig; import com.wcc.platform.domain.exceptions.DuplicatedPriorityException; import com.wcc.platform.domain.exceptions.InvalidMentorshipTypeException; import com.wcc.platform.domain.exceptions.MenteeRegistrationLimitException; @@ -50,9 +49,6 @@ class MenteeServiceTest { @Mock private MenteeApplicationRepository applicationRepository; @Mock private MenteeRepository menteeRepository; - @Mock private MentorshipService mentorshipService; - @Mock private MentorshipConfig mentorshipConfig; - @Mock private MentorshipConfig.Validation validation; @Mock private MentorshipCycleRepository cycleRepository; @Mock private MemberRepository memberRepository; @Mock private MentorRepository mentorRepository; @@ -64,8 +60,6 @@ class MenteeServiceTest { @BeforeEach void setUp() { MockitoAnnotations.openMocks(this); - when(mentorshipConfig.getValidation()).thenReturn(validation); - when(validation.isEnabled()).thenReturn(true); menteeService = new MenteeService( cycleRepository, @@ -405,9 +399,9 @@ void shouldThrowExceptionWhenMentorDoesNotExist() { @Test @DisplayName( - "Given validation is disabled, when creating mentee, " - + "then should skip validation and create successfully") - void shouldSkipValidationWhenValidationIsDisabled() { + "Given an open cycle matching the registration type, when creating mentee, " + + "then should create successfully") + void shouldCreateMenteeWhenOpenCycleMatches() { var currentYear = Year.now(); MenteeRegistration registration = new MenteeRegistration( @@ -416,9 +410,7 @@ void shouldSkipValidationWhenValidationIsDisabled() { currentYear, List.of( new MenteeApplicationDto(1L, 1, "Test application message", "Test why mentor"))); - when(validation.isEnabled()).thenReturn(false); - when(cycleRepository.findByYearAndType(any(), any())).thenReturn(Optional.empty()); when(cycleRepository.findOpenCycle()) .thenReturn( Optional.of( diff --git a/src/test/java/com/wcc/platform/service/MentorshipServiceTest.java b/src/test/java/com/wcc/platform/service/MentorshipServiceTest.java index 5409c711..8a9b5492 100644 --- a/src/test/java/com/wcc/platform/service/MentorshipServiceTest.java +++ b/src/test/java/com/wcc/platform/service/MentorshipServiceTest.java @@ -25,7 +25,6 @@ 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.exceptions.MentorshipCycleClosedException; import com.wcc.platform.domain.platform.member.Member; import com.wcc.platform.domain.platform.member.ProfileStatus; import com.wcc.platform.domain.platform.mentorship.CycleStatus; @@ -75,7 +74,6 @@ public MentorshipServiceTest() { @BeforeEach void setUp() { - final int daysOpen = 10; MockitoAnnotations.openMocks(this); mentor = createMentorTest(); mentorDto = createMentorDtoTest(1L, MemberType.DIRECTOR); @@ -106,17 +104,6 @@ void whenCreateGivenMentorAlreadyExistsThenThrowDuplicatedMemberException() { verify(mentorRepository, never()).create(any()); } - @Test - @DisplayName( - "Given closed cycle, when get current cycle, then throw MentorshipCycleClosedException") - void whenGetCurrentCycleGivenNoOpenCycleThenThrowException() { - when(cycleRepository.findOpenCycle()).thenReturn(Optional.empty()); - - var exception = - assertThrows(MentorshipCycleClosedException.class, service::getOpenCycle); - assertEquals("Mentorship cycle is closed", exception.getMessage()); - } - void whenCreateGivenMentorDoesNotExistThenCreateMentor() { var mentor = mock(Mentor.class); var menteeSection = mock(MenteeSection.class); From 763492dbd9f849920ef2d2bdd4db1da417427ea3 Mon Sep 17 00:00:00 2001 From: Adriana Zencke Zimmermann Date: Thu, 2 Apr 2026 23:03:54 +0200 Subject: [PATCH 3/5] fix: Remove redundant null ternary and fix getOpenCycle Javadoc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In toOpenCycleValue(), the expression 'cycleMonth != null ? cycleMonth : null' is equivalent to just 'cycleMonth' — remove the redundant ternary. Fix the copy-pasted Javadoc on getOpenCycle() which incorrectly described the removed getMentorshipCycle() parameters instead of the actual no-arg signature. --- .../domain/platform/mentorship/MentorshipCycleEntity.java | 3 +-- .../java/com/wcc/platform/service/MentorshipService.java | 7 +++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/wcc/platform/domain/platform/mentorship/MentorshipCycleEntity.java b/src/main/java/com/wcc/platform/domain/platform/mentorship/MentorshipCycleEntity.java index 87da9bba..192d226f 100644 --- a/src/main/java/com/wcc/platform/domain/platform/mentorship/MentorshipCycleEntity.java +++ b/src/main/java/com/wcc/platform/domain/platform/mentorship/MentorshipCycleEntity.java @@ -58,8 +58,7 @@ public boolean isActive() { * @return MentorsPage.OpenCycle value object */ public MentorsPage.OpenCycle toOpenCycleValue() { - return new MentorshipCycle(mentorshipType, cycleMonth != null ? cycleMonth : null) - .toOpenCycle(); + return new MentorshipCycle(mentorshipType, cycleMonth).toOpenCycle(); } /** diff --git a/src/main/java/com/wcc/platform/service/MentorshipService.java b/src/main/java/com/wcc/platform/service/MentorshipService.java index 39dd96f1..1072a829 100644 --- a/src/main/java/com/wcc/platform/service/MentorshipService.java +++ b/src/main/java/com/wcc/platform/service/MentorshipService.java @@ -147,11 +147,10 @@ public MentorshipCycleEntity getCurrentCycle() { } /** - * Retrieves the MentorshipCycleEntity for the given mentorship type and cycle year. Validates - * that the cycle is open and the mentorship type matches the current cycle. + * Retrieves the currently open MentorshipCycleEntity. * - * @return The MentorshipCycleEntity - * @throws MentorshipCycleClosedException If the mentorship cycle is closed. + * @return The open MentorshipCycleEntity. + * @throws MentorshipCycleClosedException If no open cycle exists. */ public MentorshipCycleEntity getOpenCycle() { final var openCycle = cycleRepository.findOpenCycle(); From ac578852fe060fa0a493c782f1264b76711299ce Mon Sep 17 00:00:00 2001 From: Adriana Zencke Zimmermann Date: Thu, 2 Apr 2026 23:11:25 +0200 Subject: [PATCH 4/5] refactor: centralize FAIL_ON_UNKNOWN_PROPERTIES in ObjectMapperConfig Move Jackson unknown-property tolerance from per-class @JsonIgnoreProperties annotations to a single global DeserializationFeature configuration in ObjectMapperConfig, reducing annotation noise and ensuring consistent deserialization behaviour across all DTOs. --- .../java/com/wcc/platform/configuration/ObjectMapperConfig.java | 2 ++ .../domain/cms/pages/mentorship/MentorFilterSection.java | 2 -- .../java/com/wcc/platform/domain/platform/member/MemberDto.java | 2 -- .../com/wcc/platform/domain/platform/mentorship/MentorDto.java | 2 -- 4 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/wcc/platform/configuration/ObjectMapperConfig.java b/src/main/java/com/wcc/platform/configuration/ObjectMapperConfig.java index 6516a749..469d09c6 100644 --- a/src/main/java/com/wcc/platform/configuration/ObjectMapperConfig.java +++ b/src/main/java/com/wcc/platform/configuration/ObjectMapperConfig.java @@ -1,6 +1,7 @@ package com.wcc.platform.configuration; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.module.SimpleModule; @@ -41,6 +42,7 @@ public ObjectMapper objectMapper() { final ObjectMapper objectMapper = new ObjectMapper(); objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); diff --git a/src/main/java/com/wcc/platform/domain/cms/pages/mentorship/MentorFilterSection.java b/src/main/java/com/wcc/platform/domain/cms/pages/mentorship/MentorFilterSection.java index ff3715ba..1532f030 100644 --- a/src/main/java/com/wcc/platform/domain/cms/pages/mentorship/MentorFilterSection.java +++ b/src/main/java/com/wcc/platform/domain/cms/pages/mentorship/MentorFilterSection.java @@ -1,6 +1,5 @@ package com.wcc.platform.domain.cms.pages.mentorship; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.wcc.platform.domain.platform.mentorship.MentorshipType; import com.wcc.platform.domain.platform.mentorship.SkillsFilter; import java.util.List; @@ -18,7 +17,6 @@ @EqualsAndHashCode @ToString @Getter -@JsonIgnoreProperties(ignoreUnknown = true) public class MentorFilterSection { private String keyword; private List types; 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 424a5afe..b1118fb1 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 @@ -1,6 +1,5 @@ package com.wcc.platform.domain.platform.member; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.wcc.platform.domain.cms.attributes.Country; import com.wcc.platform.domain.cms.attributes.Image; @@ -23,7 +22,6 @@ @EqualsAndHashCode @ToString @Builder -@JsonIgnoreProperties(ignoreUnknown = true) public class MemberDto { @Schema(accessMode = Schema.AccessMode.READ_ONLY) @JsonProperty(access = JsonProperty.Access.READ_ONLY) 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 6a1d2616..c134b025 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 @@ -1,6 +1,5 @@ package com.wcc.platform.domain.platform.mentorship; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.wcc.platform.domain.cms.attributes.Country; import com.wcc.platform.domain.cms.attributes.Image; @@ -31,7 +30,6 @@ @ToString @NoArgsConstructor @SuppressWarnings("PMD.ImmutableField") -@JsonIgnoreProperties(ignoreUnknown = true) public class MentorDto extends MemberDto { /** From e7411e3a0ee54eeb257d4bd5b6a26c8916b46d2c Mon Sep 17 00:00:00 2001 From: Adriana Zencke Zimmermann Date: Thu, 2 Apr 2026 23:27:02 +0200 Subject: [PATCH 5/5] refactor: remove getOpenCycle and update unknown-field test Remove unused getOpenCycle() from MentorshipService as no callers remain. Remove the unknown-field-causes-400 controller test now that FAIL_ON_UNKNOWN_PROPERTIES is globally disabled in ObjectMapperConfig. --- .../platform/service/MentorshipService.java | 14 ----- .../controller/MentorControllerTest.java | 59 ------------------- 2 files changed, 73 deletions(-) diff --git a/src/main/java/com/wcc/platform/service/MentorshipService.java b/src/main/java/com/wcc/platform/service/MentorshipService.java index 1072a829..e6008167 100644 --- a/src/main/java/com/wcc/platform/service/MentorshipService.java +++ b/src/main/java/com/wcc/platform/service/MentorshipService.java @@ -7,7 +7,6 @@ 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.exceptions.MentorshipCycleClosedException; import com.wcc.platform.domain.platform.member.ProfileStatus; import com.wcc.platform.domain.platform.mentorship.CycleStatus; import com.wcc.platform.domain.platform.mentorship.Mentor; @@ -146,19 +145,6 @@ public MentorshipCycleEntity getCurrentCycle() { return openCycle.orElse(CLOSED_CYCLE); } - /** - * Retrieves the currently open MentorshipCycleEntity. - * - * @return The open MentorshipCycleEntity. - * @throws MentorshipCycleClosedException If no open cycle exists. - */ - public MentorshipCycleEntity getOpenCycle() { - final var openCycle = cycleRepository.findOpenCycle(); - - return openCycle.orElseThrow( - () -> new MentorshipCycleClosedException("Mentorship cycle is closed")); - } - /** * Return all ACTIVE mentors in the current cycle. Mentors with PENDING or REJECTED status are * always excluded regardless of the cycle state. diff --git a/src/test/java/com/wcc/platform/controller/MentorControllerTest.java b/src/test/java/com/wcc/platform/controller/MentorControllerTest.java index 89eda513..3a426ea4 100644 --- a/src/test/java/com/wcc/platform/controller/MentorControllerTest.java +++ b/src/test/java/com/wcc/platform/controller/MentorControllerTest.java @@ -78,65 +78,6 @@ void shouldCreateMentorAndReturnCreated() throws Exception { .andExpect(jsonPath("$.profileStatus", is("PENDING"))); } - @Test - @DisplayName( - "Given mentor payload with unknown nested field, " - + "when creating mentor, then return 400 with field context") - void shouldReturnBadRequestWithFieldContextForUnknownJsonField() throws Exception { - var invalidMentorPayload = - """ - { - "fullName": "fullName MENTOR", - "position": "position MENTOR", - "email": "email@mentor", - "slackDisplayName": "slackDisplayName", - "country": { - "countryCode": "ES", - "countryName": "Spain" - }, - "city": "City", - "companyName": "Company name", - "images": [], - "network": [], - "bio": "Mentor bio", - "spokenLanguages": ["English"], - "skills": { - "yearsExperience": 2, - "areas": [ - { - "name": "BACKEND", - "proficiencyLevel": "BEGINNER" - } - ], - "languages": [], - "mentorshipFocus": [] - }, - "menteeSection": { - "idealMentee": "ideal mentee description", - "additional": "additional", - "longTerm": { - "maxMentees": 1, - "hoursPerMentee": 4 - }, - "adHoc": [] - } - } - """; - - mockMvc - .perform( - MockMvcRequestBuilders.post(API_MENTORS) - .header(API_KEY_HEADER, API_KEY_VALUE) - .contentType(APPLICATION_JSON) - .content(invalidMentorPayload)) - .andExpect(status().isBadRequest()) - .andExpect( - jsonPath( - "$.message", - is( - "Unrecognized field 'name' at 'skills.areas[0].name'. Allowed fields: proficiencyLevel, technicalArea"))); - } - @Test @DisplayName("Given valid mentor ID and DTO, when updating mentor, then return 200 OK") void shouldUpdateMentorAndReturnOk() throws Exception {